Skip to content

Commit 6f03c60

Browse files
Add support for nv.draw_bitmap
1 parent 27fb09c commit 6f03c60

File tree

6 files changed

+90
-9
lines changed

6 files changed

+90
-9
lines changed

js/lib.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export function handleBufferMsg(
4747
targetObject: any,
4848
payload: TypedBufferPayload,
4949
buffers: DataView[],
50-
callback: () => void,
50+
callback: (data: TypedBufferPayload) => void,
5151
): boolean {
5252
const { type, data } = payload;
5353

@@ -61,7 +61,7 @@ export function handleBufferMsg(
6161

6262
targetObject[attrName] = typedArray;
6363

64-
callback();
64+
callback(payload);
6565

6666
return true;
6767
}
@@ -91,7 +91,7 @@ export function handleBufferMsg(
9191

9292
applyDifferencesToTypedArray(existingArray, indicesArray, valuesArray);
9393

94-
callback();
94+
callback(payload);
9595

9696
return true;
9797
}

js/mesh.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,10 @@ function setup_mesh_property_listeners(
201201
buffers: DataView[],
202202
) {
203203
const handled = lib.handleBufferMsg(mesh, payload, buffers, () => {
204-
mesh.updateMesh(nv.gl);
205-
nv.updateGLVolume();
204+
if (nv._gl) {
205+
mesh.updateMesh(nv.gl);
206+
nv.updateGLVolume();
207+
}
206208
});
207209
if (handled) {
208210
return;

js/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ export type Model = AnyModel<{
183183
overlay_alpha_shader: number;
184184

185185
_volume_object_3d_data: NiivueObject3D; // only updated via frontend (1-way comm)
186+
187+
draw_bitmap: DataView | null;
186188
}>;
187189

188190
// Custom message datas

js/volume.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,11 @@ function setup_volume_property_listeners(
146146
volume,
147147
payload as TypedBufferPayload,
148148
buffers,
149-
() => nv.updateGLVolume(),
149+
() => {
150+
if (nv._gl) {
151+
nv.updateGLVolume();
152+
}
153+
},
150154
);
151155
if (handled) {
152156
return;

js/widget.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,41 @@ import type {
1313
Model,
1414
NiivueObject3D,
1515
Scene,
16+
TypedBufferPayload,
1617
VolumeModel,
1718
} from "./types.ts";
1819

1920
let nv: niivue.Niivue;
2021
let syncInterval: number | undefined;
2122

23+
async function sendDrawBitmap(nv: niivue.Niivue, model: Model) {
24+
const thisModelId = model.get("this_model_id");
25+
if (!thisModelId) {
26+
return;
27+
}
28+
let thisAnyModel: AnyModel;
29+
try {
30+
thisAnyModel = (await model.widget_manager.get_model(
31+
thisModelId,
32+
)) as AnyModel;
33+
} catch (err) {
34+
return;
35+
}
36+
37+
if (nv.drawBitmap) {
38+
const dataType = lib.getArrayType(nv.drawBitmap);
39+
lib.sendChunkedData(
40+
thisAnyModel,
41+
"draw_bitmap",
42+
nv.drawBitmap.buffer as ArrayBuffer,
43+
dataType,
44+
);
45+
} else {
46+
model.set("draw_bitmap", null);
47+
model.save_changes();
48+
}
49+
}
50+
2251
// Attach model event handlers
2352
function attachModelEventHandlers(
2453
nv: niivue.Niivue,
@@ -129,7 +158,24 @@ function attachModelEventHandlers(
129158
// Handle any message directions from the nv object.
130159
model.on(
131160
"msg:custom",
132-
async (payload: CustomMessagePayload, buffers: DataView[]) => {
161+
async (
162+
payload: TypedBufferPayload | CustomMessagePayload,
163+
buffers: DataView[],
164+
) => {
165+
const handled = lib.handleBufferMsg(
166+
nv,
167+
payload as TypedBufferPayload,
168+
buffers,
169+
(pyData) => {
170+
if (pyData.data.attr === "drawBitmap") {
171+
nv.refreshDrawing();
172+
}
173+
},
174+
);
175+
if (handled) {
176+
return;
177+
}
178+
133179
const { type, data } = payload;
134180
switch (type) {
135181
case "save_document": {
@@ -203,15 +249,18 @@ function attachModelEventHandlers(
203249
case "set_drawing_enabled": {
204250
const [drawingEnabled] = data;
205251
nv.setDrawingEnabled(drawingEnabled);
252+
await sendDrawBitmap(nv, model);
206253
break;
207254
}
208255
case "draw_otsu": {
209256
const [levels] = data;
210257
nv.drawOtsu(levels);
258+
await sendDrawBitmap(nv, model);
211259
break;
212260
}
213261
case "draw_grow_cut": {
214262
nv.drawGrowCut();
263+
await sendDrawBitmap(nv, model);
215264
break;
216265
}
217266
case "move_crosshair_in_vox": {
@@ -226,10 +275,12 @@ function attachModelEventHandlers(
226275
}
227276
case "draw_undo": {
228277
nv.drawUndo();
278+
await sendDrawBitmap(nv, model);
229279
break;
230280
}
231281
case "close_drawing": {
232282
nv.closeDrawing();
283+
await sendDrawBitmap(nv, model);
233284
break;
234285
}
235286
case "load_drawing_from_url": {
@@ -255,6 +306,9 @@ function attachModelEventHandlers(
255306
} else {
256307
nv.loadDrawingFromUrl(url, isBinarize);
257308
}
309+
310+
await sendDrawBitmap(nv, model);
311+
258312
break;
259313
}
260314
case "load_document_from_url": {
@@ -279,6 +333,9 @@ function attachModelEventHandlers(
279333
} catch (err) {
280334
console.error(`loadDocument() failed to load: ${err}`);
281335
}
336+
337+
await sendDrawBitmap(nv, model);
338+
282339
break;
283340
}
284341
}

src/ipyniivue/widget.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ class BaseAnyWidget(anywidget.AnyWidget):
6969
_data_handlers: typing.ClassVar[dict] = {}
7070
_event_handlers: typing.ClassVar[dict] = {}
7171

72+
_binary_trait_to_js_names: typing.ClassVar[dict] = {}
73+
7274
def __init__(self, *args, **kwargs):
7375
super().__init__(*args, **kwargs)
7476
self._setup_binary_change_handlers()
@@ -80,7 +82,8 @@ def set_state(self, state):
8082

8183
for attr_name, attr_value in state.items():
8284
if attr_name.startswith("chunk_"):
83-
_, data_property, chunk_index = attr_name.split("_")
85+
base, chunk_index = attr_name.rsplit("_", 1)
86+
data_property = base[6:]
8487
chunk_index = int(chunk_index)
8588
chunk_info = attr_value
8689
chunk_index_received = chunk_info["chunk_index"]
@@ -122,8 +125,12 @@ def _setup_binary_change_handlers(self):
122125
def _get_binary_traits(self):
123126
return []
124127

128+
def _get_js_name(self, trait_name):
129+
"""Get the JavaScript attribute name for a trait."""
130+
return self._binary_trait_to_js_names.get(trait_name, trait_name)
131+
125132
def _handle_binary_trait_change(self, change):
126-
trait_name = change["name"]
133+
trait_name = self._get_js_name(change["name"])
127134
old_value = change["old"]
128135
new_value = change["new"]
129136
if old_value is not None:
@@ -802,6 +809,8 @@ class NiiVue(BaseAnyWidget):
802809

803810
_esm = pathlib.Path(__file__).parent / "static" / "widget.js"
804811

812+
_binary_trait_to_js_names: typing.ClassVar[dict] = {"draw_bitmap": "drawBitmap"}
813+
805814
height = t.Int().tag(sync=True)
806815
opts = t.Instance(ConfigOptions).tag(
807816
sync=True, to_json=serialize_options, from_json=deserialize_options
@@ -847,6 +856,10 @@ class NiiVue(BaseAnyWidget):
847856
sync=False
848857
)
849858

859+
draw_bitmap = t.Instance(np.ndarray, allow_none=True).tag(
860+
sync=True, to_json=serialize_ndarray
861+
)
862+
850863
@t.validate("other_nv")
851864
def _validate_other_nv(self, proposal):
852865
value = proposal["value"]
@@ -904,6 +917,9 @@ def __init__(self, height: int = 300, **options): # noqa: D417
904917
self._event_handlers = {}
905918
self.on_msg(self._handle_custom_msg)
906919

920+
def _get_binary_traits(self):
921+
return ["draw_bitmap"]
922+
907923
def set_state(self, state):
908924
"""Override set_state to silence notifications for certain updates."""
909925
if "scene" in state:

0 commit comments

Comments
 (0)