Skip to content

RasterViewerWidget

RasterViewerWidget is an instance-scoped NiceGUI component backed by a framework-independent JavaScript canvas viewer. It supports uint16 and float32 data, multiple channels, T/Z navigation, Sliding-Z maximum projections, physical axes, typed rectangle and line ROIs, and non-interactive X/Y overlays.

The existing PlotlyRasterViewer remains a separate widget.

App (native) and web use the same JS/CSS under web/. Web browsers cache those static files; when chrome/CSS changes, bump RASTER_VIEWER_ASSETS_VERSION in web/raster_viewer_component.js (and any ?v= on touched ES module imports).

Chrome notes

Three chrome layers (names match Viewer options toggles where applicable):

  1. Viewer options — hamburger menu, pinned leftmost on the top toolbar. Toggles Axes / ROIs / Channel Toolbars / ROI Toolbar and Reset view.
  2. Top toolbar (rest) — channel layout radios (hidden for one channel), channel select (single mode), Sliding-Z, then a thin divider (when those neighbors are visible) and the ROI Toolbar strip (add/delete/edit/commit/cancel) in normal left-to-right flow.
  3. Channel Toolbars — per-pane header rows (channel index, enable, LUT, Set contrast) plus Copy view to clipboard; shown/hidden together via the Channel Toolbars option. Copy uses the browser Clipboard API on web, and on NiceGUI native/pywebview composes a PNG in JS then writes via nicewidgets.utils.clipboard.copy_png_bytes_to_native_clipboard (requires the optional desktop extras: pyperclipimg + Pillow).

Also:

  • ROI edit Commit / Cancel use muted green / red icon styling. While editing, idle ROI controls stay visible but disabled; Commit/Cancel appear on the same row (hidden when not editing).
  • IDLE: plain drag zooms (including over an ROI); shift+drag pans. A short click on an ROI selects it without changing the viewport. Double-click resets the view everywhere (including over an ROI).
  • CREATING/EDITING: press on the draft moves/resizes it; plain drag / shift+drag / double-click outside the draft still zoom / pan / reset. Double-click on the draft does not reset.
  • Set contrast opens a histogram dialog on document.body (so it stacks above sibling viewers). A Log checkbox (default on) switches histogram Y scaling between log and linear.
  • After a pointer click/press inside a viewer (ignored while typing in inputs): with two or more channels, keys 1 / 2 switch to one-channel view for channel 0 / 1, and 3 switches to composite; Enter runs Viewer options → Reset view (same as the menu action).

Embed

import numpy as np

from nicewidgets.raster_viewer_widget import RasterViewerWidget

image = np.arange(20 * 512 * 512, dtype=np.uint16).reshape(20, 512, 512)
viewer = RasterViewerWidget.from_array(
    image,
    dims=('Z', 'Y', 'X'),
    physical_units=(0.75, 0.4, 0.2),
    physical_units_labels=('um', 'um', 'um'),
)
viewer.classes('w-full h-[70vh]')

Source arrays remain in NumPy row-major coordinates. Browser display applies the widget's defined transpose and display Y-flip. Dataset descriptors use snake_case and exclude the channel axis from shape and dims.

Public API

Import public models and the widget from nicewidgets.raster_viewer_widget. Namespaced runtime APIs are available through viewer.channels, viewer.rois, and viewer.xy_plots.

nicewidgets.raster_viewer_widget.RasterViewerWidget

Bases: element

Display one instance-scoped JavaScript raster viewer in NiceGUI.

The widget owns either a Python RasterDataSource registration or an external descriptor URL. Public methods are the supported Python API; callers do not need to access JavaScript or component internals. Loading a new source replaces the dataset atomically in the browser, aborts old plane requests, clears its decoded-plane cache, and releases the previous Python registration.

ROI mutations initiated by Python are silent. Only genuine browser user interactions emit ROI callbacks. Instant add/delete and edit-start chrome either mutate locally (RoiHostMode.LOCAL) or emit request events (RoiHostMode.DELEGATED) until the host validates and calls silent rois APIs. Creation/editing geometry remains transactional until Python validates the proposal and calls rois.complete_commit.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
class RasterViewerWidget(ui.element, component="web/raster_viewer_component.js"):
    """Display one instance-scoped JavaScript raster viewer in NiceGUI.

    The widget owns either a Python ``RasterDataSource`` registration or an
    external descriptor URL. Public methods are the supported Python API;
    callers do not need to access JavaScript or component internals. Loading a
    new source replaces the dataset atomically in the browser, aborts old plane
    requests, clears its decoded-plane cache, and releases the previous Python
    registration.

    ROI mutations initiated by Python are silent. Only genuine browser user
    interactions emit ROI callbacks. Instant add/delete and edit-start chrome
    either mutate locally (``RoiHostMode.LOCAL``) or emit request events
    (``RoiHostMode.DELEGATED``) until the host validates and calls silent
    ``rois`` APIs. Creation/editing geometry remains transactional until Python
    validates the proposal and calls ``rois.complete_commit``.
    """

    def __init__(
        self,
        source: RasterDataSource | None = None,
        *,
        descriptor_url: str | None = None,
        config: RasterViewerConfig | None = None,
        on_channel_selected: Callable[[RasterChannelSelectedEvent], Any] | None = None,
        on_display_changed: Callable[[RasterDisplayChangeEvent], Any] | None = None,
    ) -> None:
        """Create one viewer from a Python source or external descriptor URL.

        Args:
            source: Python object implementing ``RasterDataSource``.
            descriptor_url: Existing browser-readable descriptor endpoint.
            config: Initial presentation configuration.
            on_channel_selected: Optional user-originated channel callback.
            on_display_changed: Optional user-originated display-state callback.

        Raises:
            ValueError: If both or neither source forms are supplied.
        """
        if source is not None and descriptor_url is not None:
            raise ValueError("provide at most one of source or descriptor_url")
        super().__init__()
        self.rois = RoiApi(self._run)
        self.xy_plots = XYPlotApi(self._run)
        self.channels = ChannelApi(self._run)
        self._source_token: str | None = None
        self._config = config or RasterViewerConfig()
        self.add_resource(WEB_ASSETS)
        if source is not None:
            ensure_routes_registered()
            self._source_token = REGISTRY.register(source)
            descriptor_url = self._url_for_token(self._source_token)
        self.client.on_delete(self._release_source)
        self._props.update(
            {
                "descriptor-url": descriptor_url,
                "initial-theme": self._config.theme.value,
                "initial-layout": self._config.layout.value,
                "initial-axes-visible": self._config.axes_visible,
                "initial-rois-visible": (
                    self._config.roi_chrome_enabled and self._config.rois_visible
                ),
                "initial-channel-toolbars-visible": self._config.channel_toolbars_visible,
                "initial-roi-toolbar-visible": (
                    self._config.roi_chrome_enabled and self._config.roi_toolbar_visible
                ),
                "roi-chrome-enabled": self._config.roi_chrome_enabled,
                "roi-host-mode": self._config.roi_host_mode.value,
                "invert-slice-wheel": self._config.invert_slice_wheel,
                "wheel-zoom-factor": self._config.wheel_zoom_factor,
                # Always advertise the bridge: JS prefers the browser Clipboard
                # API when available, and only emits raster-copy-view-request
                # when that API is missing (typical NiceGUI native / pywebview).
                "host-clipboard-bridge": True,
            }
        )
        self.classes("w-full h-full min-h-0")
        self.on_viewer_event(COPY_VIEW_REQUESTED_EVENT, self._on_copy_view_requested)
        if on_channel_selected is not None:
            self.on_channel_selected(on_channel_selected)
        if on_display_changed is not None:
            self.on_display_change(on_display_changed)

    @classmethod
    def from_descriptor_url(
        cls,
        descriptor_url: str,
        *,
        config: RasterViewerConfig | None = None,
        on_channel_selected: Callable[[RasterChannelSelectedEvent], Any] | None = None,
        on_display_changed: Callable[[RasterDisplayChangeEvent], Any] | None = None,
    ) -> RasterViewerWidget:
        """Create a widget backed by an existing descriptor service.

        Args:
            descriptor_url: Browser-readable descriptor endpoint.
            config: Initial presentation configuration.
            on_channel_selected: Optional user-originated channel callback.
            on_display_changed: Optional user-originated display-state callback.

        Returns:
            Newly created widget.
        """
        return cls(
            descriptor_url=descriptor_url,
            config=config,
            on_channel_selected=on_channel_selected,
            on_display_changed=on_display_changed,
        )

    @classmethod
    def from_channels(
        cls,
        channels: Sequence[npt.NDArray[Any]] | Mapping[str, npt.NDArray[Any]],
        *,
        dims: Sequence[str],
        physical_units: Sequence[float],
        physical_units_labels: Sequence[str],
        rois: Sequence[Roi | Mapping[str, object]] = (),
        source_id: str | None = None,
        label: str = "NumPy raster",
        default_luts: Sequence[str] | None = None,
        channel_displays: Sequence[RasterChannelDisplay] | None = None,
        config: RasterViewerConfig | None = None,
        on_channel_selected: Callable[[RasterChannelSelectedEvent], Any] | None = None,
        on_display_changed: Callable[[RasterDisplayChangeEvent], Any] | None = None,
    ) -> RasterViewerWidget:
        """Create a widget directly from separate NumPy channel arrays.

        Every channel must have the same shape and dtype. Per-channel ``dims``
        end in ``("Y", "X")`` and may contain leading ``T`` and ``Z`` axes; a
        channel axis is not included because each array already represents one channel. Source
        arrays remain Python-owned and planes are fetched lazily as raw binary.

        Args:
            channels: Ordered arrays or stable-channel-ID mapping.
            dims: Dimension names describing every channel array axis.
            physical_units: Positive sample spacing corresponding to ``dims``.
            physical_units_labels: Display unit labels corresponding to ``dims``.
            rois: Initial typed ROIs or external descriptor envelopes.
            source_id: Optional stable dataset identity.
            label: Human-readable dataset label.
            default_luts: Optional LUT name for every logical channel.
            channel_displays: Optional complete initial display state per channel.
            config: Initial presentation configuration.
            on_channel_selected: Optional user-originated channel callback.
            on_display_changed: Optional user-originated display-state callback.

        Returns:
            Mounted viewer backed by a registered ``NumPyRasterSource``.
        """
        source = NumPyRasterSource.from_channels(
            channels,
            dims=dims,
            physical_units=physical_units,
            physical_units_labels=physical_units_labels,
            rois=rois,
            source_id=source_id,
            label=label,
            default_luts=default_luts,
            channel_displays=channel_displays,
        )
        return cls(
            source=source,
            config=config,
            on_channel_selected=on_channel_selected,
            on_display_changed=on_display_changed,
        )

    @classmethod
    def from_array(
        cls,
        data: npt.NDArray[Any],
        *,
        dims: Sequence[str],
        physical_units: Sequence[float],
        physical_units_labels: Sequence[str],
        rois: Sequence[Roi | Mapping[str, object]] = (),
        source_id: str | None = None,
        label: str = "NumPy raster",
        channel_ids: Sequence[str] | None = None,
        default_luts: Sequence[str] | None = None,
        channel_displays: Sequence[RasterChannelDisplay] | None = None,
        config: RasterViewerConfig | None = None,
        on_channel_selected: Callable[[RasterChannelSelectedEvent], Any] | None = None,
        on_display_changed: Callable[[RasterDisplayChangeEvent], Any] | None = None,
    ) -> RasterViewerWidget:
        """Create a widget from one explicitly dimensioned NumPy array.

        Supported layouts end in Y/X and may contain C, T, and Z. A named ``C``
        axis is split into logical channel views and excluded from the browser
        header. The display applies transpose followed by bottom-origin flip-Y;
        callers keep data and ROI geometry in original NumPy coordinates.

        Args:
            data: Contiguous or strided uint16/float32 source array.
            dims: Unique dimension names describing every input axis.
            physical_units: Positive sample spacing corresponding to ``dims``.
            physical_units_labels: Display unit labels corresponding to ``dims``.
            rois: Initial typed ROIs or external descriptor envelopes.
            source_id: Optional stable dataset identity.
            label: Human-readable dataset label.
            channel_ids: Optional stable IDs matching the named C axis.
            default_luts: Optional LUT names matching logical channels.
            channel_displays: Optional complete initial display state per channel.
            config: Initial presentation configuration.
            on_channel_selected: Optional user-originated channel callback.
            on_display_changed: Optional user-originated display-state callback.

        Returns:
            Mounted viewer backed by a registered ``NumPyRasterSource``.
        """
        source = NumPyRasterSource.from_array(
            data,
            dims=dims,
            physical_units=physical_units,
            physical_units_labels=physical_units_labels,
            rois=rois,
            source_id=source_id,
            label=label,
            channel_ids=channel_ids,
            default_luts=default_luts,
            channel_displays=channel_displays,
        )
        return cls(
            source=source,
            config=config,
            on_channel_selected=on_channel_selected,
            on_display_changed=on_display_changed,
        )

    @staticmethod
    def _url_for_token(token: str) -> str:
        """Return the internal descriptor URL for an opaque source token."""
        return f"{ROUTE_PREFIX}/{token}/descriptor"

    async def load_source(self, source: RasterDataSource) -> str:
        """Replace the current Python source after loading it in the browser.

        Args:
            source: New protocol-compatible raster source.

        Returns:
            Loaded source identifier reported by JavaScript.
        """
        ensure_routes_registered()
        new_token = REGISTRY.register(source)
        try:
            result = await self.run_method(
                "loadDescriptorUrl",
                self._url_for_token(new_token),
                timeout=JAVASCRIPT_TIMEOUT_SECONDS,
            )
        except Exception:
            REGISTRY.unregister(new_token)
            raise
        previous_token = self._source_token
        self._source_token = new_token
        if previous_token is not None:
            REGISTRY.unregister(previous_token)
        return str(result)

    async def load_descriptor_url(self, descriptor_url: str) -> str:
        """Replace the dataset from an external descriptor endpoint.

        Args:
            descriptor_url: Browser-readable URL returning the exact supported
                versioned descriptor schema.

        Returns:
            Loaded dataset identifier reported by JavaScript.
        """
        result = await self.run_method(
            "loadDescriptorUrl", descriptor_url, timeout=JAVASCRIPT_TIMEOUT_SECONDS
        )
        if self._source_token is not None:
            REGISTRY.unregister(self._source_token)
            self._source_token = None
        return str(result)

    async def set_theme(self, theme: ViewerTheme | str) -> str:
        """Apply a viewer chrome theme.

        Args:
            theme: Supported enum or ``light``/``dark`` value.

        Returns:
            Applied normalized theme value.
        """
        value = theme.value if isinstance(theme, ViewerTheme) else theme
        return str(await self._run("setTheme", value))

    async def set_layout(self, layout: ViewerLayout | str) -> str:
        """Apply a channel-pane layout.

        Args:
            layout: Supported enum or layout value.

        Returns:
            Applied normalized layout value.
        """
        value = layout.value if isinstance(layout, ViewerLayout) else layout
        return str(await self._run("setLayout", value))

    async def set_axes_visible(self, visible: bool) -> bool:
        """Set axis visibility.

        Args:
            visible: Whether fixed axis gutters and labels are drawn.

        Returns:
            Applied visibility.
        """
        return bool(await self._run("setAxesVisible", visible))

    async def set_rois_visible(self, visible: bool) -> bool:
        """Set committed ROI-overlay visibility.

        Args:
            visible: Whether committed ROI overlays are drawn.

        Returns:
            Applied visibility; active ROI drafts cannot be hidden.
        """
        return bool(await self._run("setRoisVisible", visible))

    async def set_channel_toolbars_visible(self, visible: bool) -> bool:
        """Set complete pane-header toolbar visibility.

        Args:
            visible: Whether headers containing channel controls and Copy are shown.

        Returns:
            Applied visibility for all current and future panes.
        """
        return bool(await self._run("setChannelToolbarsVisible", visible))

    async def set_roi_toolbar_visible(self, visible: bool) -> bool:
        """Set top-toolbar ROI strip visibility (dropdown + CRUD controls).

        Args:
            visible: Whether the ROI chrome strip is shown.

        Returns:
            Applied visibility.
        """
        return bool(await self._run("setRoiToolbarVisible", visible))

    async def set_x_range(self, minimum: float, maximum: float) -> dict[str, float]:
        """Set the physical display-X range without changing the Y transform.

        Args:
            minimum: Requested lower bound in the header's display-X units.
            maximum: Requested upper bound in the header's display-X units.

        Returns:
            Applied, image-clamped minimum and maximum.
        """
        result = await self._run("setXRange", minimum, maximum)
        return result if isinstance(result, dict) else {}

    async def set_y_range(self, minimum: float, maximum: float) -> dict[str, float]:
        """Set the physical display-Y range without changing the X transform.

        Args:
            minimum: Requested lower bound in the header's display-Y units.
            maximum: Requested upper bound in the header's display-Y units.

        Returns:
            Applied, image-clamped minimum and maximum.
        """
        result = await self._run("setYRange", minimum, maximum)
        return result if isinstance(result, dict) else {}

    async def set_physical_range(
        self,
        x_minimum: float,
        x_maximum: float,
        y_minimum: float,
        y_maximum: float,
    ) -> dict[str, object]:
        """Set physical display X and Y ranges in one viewport update.

        Prefer this over sequential :meth:`set_x_range` / :meth:`set_y_range`
        when restoring a reconnect viewport so the browser paints once.

        Args:
            x_minimum: Requested lower bound in display-X units.
            x_maximum: Requested upper bound in display-X units.
            y_minimum: Requested lower bound in display-Y units.
            y_maximum: Requested upper bound in display-Y units.

        Returns:
            Applied clamped ``{"x": {...}, "y": {...}}`` mapping when available.
        """
        result = await self._run(
            "setPhysicalRange",
            x_minimum,
            x_maximum,
            y_minimum,
            y_maximum,
        )
        return result if isinstance(result, dict) else {}

    async def set_z_index(self, z_index: int) -> dict[str, int | None]:
        """Select a zero-based Z plane.

        When the active dataset has no Z axis this is a no-op and returns the
        current plane selection.

        Args:
            z_index: Requested index, clamped to the active Z extent.

        Returns:
            Complete applied T/Z and sliding-Z selection.
        """
        result = await self._run("setZIndex", z_index)
        return result if isinstance(result, dict) else {}

    async def set_t_index(self, t_index: int) -> dict[str, int | None]:
        """Select a zero-based T plane.

        When the active dataset has no T axis this is a no-op and returns the
        current plane selection.

        Args:
            t_index: Requested index, clamped to the active T extent.

        Returns:
            Complete applied T/Z and sliding-Z selection.
        """
        result = await self._run("setTIndex", t_index)
        return result if isinstance(result, dict) else {}

    async def set_physical_calibration(
        self,
        physical_units: Sequence[float],
        physical_units_labels: Sequence[str],
    ) -> dict[str, object]:
        """Update runtime calibration without reloading pixel planes.

        Args:
            physical_units: Positive sample spacing aligned with active ``dims``.
            physical_units_labels: Display labels aligned with active ``dims``.

        Returns:
            Applied physical units and labels reported by the browser.
        """
        result = await self._run(
            "setPhysicalCalibration", list(physical_units), list(physical_units_labels)
        )
        return result if isinstance(result, dict) else {}

    async def reset_view(self) -> bool:
        """Restore the full X/Y image extent and emit final viewport events.

        Returns:
            True after every current pane is reset.
        """
        return bool(await self._run("resetView"))

    async def reset_x_range(self) -> dict[str, float]:
        """Restore full X extent while preserving the current Y transform.

        Returns:
            Applied physical X minimum and maximum.
        """
        result = await self._run("resetXRange")
        return result if isinstance(result, dict) else {}

    async def clear_source(self) -> bool:
        """Clear browser dataset state and release the registered Python source.

        Returns:
            True after the viewer returns to its empty state.
        """
        result = bool(await self._run("clear"))
        self._release_source()
        return result

    async def set_sliding_z(
        self, enabled: bool, plus_minus_slices: int = 1
    ) -> dict[str, int | None]:
        """Configure a centered sliding-Z maximum projection.

        Args:
            enabled: Whether the backend projection is active.
            plus_minus_slices: Non-negative Z radius around the selected plane.

        Returns:
            Complete applied T/Z and sliding-Z selection.
        """
        result = await self._run("setSlidingZ", enabled, plus_minus_slices)
        return result if isinstance(result, dict) else {}

    async def _run(self, method: str, *arguments: object) -> Any:
        """Invoke one declared component method with the shared timeout."""
        return await self.run_method(
            method, *arguments, timeout=JAVASCRIPT_TIMEOUT_SECONDS
        )

    def on_viewer_event(
        self, event_name: str, handler: RasterEventHandler, **event_options: Any
    ) -> RasterViewerWidget:
        """Register an advanced callback for a raw raster custom event.

        Prefer the named typed ``on_*`` helpers for stable application code.

        Args:
            event_name: JavaScript custom-event name.
            handler: Callback receiving NiceGUI's generic event wrapper.
            **event_options: Additional options forwarded to ``ui.element.on``.

        Returns:
            This widget for fluent registration.
        """
        self.on(
            event_name,
            handler,
            js_handler="event => emit(event.detail)",
            **event_options,
        )
        return self

    def on_ready(self, handler: Callable[[RasterReadyEvent], Any]) -> RasterViewerWidget:
        """Register a viewer-ready callback."""
        return self._on_typed(READY_EVENT, RasterReadyEvent, handler)

    def on_error(self, handler: Callable[[RasterErrorEvent], Any]) -> RasterViewerWidget:
        """Register a viewer-error callback."""
        return self._on_typed(ERROR_EVENT, RasterErrorEvent, handler)

    def on_view_change(
        self, handler: Callable[[RasterViewChangeEvent], Any], **options: Any
    ) -> RasterViewerWidget:
        """Register a viewport-change callback."""
        return self._on_typed(VIEW_CHANGE_EVENT, RasterViewChangeEvent, handler, **options)

    def on_display_change(
        self, handler: Callable[[RasterDisplayChangeEvent], Any], **options: Any
    ) -> RasterViewerWidget:
        """Register a channel-display-change callback."""
        return self._on_typed(
            DISPLAY_CHANGE_EVENT, RasterDisplayChangeEvent, handler, **options
        )

    def on_channel_selected(
        self, handler: Callable[[RasterChannelSelectedEvent], Any]
    ) -> RasterViewerWidget:
        """Register a user-originated active-channel callback."""
        return self._on_typed(CHANNEL_SELECTED_EVENT, RasterChannelSelectedEvent, handler)

    def on_toolbar_action(
        self, handler: Callable[[RasterToolbarActionEvent], Any]
    ) -> RasterViewerWidget:
        """Register a viewer-toolbar callback."""
        return self._on_typed(TOOLBAR_ACTION_EVENT, RasterToolbarActionEvent, handler)

    def on_plane_change(
        self, handler: Callable[[RasterPlaneChangeEvent], Any]
    ) -> RasterViewerWidget:
        """Register a plane-selection callback."""
        return self._on_typed(PLANE_CHANGE_EVENT, RasterPlaneChangeEvent, handler)

    def on_performance(
        self, handler: Callable[[RasterPerformanceEvent], Any]
    ) -> RasterViewerWidget:
        """Register a browser performance-metric callback."""
        return self._on_typed(PERFORMANCE_EVENT, RasterPerformanceEvent, handler)

    def _on_typed(
        self,
        event_name: str,
        event_type: type[RasterEventT],
        handler: Callable[[RasterEventT], Any],
        **event_options: Any,
    ) -> RasterViewerWidget:
        """Convert a NiceGUI event before calling a typed public handler."""

        def typed_handler(event: events.GenericEventArguments) -> Any:
            """Translate and forward one NiceGUI custom event."""
            converted = event_type.from_nicegui(event)
            return handler(converted)

        return self.on_viewer_event(event_name, typed_handler, **event_options)

    def on_roi_selected(
        self, handler: Callable[[RasterRoiSelectedEvent], Any]
    ) -> RasterViewerWidget:
        """Register a user-originated ROI-selection callback."""
        return self._on_typed(ROI_SELECTED_EVENT, RasterRoiSelectedEvent, handler)

    def on_roi_add_requested(
        self, handler: Callable[[RasterRoiAddRequestedEvent], Any]
    ) -> RasterViewerWidget:
        """Register a user request to add an ROI (host chooses identity/geometry)."""
        return self._on_typed(
            ROI_ADD_REQUESTED_EVENT, RasterRoiAddRequestedEvent, handler
        )

    def on_roi_delete_requested(
        self, handler: Callable[[RasterRoiDeleteRequestedEvent], Any]
    ) -> RasterViewerWidget:
        """Register a user request to delete one ROI."""
        return self._on_typed(
            ROI_DELETE_REQUESTED_EVENT, RasterRoiDeleteRequestedEvent, handler
        )

    def on_roi_edit_requested(
        self, handler: Callable[[RasterRoiEditRequestedEvent], Any]
    ) -> RasterViewerWidget:
        """Register a user request to enter ROI edit mode."""
        return self._on_typed(
            ROI_EDIT_REQUESTED_EVENT, RasterRoiEditRequestedEvent, handler
        )

    def on_roi_edit_cancel_requested(
        self, handler: Callable[[RasterRoiEditCancelRequestedEvent], Any]
    ) -> RasterViewerWidget:
        """Register a user request to cancel an active ROI draft."""
        return self._on_typed(
            ROI_EDIT_CANCEL_REQUESTED_EVENT,
            RasterRoiEditCancelRequestedEvent,
            handler,
        )

    def on_roi_create_requested(
        self, handler: Callable[[RasterRoiCreateRequestedEvent], Any]
    ) -> RasterViewerWidget:
        """Register a user ROI-creation proposal callback."""
        return self._on_typed(
            ROI_CREATE_REQUESTED_EVENT, RasterRoiCreateRequestedEvent, handler
        )

    def on_roi_edit_committed(
        self, handler: Callable[[RasterRoiEditCommittedEvent], Any]
    ) -> RasterViewerWidget:
        """Register a user ROI-edit proposal callback."""
        return self._on_typed(
            ROI_EDIT_COMMITTED_EVENT, RasterRoiEditCommittedEvent, handler
        )

    def on_roi_state_change(
        self, handler: Callable[[RasterRoiStateChangeEvent], Any]
    ) -> RasterViewerWidget:
        """Register a ROI interaction-state callback."""
        return self._on_typed(ROI_STATE_CHANGE_EVENT, RasterRoiStateChangeEvent, handler)

    def delete(self) -> None:
        """Delete the component and unregister its Python source."""
        self._release_source()
        super().delete()

    def _handle_delete(self) -> None:
        """Release source state when NiceGUI removes this element indirectly."""
        self._release_source()
        super()._handle_delete()

    def _on_copy_view_requested(self, event: events.GenericEventArguments) -> None:
        """Write a JS-composed viewport PNG to the native OS clipboard.

        Used when the browser Clipboard API cannot write images (NiceGUI
        native / pywebview). Web sessions copy entirely in JavaScript.

        Args:
            event: NiceGUI custom-event wrapper from ``raster-copy-view-request``.

        Returns:
            None.
        """
        try:
            request = RasterCopyViewRequestedEvent.from_nicegui(event)
            png_bytes = png_bytes_from_data_url(request.png_data_url)
            copy_png_bytes_to_native_clipboard(png_bytes)
        except Exception as exc:
            logger.exception('Native raster copy-view clipboard write failed')
            ui.notify(f'Copy failed: {exc}', type='negative')

    def _release_source(self) -> None:
        """Idempotently unregister the current Python source."""
        if self._source_token is not None:
            REGISTRY.unregister(self._source_token)
            self._source_token = None

from_descriptor_url classmethod

from_descriptor_url(
    descriptor_url: str,
    *,
    config: RasterViewerConfig | None = None,
    on_channel_selected: Callable[
        [RasterChannelSelectedEvent], Any
    ]
    | None = None,
    on_display_changed: Callable[
        [RasterDisplayChangeEvent], Any
    ]
    | None = None,
) -> RasterViewerWidget

Create a widget backed by an existing descriptor service.

Parameters:

Name Type Description Default
descriptor_url str

Browser-readable descriptor endpoint.

required
config RasterViewerConfig | None

Initial presentation configuration.

None
on_channel_selected Callable[[RasterChannelSelectedEvent], Any] | None

Optional user-originated channel callback.

None
on_display_changed Callable[[RasterDisplayChangeEvent], Any] | None

Optional user-originated display-state callback.

None

Returns:

Type Description
RasterViewerWidget

Newly created widget.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
@classmethod
def from_descriptor_url(
    cls,
    descriptor_url: str,
    *,
    config: RasterViewerConfig | None = None,
    on_channel_selected: Callable[[RasterChannelSelectedEvent], Any] | None = None,
    on_display_changed: Callable[[RasterDisplayChangeEvent], Any] | None = None,
) -> RasterViewerWidget:
    """Create a widget backed by an existing descriptor service.

    Args:
        descriptor_url: Browser-readable descriptor endpoint.
        config: Initial presentation configuration.
        on_channel_selected: Optional user-originated channel callback.
        on_display_changed: Optional user-originated display-state callback.

    Returns:
        Newly created widget.
    """
    return cls(
        descriptor_url=descriptor_url,
        config=config,
        on_channel_selected=on_channel_selected,
        on_display_changed=on_display_changed,
    )

from_channels classmethod

from_channels(
    channels: Sequence[NDArray[Any]]
    | Mapping[str, NDArray[Any]],
    *,
    dims: Sequence[str],
    physical_units: Sequence[float],
    physical_units_labels: Sequence[str],
    rois: Sequence[Roi | Mapping[str, object]] = (),
    source_id: str | None = None,
    label: str = 'NumPy raster',
    default_luts: Sequence[str] | None = None,
    channel_displays: Sequence[RasterChannelDisplay]
    | None = None,
    config: RasterViewerConfig | None = None,
    on_channel_selected: Callable[
        [RasterChannelSelectedEvent], Any
    ]
    | None = None,
    on_display_changed: Callable[
        [RasterDisplayChangeEvent], Any
    ]
    | None = None,
) -> RasterViewerWidget

Create a widget directly from separate NumPy channel arrays.

Every channel must have the same shape and dtype. Per-channel dims end in ("Y", "X") and may contain leading T and Z axes; a channel axis is not included because each array already represents one channel. Source arrays remain Python-owned and planes are fetched lazily as raw binary.

Parameters:

Name Type Description Default
channels Sequence[NDArray[Any]] | Mapping[str, NDArray[Any]]

Ordered arrays or stable-channel-ID mapping.

required
dims Sequence[str]

Dimension names describing every channel array axis.

required
physical_units Sequence[float]

Positive sample spacing corresponding to dims.

required
physical_units_labels Sequence[str]

Display unit labels corresponding to dims.

required
rois Sequence[Roi | Mapping[str, object]]

Initial typed ROIs or external descriptor envelopes.

()
source_id str | None

Optional stable dataset identity.

None
label str

Human-readable dataset label.

'NumPy raster'
default_luts Sequence[str] | None

Optional LUT name for every logical channel.

None
channel_displays Sequence[RasterChannelDisplay] | None

Optional complete initial display state per channel.

None
config RasterViewerConfig | None

Initial presentation configuration.

None
on_channel_selected Callable[[RasterChannelSelectedEvent], Any] | None

Optional user-originated channel callback.

None
on_display_changed Callable[[RasterDisplayChangeEvent], Any] | None

Optional user-originated display-state callback.

None

Returns:

Type Description
RasterViewerWidget

Mounted viewer backed by a registered NumPyRasterSource.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
@classmethod
def from_channels(
    cls,
    channels: Sequence[npt.NDArray[Any]] | Mapping[str, npt.NDArray[Any]],
    *,
    dims: Sequence[str],
    physical_units: Sequence[float],
    physical_units_labels: Sequence[str],
    rois: Sequence[Roi | Mapping[str, object]] = (),
    source_id: str | None = None,
    label: str = "NumPy raster",
    default_luts: Sequence[str] | None = None,
    channel_displays: Sequence[RasterChannelDisplay] | None = None,
    config: RasterViewerConfig | None = None,
    on_channel_selected: Callable[[RasterChannelSelectedEvent], Any] | None = None,
    on_display_changed: Callable[[RasterDisplayChangeEvent], Any] | None = None,
) -> RasterViewerWidget:
    """Create a widget directly from separate NumPy channel arrays.

    Every channel must have the same shape and dtype. Per-channel ``dims``
    end in ``("Y", "X")`` and may contain leading ``T`` and ``Z`` axes; a
    channel axis is not included because each array already represents one channel. Source
    arrays remain Python-owned and planes are fetched lazily as raw binary.

    Args:
        channels: Ordered arrays or stable-channel-ID mapping.
        dims: Dimension names describing every channel array axis.
        physical_units: Positive sample spacing corresponding to ``dims``.
        physical_units_labels: Display unit labels corresponding to ``dims``.
        rois: Initial typed ROIs or external descriptor envelopes.
        source_id: Optional stable dataset identity.
        label: Human-readable dataset label.
        default_luts: Optional LUT name for every logical channel.
        channel_displays: Optional complete initial display state per channel.
        config: Initial presentation configuration.
        on_channel_selected: Optional user-originated channel callback.
        on_display_changed: Optional user-originated display-state callback.

    Returns:
        Mounted viewer backed by a registered ``NumPyRasterSource``.
    """
    source = NumPyRasterSource.from_channels(
        channels,
        dims=dims,
        physical_units=physical_units,
        physical_units_labels=physical_units_labels,
        rois=rois,
        source_id=source_id,
        label=label,
        default_luts=default_luts,
        channel_displays=channel_displays,
    )
    return cls(
        source=source,
        config=config,
        on_channel_selected=on_channel_selected,
        on_display_changed=on_display_changed,
    )

from_array classmethod

from_array(
    data: NDArray[Any],
    *,
    dims: Sequence[str],
    physical_units: Sequence[float],
    physical_units_labels: Sequence[str],
    rois: Sequence[Roi | Mapping[str, object]] = (),
    source_id: str | None = None,
    label: str = 'NumPy raster',
    channel_ids: Sequence[str] | None = None,
    default_luts: Sequence[str] | None = None,
    channel_displays: Sequence[RasterChannelDisplay]
    | None = None,
    config: RasterViewerConfig | None = None,
    on_channel_selected: Callable[
        [RasterChannelSelectedEvent], Any
    ]
    | None = None,
    on_display_changed: Callable[
        [RasterDisplayChangeEvent], Any
    ]
    | None = None,
) -> RasterViewerWidget

Create a widget from one explicitly dimensioned NumPy array.

Supported layouts end in Y/X and may contain C, T, and Z. A named C axis is split into logical channel views and excluded from the browser header. The display applies transpose followed by bottom-origin flip-Y; callers keep data and ROI geometry in original NumPy coordinates.

Parameters:

Name Type Description Default
data NDArray[Any]

Contiguous or strided uint16/float32 source array.

required
dims Sequence[str]

Unique dimension names describing every input axis.

required
physical_units Sequence[float]

Positive sample spacing corresponding to dims.

required
physical_units_labels Sequence[str]

Display unit labels corresponding to dims.

required
rois Sequence[Roi | Mapping[str, object]]

Initial typed ROIs or external descriptor envelopes.

()
source_id str | None

Optional stable dataset identity.

None
label str

Human-readable dataset label.

'NumPy raster'
channel_ids Sequence[str] | None

Optional stable IDs matching the named C axis.

None
default_luts Sequence[str] | None

Optional LUT names matching logical channels.

None
channel_displays Sequence[RasterChannelDisplay] | None

Optional complete initial display state per channel.

None
config RasterViewerConfig | None

Initial presentation configuration.

None
on_channel_selected Callable[[RasterChannelSelectedEvent], Any] | None

Optional user-originated channel callback.

None
on_display_changed Callable[[RasterDisplayChangeEvent], Any] | None

Optional user-originated display-state callback.

None

Returns:

Type Description
RasterViewerWidget

Mounted viewer backed by a registered NumPyRasterSource.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
@classmethod
def from_array(
    cls,
    data: npt.NDArray[Any],
    *,
    dims: Sequence[str],
    physical_units: Sequence[float],
    physical_units_labels: Sequence[str],
    rois: Sequence[Roi | Mapping[str, object]] = (),
    source_id: str | None = None,
    label: str = "NumPy raster",
    channel_ids: Sequence[str] | None = None,
    default_luts: Sequence[str] | None = None,
    channel_displays: Sequence[RasterChannelDisplay] | None = None,
    config: RasterViewerConfig | None = None,
    on_channel_selected: Callable[[RasterChannelSelectedEvent], Any] | None = None,
    on_display_changed: Callable[[RasterDisplayChangeEvent], Any] | None = None,
) -> RasterViewerWidget:
    """Create a widget from one explicitly dimensioned NumPy array.

    Supported layouts end in Y/X and may contain C, T, and Z. A named ``C``
    axis is split into logical channel views and excluded from the browser
    header. The display applies transpose followed by bottom-origin flip-Y;
    callers keep data and ROI geometry in original NumPy coordinates.

    Args:
        data: Contiguous or strided uint16/float32 source array.
        dims: Unique dimension names describing every input axis.
        physical_units: Positive sample spacing corresponding to ``dims``.
        physical_units_labels: Display unit labels corresponding to ``dims``.
        rois: Initial typed ROIs or external descriptor envelopes.
        source_id: Optional stable dataset identity.
        label: Human-readable dataset label.
        channel_ids: Optional stable IDs matching the named C axis.
        default_luts: Optional LUT names matching logical channels.
        channel_displays: Optional complete initial display state per channel.
        config: Initial presentation configuration.
        on_channel_selected: Optional user-originated channel callback.
        on_display_changed: Optional user-originated display-state callback.

    Returns:
        Mounted viewer backed by a registered ``NumPyRasterSource``.
    """
    source = NumPyRasterSource.from_array(
        data,
        dims=dims,
        physical_units=physical_units,
        physical_units_labels=physical_units_labels,
        rois=rois,
        source_id=source_id,
        label=label,
        channel_ids=channel_ids,
        default_luts=default_luts,
        channel_displays=channel_displays,
    )
    return cls(
        source=source,
        config=config,
        on_channel_selected=on_channel_selected,
        on_display_changed=on_display_changed,
    )

load_source async

load_source(source: RasterDataSource) -> str

Replace the current Python source after loading it in the browser.

Parameters:

Name Type Description Default
source RasterDataSource

New protocol-compatible raster source.

required

Returns:

Type Description
str

Loaded source identifier reported by JavaScript.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
async def load_source(self, source: RasterDataSource) -> str:
    """Replace the current Python source after loading it in the browser.

    Args:
        source: New protocol-compatible raster source.

    Returns:
        Loaded source identifier reported by JavaScript.
    """
    ensure_routes_registered()
    new_token = REGISTRY.register(source)
    try:
        result = await self.run_method(
            "loadDescriptorUrl",
            self._url_for_token(new_token),
            timeout=JAVASCRIPT_TIMEOUT_SECONDS,
        )
    except Exception:
        REGISTRY.unregister(new_token)
        raise
    previous_token = self._source_token
    self._source_token = new_token
    if previous_token is not None:
        REGISTRY.unregister(previous_token)
    return str(result)

load_descriptor_url async

load_descriptor_url(descriptor_url: str) -> str

Replace the dataset from an external descriptor endpoint.

Parameters:

Name Type Description Default
descriptor_url str

Browser-readable URL returning the exact supported versioned descriptor schema.

required

Returns:

Type Description
str

Loaded dataset identifier reported by JavaScript.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
async def load_descriptor_url(self, descriptor_url: str) -> str:
    """Replace the dataset from an external descriptor endpoint.

    Args:
        descriptor_url: Browser-readable URL returning the exact supported
            versioned descriptor schema.

    Returns:
        Loaded dataset identifier reported by JavaScript.
    """
    result = await self.run_method(
        "loadDescriptorUrl", descriptor_url, timeout=JAVASCRIPT_TIMEOUT_SECONDS
    )
    if self._source_token is not None:
        REGISTRY.unregister(self._source_token)
        self._source_token = None
    return str(result)

set_theme async

set_theme(theme: ViewerTheme | str) -> str

Apply a viewer chrome theme.

Parameters:

Name Type Description Default
theme ViewerTheme | str

Supported enum or light/dark value.

required

Returns:

Type Description
str

Applied normalized theme value.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
353
354
355
356
357
358
359
360
361
362
363
async def set_theme(self, theme: ViewerTheme | str) -> str:
    """Apply a viewer chrome theme.

    Args:
        theme: Supported enum or ``light``/``dark`` value.

    Returns:
        Applied normalized theme value.
    """
    value = theme.value if isinstance(theme, ViewerTheme) else theme
    return str(await self._run("setTheme", value))

set_layout async

set_layout(layout: ViewerLayout | str) -> str

Apply a channel-pane layout.

Parameters:

Name Type Description Default
layout ViewerLayout | str

Supported enum or layout value.

required

Returns:

Type Description
str

Applied normalized layout value.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
365
366
367
368
369
370
371
372
373
374
375
async def set_layout(self, layout: ViewerLayout | str) -> str:
    """Apply a channel-pane layout.

    Args:
        layout: Supported enum or layout value.

    Returns:
        Applied normalized layout value.
    """
    value = layout.value if isinstance(layout, ViewerLayout) else layout
    return str(await self._run("setLayout", value))

set_axes_visible async

set_axes_visible(visible: bool) -> bool

Set axis visibility.

Parameters:

Name Type Description Default
visible bool

Whether fixed axis gutters and labels are drawn.

required

Returns:

Type Description
bool

Applied visibility.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
377
378
379
380
381
382
383
384
385
386
async def set_axes_visible(self, visible: bool) -> bool:
    """Set axis visibility.

    Args:
        visible: Whether fixed axis gutters and labels are drawn.

    Returns:
        Applied visibility.
    """
    return bool(await self._run("setAxesVisible", visible))

set_rois_visible async

set_rois_visible(visible: bool) -> bool

Set committed ROI-overlay visibility.

Parameters:

Name Type Description Default
visible bool

Whether committed ROI overlays are drawn.

required

Returns:

Type Description
bool

Applied visibility; active ROI drafts cannot be hidden.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
388
389
390
391
392
393
394
395
396
397
async def set_rois_visible(self, visible: bool) -> bool:
    """Set committed ROI-overlay visibility.

    Args:
        visible: Whether committed ROI overlays are drawn.

    Returns:
        Applied visibility; active ROI drafts cannot be hidden.
    """
    return bool(await self._run("setRoisVisible", visible))

set_channel_toolbars_visible async

set_channel_toolbars_visible(visible: bool) -> bool

Set complete pane-header toolbar visibility.

Parameters:

Name Type Description Default
visible bool

Whether headers containing channel controls and Copy are shown.

required

Returns:

Type Description
bool

Applied visibility for all current and future panes.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
399
400
401
402
403
404
405
406
407
408
async def set_channel_toolbars_visible(self, visible: bool) -> bool:
    """Set complete pane-header toolbar visibility.

    Args:
        visible: Whether headers containing channel controls and Copy are shown.

    Returns:
        Applied visibility for all current and future panes.
    """
    return bool(await self._run("setChannelToolbarsVisible", visible))

set_roi_toolbar_visible async

set_roi_toolbar_visible(visible: bool) -> bool

Set top-toolbar ROI strip visibility (dropdown + CRUD controls).

Parameters:

Name Type Description Default
visible bool

Whether the ROI chrome strip is shown.

required

Returns:

Type Description
bool

Applied visibility.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
410
411
412
413
414
415
416
417
418
419
async def set_roi_toolbar_visible(self, visible: bool) -> bool:
    """Set top-toolbar ROI strip visibility (dropdown + CRUD controls).

    Args:
        visible: Whether the ROI chrome strip is shown.

    Returns:
        Applied visibility.
    """
    return bool(await self._run("setRoiToolbarVisible", visible))

set_x_range async

set_x_range(
    minimum: float, maximum: float
) -> dict[str, float]

Set the physical display-X range without changing the Y transform.

Parameters:

Name Type Description Default
minimum float

Requested lower bound in the header's display-X units.

required
maximum float

Requested upper bound in the header's display-X units.

required

Returns:

Type Description
dict[str, float]

Applied, image-clamped minimum and maximum.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
421
422
423
424
425
426
427
428
429
430
431
432
async def set_x_range(self, minimum: float, maximum: float) -> dict[str, float]:
    """Set the physical display-X range without changing the Y transform.

    Args:
        minimum: Requested lower bound in the header's display-X units.
        maximum: Requested upper bound in the header's display-X units.

    Returns:
        Applied, image-clamped minimum and maximum.
    """
    result = await self._run("setXRange", minimum, maximum)
    return result if isinstance(result, dict) else {}

set_y_range async

set_y_range(
    minimum: float, maximum: float
) -> dict[str, float]

Set the physical display-Y range without changing the X transform.

Parameters:

Name Type Description Default
minimum float

Requested lower bound in the header's display-Y units.

required
maximum float

Requested upper bound in the header's display-Y units.

required

Returns:

Type Description
dict[str, float]

Applied, image-clamped minimum and maximum.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
434
435
436
437
438
439
440
441
442
443
444
445
async def set_y_range(self, minimum: float, maximum: float) -> dict[str, float]:
    """Set the physical display-Y range without changing the X transform.

    Args:
        minimum: Requested lower bound in the header's display-Y units.
        maximum: Requested upper bound in the header's display-Y units.

    Returns:
        Applied, image-clamped minimum and maximum.
    """
    result = await self._run("setYRange", minimum, maximum)
    return result if isinstance(result, dict) else {}

set_physical_range async

set_physical_range(
    x_minimum: float,
    x_maximum: float,
    y_minimum: float,
    y_maximum: float,
) -> dict[str, object]

Set physical display X and Y ranges in one viewport update.

Prefer this over sequential :meth:set_x_range / :meth:set_y_range when restoring a reconnect viewport so the browser paints once.

Parameters:

Name Type Description Default
x_minimum float

Requested lower bound in display-X units.

required
x_maximum float

Requested upper bound in display-X units.

required
y_minimum float

Requested lower bound in display-Y units.

required
y_maximum float

Requested upper bound in display-Y units.

required

Returns:

Type Description
dict[str, object]

Applied clamped {"x": {...}, "y": {...}} mapping when available.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
async def set_physical_range(
    self,
    x_minimum: float,
    x_maximum: float,
    y_minimum: float,
    y_maximum: float,
) -> dict[str, object]:
    """Set physical display X and Y ranges in one viewport update.

    Prefer this over sequential :meth:`set_x_range` / :meth:`set_y_range`
    when restoring a reconnect viewport so the browser paints once.

    Args:
        x_minimum: Requested lower bound in display-X units.
        x_maximum: Requested upper bound in display-X units.
        y_minimum: Requested lower bound in display-Y units.
        y_maximum: Requested upper bound in display-Y units.

    Returns:
        Applied clamped ``{"x": {...}, "y": {...}}`` mapping when available.
    """
    result = await self._run(
        "setPhysicalRange",
        x_minimum,
        x_maximum,
        y_minimum,
        y_maximum,
    )
    return result if isinstance(result, dict) else {}

set_z_index async

set_z_index(z_index: int) -> dict[str, int | None]

Select a zero-based Z plane.

When the active dataset has no Z axis this is a no-op and returns the current plane selection.

Parameters:

Name Type Description Default
z_index int

Requested index, clamped to the active Z extent.

required

Returns:

Type Description
dict[str, int | None]

Complete applied T/Z and sliding-Z selection.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
477
478
479
480
481
482
483
484
485
486
487
488
489
490
async def set_z_index(self, z_index: int) -> dict[str, int | None]:
    """Select a zero-based Z plane.

    When the active dataset has no Z axis this is a no-op and returns the
    current plane selection.

    Args:
        z_index: Requested index, clamped to the active Z extent.

    Returns:
        Complete applied T/Z and sliding-Z selection.
    """
    result = await self._run("setZIndex", z_index)
    return result if isinstance(result, dict) else {}

set_t_index async

set_t_index(t_index: int) -> dict[str, int | None]

Select a zero-based T plane.

When the active dataset has no T axis this is a no-op and returns the current plane selection.

Parameters:

Name Type Description Default
t_index int

Requested index, clamped to the active T extent.

required

Returns:

Type Description
dict[str, int | None]

Complete applied T/Z and sliding-Z selection.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
492
493
494
495
496
497
498
499
500
501
502
503
504
505
async def set_t_index(self, t_index: int) -> dict[str, int | None]:
    """Select a zero-based T plane.

    When the active dataset has no T axis this is a no-op and returns the
    current plane selection.

    Args:
        t_index: Requested index, clamped to the active T extent.

    Returns:
        Complete applied T/Z and sliding-Z selection.
    """
    result = await self._run("setTIndex", t_index)
    return result if isinstance(result, dict) else {}

set_physical_calibration async

set_physical_calibration(
    physical_units: Sequence[float],
    physical_units_labels: Sequence[str],
) -> dict[str, object]

Update runtime calibration without reloading pixel planes.

Parameters:

Name Type Description Default
physical_units Sequence[float]

Positive sample spacing aligned with active dims.

required
physical_units_labels Sequence[str]

Display labels aligned with active dims.

required

Returns:

Type Description
dict[str, object]

Applied physical units and labels reported by the browser.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
async def set_physical_calibration(
    self,
    physical_units: Sequence[float],
    physical_units_labels: Sequence[str],
) -> dict[str, object]:
    """Update runtime calibration without reloading pixel planes.

    Args:
        physical_units: Positive sample spacing aligned with active ``dims``.
        physical_units_labels: Display labels aligned with active ``dims``.

    Returns:
        Applied physical units and labels reported by the browser.
    """
    result = await self._run(
        "setPhysicalCalibration", list(physical_units), list(physical_units_labels)
    )
    return result if isinstance(result, dict) else {}

reset_view async

reset_view() -> bool

Restore the full X/Y image extent and emit final viewport events.

Returns:

Type Description
bool

True after every current pane is reset.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
526
527
528
529
530
531
532
async def reset_view(self) -> bool:
    """Restore the full X/Y image extent and emit final viewport events.

    Returns:
        True after every current pane is reset.
    """
    return bool(await self._run("resetView"))

reset_x_range async

reset_x_range() -> dict[str, float]

Restore full X extent while preserving the current Y transform.

Returns:

Type Description
dict[str, float]

Applied physical X minimum and maximum.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
534
535
536
537
538
539
540
541
async def reset_x_range(self) -> dict[str, float]:
    """Restore full X extent while preserving the current Y transform.

    Returns:
        Applied physical X minimum and maximum.
    """
    result = await self._run("resetXRange")
    return result if isinstance(result, dict) else {}

clear_source async

clear_source() -> bool

Clear browser dataset state and release the registered Python source.

Returns:

Type Description
bool

True after the viewer returns to its empty state.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
543
544
545
546
547
548
549
550
551
async def clear_source(self) -> bool:
    """Clear browser dataset state and release the registered Python source.

    Returns:
        True after the viewer returns to its empty state.
    """
    result = bool(await self._run("clear"))
    self._release_source()
    return result

set_sliding_z async

set_sliding_z(
    enabled: bool, plus_minus_slices: int = 1
) -> dict[str, int | None]

Configure a centered sliding-Z maximum projection.

Parameters:

Name Type Description Default
enabled bool

Whether the backend projection is active.

required
plus_minus_slices int

Non-negative Z radius around the selected plane.

1

Returns:

Type Description
dict[str, int | None]

Complete applied T/Z and sliding-Z selection.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
553
554
555
556
557
558
559
560
561
562
563
564
565
566
async def set_sliding_z(
    self, enabled: bool, plus_minus_slices: int = 1
) -> dict[str, int | None]:
    """Configure a centered sliding-Z maximum projection.

    Args:
        enabled: Whether the backend projection is active.
        plus_minus_slices: Non-negative Z radius around the selected plane.

    Returns:
        Complete applied T/Z and sliding-Z selection.
    """
    result = await self._run("setSlidingZ", enabled, plus_minus_slices)
    return result if isinstance(result, dict) else {}

on_viewer_event

on_viewer_event(
    event_name: str,
    handler: RasterEventHandler,
    **event_options: Any,
) -> RasterViewerWidget

Register an advanced callback for a raw raster custom event.

Prefer the named typed on_* helpers for stable application code.

Parameters:

Name Type Description Default
event_name str

JavaScript custom-event name.

required
handler RasterEventHandler

Callback receiving NiceGUI's generic event wrapper.

required
**event_options Any

Additional options forwarded to ui.element.on.

{}

Returns:

Type Description
RasterViewerWidget

This widget for fluent registration.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
def on_viewer_event(
    self, event_name: str, handler: RasterEventHandler, **event_options: Any
) -> RasterViewerWidget:
    """Register an advanced callback for a raw raster custom event.

    Prefer the named typed ``on_*`` helpers for stable application code.

    Args:
        event_name: JavaScript custom-event name.
        handler: Callback receiving NiceGUI's generic event wrapper.
        **event_options: Additional options forwarded to ``ui.element.on``.

    Returns:
        This widget for fluent registration.
    """
    self.on(
        event_name,
        handler,
        js_handler="event => emit(event.detail)",
        **event_options,
    )
    return self

on_ready

on_ready(
    handler: Callable[[RasterReadyEvent], Any],
) -> RasterViewerWidget

Register a viewer-ready callback.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
597
598
599
def on_ready(self, handler: Callable[[RasterReadyEvent], Any]) -> RasterViewerWidget:
    """Register a viewer-ready callback."""
    return self._on_typed(READY_EVENT, RasterReadyEvent, handler)

on_error

on_error(
    handler: Callable[[RasterErrorEvent], Any],
) -> RasterViewerWidget

Register a viewer-error callback.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
601
602
603
def on_error(self, handler: Callable[[RasterErrorEvent], Any]) -> RasterViewerWidget:
    """Register a viewer-error callback."""
    return self._on_typed(ERROR_EVENT, RasterErrorEvent, handler)

on_view_change

on_view_change(
    handler: Callable[[RasterViewChangeEvent], Any],
    **options: Any,
) -> RasterViewerWidget

Register a viewport-change callback.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
605
606
607
608
609
def on_view_change(
    self, handler: Callable[[RasterViewChangeEvent], Any], **options: Any
) -> RasterViewerWidget:
    """Register a viewport-change callback."""
    return self._on_typed(VIEW_CHANGE_EVENT, RasterViewChangeEvent, handler, **options)

on_display_change

on_display_change(
    handler: Callable[[RasterDisplayChangeEvent], Any],
    **options: Any,
) -> RasterViewerWidget

Register a channel-display-change callback.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
611
612
613
614
615
616
617
def on_display_change(
    self, handler: Callable[[RasterDisplayChangeEvent], Any], **options: Any
) -> RasterViewerWidget:
    """Register a channel-display-change callback."""
    return self._on_typed(
        DISPLAY_CHANGE_EVENT, RasterDisplayChangeEvent, handler, **options
    )

on_channel_selected

on_channel_selected(
    handler: Callable[[RasterChannelSelectedEvent], Any],
) -> RasterViewerWidget

Register a user-originated active-channel callback.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
619
620
621
622
623
def on_channel_selected(
    self, handler: Callable[[RasterChannelSelectedEvent], Any]
) -> RasterViewerWidget:
    """Register a user-originated active-channel callback."""
    return self._on_typed(CHANNEL_SELECTED_EVENT, RasterChannelSelectedEvent, handler)

on_toolbar_action

on_toolbar_action(
    handler: Callable[[RasterToolbarActionEvent], Any],
) -> RasterViewerWidget

Register a viewer-toolbar callback.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
625
626
627
628
629
def on_toolbar_action(
    self, handler: Callable[[RasterToolbarActionEvent], Any]
) -> RasterViewerWidget:
    """Register a viewer-toolbar callback."""
    return self._on_typed(TOOLBAR_ACTION_EVENT, RasterToolbarActionEvent, handler)

on_plane_change

on_plane_change(
    handler: Callable[[RasterPlaneChangeEvent], Any],
) -> RasterViewerWidget

Register a plane-selection callback.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
631
632
633
634
635
def on_plane_change(
    self, handler: Callable[[RasterPlaneChangeEvent], Any]
) -> RasterViewerWidget:
    """Register a plane-selection callback."""
    return self._on_typed(PLANE_CHANGE_EVENT, RasterPlaneChangeEvent, handler)

on_performance

on_performance(
    handler: Callable[[RasterPerformanceEvent], Any],
) -> RasterViewerWidget

Register a browser performance-metric callback.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
637
638
639
640
641
def on_performance(
    self, handler: Callable[[RasterPerformanceEvent], Any]
) -> RasterViewerWidget:
    """Register a browser performance-metric callback."""
    return self._on_typed(PERFORMANCE_EVENT, RasterPerformanceEvent, handler)

on_roi_selected

on_roi_selected(
    handler: Callable[[RasterRoiSelectedEvent], Any],
) -> RasterViewerWidget

Register a user-originated ROI-selection callback.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
659
660
661
662
663
def on_roi_selected(
    self, handler: Callable[[RasterRoiSelectedEvent], Any]
) -> RasterViewerWidget:
    """Register a user-originated ROI-selection callback."""
    return self._on_typed(ROI_SELECTED_EVENT, RasterRoiSelectedEvent, handler)

on_roi_add_requested

on_roi_add_requested(
    handler: Callable[[RasterRoiAddRequestedEvent], Any],
) -> RasterViewerWidget

Register a user request to add an ROI (host chooses identity/geometry).

Source code in src/nicewidgets/raster_viewer_widget/widget.py
665
666
667
668
669
670
671
def on_roi_add_requested(
    self, handler: Callable[[RasterRoiAddRequestedEvent], Any]
) -> RasterViewerWidget:
    """Register a user request to add an ROI (host chooses identity/geometry)."""
    return self._on_typed(
        ROI_ADD_REQUESTED_EVENT, RasterRoiAddRequestedEvent, handler
    )

on_roi_delete_requested

on_roi_delete_requested(
    handler: Callable[[RasterRoiDeleteRequestedEvent], Any],
) -> RasterViewerWidget

Register a user request to delete one ROI.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
673
674
675
676
677
678
679
def on_roi_delete_requested(
    self, handler: Callable[[RasterRoiDeleteRequestedEvent], Any]
) -> RasterViewerWidget:
    """Register a user request to delete one ROI."""
    return self._on_typed(
        ROI_DELETE_REQUESTED_EVENT, RasterRoiDeleteRequestedEvent, handler
    )

on_roi_edit_requested

on_roi_edit_requested(
    handler: Callable[[RasterRoiEditRequestedEvent], Any],
) -> RasterViewerWidget

Register a user request to enter ROI edit mode.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
681
682
683
684
685
686
687
def on_roi_edit_requested(
    self, handler: Callable[[RasterRoiEditRequestedEvent], Any]
) -> RasterViewerWidget:
    """Register a user request to enter ROI edit mode."""
    return self._on_typed(
        ROI_EDIT_REQUESTED_EVENT, RasterRoiEditRequestedEvent, handler
    )

on_roi_edit_cancel_requested

on_roi_edit_cancel_requested(
    handler: Callable[
        [RasterRoiEditCancelRequestedEvent], Any
    ],
) -> RasterViewerWidget

Register a user request to cancel an active ROI draft.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
689
690
691
692
693
694
695
696
697
def on_roi_edit_cancel_requested(
    self, handler: Callable[[RasterRoiEditCancelRequestedEvent], Any]
) -> RasterViewerWidget:
    """Register a user request to cancel an active ROI draft."""
    return self._on_typed(
        ROI_EDIT_CANCEL_REQUESTED_EVENT,
        RasterRoiEditCancelRequestedEvent,
        handler,
    )

on_roi_create_requested

on_roi_create_requested(
    handler: Callable[[RasterRoiCreateRequestedEvent], Any],
) -> RasterViewerWidget

Register a user ROI-creation proposal callback.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
699
700
701
702
703
704
705
def on_roi_create_requested(
    self, handler: Callable[[RasterRoiCreateRequestedEvent], Any]
) -> RasterViewerWidget:
    """Register a user ROI-creation proposal callback."""
    return self._on_typed(
        ROI_CREATE_REQUESTED_EVENT, RasterRoiCreateRequestedEvent, handler
    )

on_roi_edit_committed

on_roi_edit_committed(
    handler: Callable[[RasterRoiEditCommittedEvent], Any],
) -> RasterViewerWidget

Register a user ROI-edit proposal callback.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
707
708
709
710
711
712
713
def on_roi_edit_committed(
    self, handler: Callable[[RasterRoiEditCommittedEvent], Any]
) -> RasterViewerWidget:
    """Register a user ROI-edit proposal callback."""
    return self._on_typed(
        ROI_EDIT_COMMITTED_EVENT, RasterRoiEditCommittedEvent, handler
    )

on_roi_state_change

on_roi_state_change(
    handler: Callable[[RasterRoiStateChangeEvent], Any],
) -> RasterViewerWidget

Register a ROI interaction-state callback.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
715
716
717
718
719
def on_roi_state_change(
    self, handler: Callable[[RasterRoiStateChangeEvent], Any]
) -> RasterViewerWidget:
    """Register a ROI interaction-state callback."""
    return self._on_typed(ROI_STATE_CHANGE_EVENT, RasterRoiStateChangeEvent, handler)

delete

delete() -> None

Delete the component and unregister its Python source.

Source code in src/nicewidgets/raster_viewer_widget/widget.py
721
722
723
724
def delete(self) -> None:
    """Delete the component and unregister its Python source."""
    self._release_source()
    super().delete()

Example

uv run python -m examples.raster_viewer_widget.main