Skip to content

Commit

Permalink
refactor: Introduce Disposer util
Browse files Browse the repository at this point in the history
  • Loading branch information
manzt committed Jun 29, 2024
1 parent 3051008 commit f5e0e29
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 26 deletions.
20 changes: 20 additions & 0 deletions js/lib.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as nv from "@niivue/niivue";
import type { AnyModel } from "@anywidget/types";
import type { Model } from "./types.ts";

Expand Down Expand Up @@ -50,3 +51,22 @@ export function determine_update_type<T>(
}
return "unknown";
}

/**
* A class to keep track of disposers for callbacks for updating the scene.
*/
export class Disposer {
#disposers = new Map<string, () => void>();
register(obj: nv.NVMesh | nv.NVImage, disposer: () => void): void {
const prefix = obj instanceof nv.NVMesh ? "mesh" : "image";
this.#disposers.set(`${prefix}:${obj.name}`, disposer);
}
disposeAll(kind?: "mesh" | "image"): void {
for (const [name, dispose] of this.#disposers) {
if (!kind || name.startsWith(kind)) {
dispose();
this.#disposers.delete(name);
}
}
}
}
24 changes: 14 additions & 10 deletions js/mesh.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as niivue from "@niivue/niivue";
import { determine_update_type, gather_models, unique_id } from "./lib.ts";
import * as lib from "./lib.ts";
import type { MeshModel, Model } from "./types.ts";

/**
Expand Down Expand Up @@ -63,29 +63,33 @@ function create_mesh(
export async function render_meshes(
nv: niivue.Niivue,
model: Model,
cleanups: Map<string, () => void>,
disposer: lib.Disposer,
) {
const mmodels = await gather_models<MeshModel>(model, model.get("_meshes"));
const mmodels = await lib.gather_models<MeshModel>(
model,
model.get("_meshes"),
);
const curr_names = nv.meshes.map((m) => m.name);
const new_names = mmodels.map(unique_id);
const update_type = determine_update_type(curr_names, new_names);
const new_names = mmodels.map(lib.unique_id);
const update_type = lib.determine_update_type(curr_names, new_names);
if (update_type === "add") {
// We know that the new meshes are the same as the old meshes,
// except for the last one. We can just add the last mesh.
const mmodel = mmodels[mmodels.length - 1];
const [mesh, cleanup] = create_mesh(nv, mmodel);
cleanups.set(mesh.name, cleanup);
disposer.register(mesh, cleanup);
nv.addMesh(mesh);
return;
}
// HERE can be the place to add more update types
for (const [_, cleanup] of cleanups) cleanup();
cleanups.clear();

// If we can't determine the update type, we need
// to remove all the meshes
disposer.disposeAll("mesh");

// create each mesh and add one-by-one
for (const mmodel of mmodels) {
const [mesh, cleanup] = create_mesh(nv, mmodel);
cleanups.set(mesh.name, cleanup);
disposer.register(mesh, cleanup);
nv.addMesh(mesh);
}
}
1 change: 1 addition & 0 deletions js/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type VolumeModel = { model_id: string } & AnyModel<{
path: File;
colormap: string;
opacity: number;
visible: boolean;
colorbar_visible: boolean;
cal_min?: number;
cal_max?: number;
Expand Down
16 changes: 11 additions & 5 deletions js/volume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function create_volume(
undefined, // trustMinCalMinMax
undefined, // percentileFrac
undefined, // ignoreZeroVoxels
vmodel.get("visible"), // visible
undefined, // useQFormNotSForm
undefined, // colormapNegative
undefined, // frame4D
Expand Down Expand Up @@ -50,11 +51,16 @@ function create_volume(
volume.opacity = vmodel.get("opacity");
nv.updateGLVolume();
}
function visible_changed() {
volume.visible = vmodel.get("visible");

Check failure on line 55 in js/volume.ts

View workflow job for this annotation

GitHub Actions / JavaScript / Typecheck

Property 'visible' does not exist on type 'NVImage'.
nv.updateGLVolume();
}
vmodel.on("change:colorbar_visible", colorbar_visible_changed);
vmodel.on("change:cal_min", cal_min_changed);
vmodel.on("change:cal_max", cal_max_changed);
vmodel.on("change:colormap", colormap_changed);
vmodel.on("change:opacity", opacity_changed);
vmodel.on("change:visible", visible_changed);
return [
volume,
() => {
Expand All @@ -63,14 +69,15 @@ function create_volume(
vmodel.off("change:cal_max", cal_max_changed);
vmodel.off("change:colormap", colormap_changed);
vmodel.off("change:opacity", opacity_changed);
vmodel.off("change:visible", visible_changed);
},
];
}

export async function render_volumes(
nv: niivue.Niivue,
model: Model,
cleanups: Map<string, () => void>,
disposer: lib.Disposer,
) {
const vmodels = await lib.gather_models<VolumeModel>(
model,
Expand All @@ -84,7 +91,7 @@ export async function render_volumes(
// except for the last one. We can just add the last volume.
const vmodel = vmodels[vmodels.length - 1];
const [volume, cleanup] = create_volume(nv, vmodel);
cleanups.set(volume.id, cleanup);
disposer.register(volume, cleanup);
nv.addVolume(volume);
return;
}
Expand All @@ -95,13 +102,12 @@ export async function render_volumes(
// and add the new ones.

// clear all volumes
for (const [_, cleanup] of cleanups) cleanup();
cleanups.clear();
disposer.disposeAll("image");

// create each volume and add one-by-one
for (const vmodel of vmodels) {
const [volume, cleanup] = create_volume(nv, vmodel);
cleanups.set(volume.id, cleanup);
disposer.register(volume, cleanup);
nv.addVolume(volume);
}
}
19 changes: 8 additions & 11 deletions js/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import type { Model } from "./types.ts";

import { render_meshes } from "./mesh.ts";
import { render_volumes } from "./volume.ts";
import { Disposer } from "./lib.ts";


export default {
async render({ model, el }: { model: Model; el: HTMLElement }) {
const disposer = new Disposer();
const canvas = document.createElement("canvas");
const container = document.createElement("div");
container.style.height = `${model.get("height")}px`;
Expand All @@ -15,13 +18,10 @@ export default {
const nv = new niivue.Niivue(model.get("_opts") ?? {});
nv.attachToCanvas(canvas);

const vcleanups = new Map<string, () => void>();
await render_volumes(nv, model, vcleanups);
model.on("change:_volumes", () => render_volumes(nv, model, vcleanups));

const mcleanups = new Map<string, () => void>();
await render_meshes(nv, model, mcleanups);
model.on("change:_meshes", () => render_meshes(nv, model, mcleanups));
await render_volumes(nv, model, disposer);
model.on("change:_volumes", () => render_volumes(nv, model, disposer));
await render_meshes(nv, model, disposer);
model.on("change:_meshes", () => render_meshes(nv, model, disposer));

// Any time we change the options, we need to update the nv object
// and redraw the scene.
Expand All @@ -36,10 +36,7 @@ export default {

// All the logic for cleaning up the event listeners and the nv object
return () => {
for (const [_, cleanup] of vcleanups) cleanup();
vcleanups.clear();
for (const [_, cleanup] of mcleanups) cleanup();
mcleanups.clear();
disposer.disposeAll();
model.off("change:_volumes");
model.off("change:_opts");
};
Expand Down
1 change: 1 addition & 0 deletions src/ipyniivue/_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class Volume(ipywidgets.Widget):
path = t.Union([t.Instance(pathlib.Path), t.Unicode()]).tag(
sync=True, to_json=file_serializer
)
visible = t.Bool(True).tag(sync=True)
opacity = t.Float(1.0).tag(sync=True)
colormap = t.Unicode("gray").tag(sync=True)
colorbar_visible = t.Bool(True).tag(sync=True)
Expand Down

0 comments on commit f5e0e29

Please sign in to comment.