@jax-data-science/sci-image-visualizer 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -166,7 +166,7 @@ const PLOT_TYPE_DESCRIPTORS = {
166
166
  // ── napari-js WebGPU (jit-ui#102). The 3D types take a runtime decimate factor. ──
167
167
  [PlotType.NAPARI_IMAGE]: { type: PlotType.NAPARI_IMAGE, label: 'Image (napari · WebGPU)', icon: 'pi pi-image', dimensions: '2d', source: 'image' },
168
168
  [PlotType.NAPARI_SCATTER]: { type: PlotType.NAPARI_SCATTER, label: 'Scatter 2D (napari · WebGPU)', icon: 'pi pi-chart-scatter', dimensions: '2d', source: 'regions' },
169
- [PlotType.NAPARI_SURFACE]: { type: PlotType.NAPARI_SURFACE, label: 'Surface (napari · WebGPU)', productionLabel: 'Surface', icon: 'assets/plotting/surface.svg', dimensions: '3d', source: 'image', requiresGrayscale: true },
169
+ [PlotType.NAPARI_SURFACE]: { type: PlotType.NAPARI_SURFACE, label: 'Surface (napari · WebGPU)', productionLabel: 'Surface', icon: 'assets/plotting/surface.svg', dimensions: '3d', source: 'image' }, // a height-field is z=intensity of one plane — works for any grayscale/RGB image (RGB→luminance), no stack needed
170
170
  [PlotType.NAPARI_SCATTER3D]: { type: PlotType.NAPARI_SCATTER3D, label: 'Scatter 3D (napari · WebGPU)', icon: 'assets/plotting/3d-coordinates.svg', dimensions: '3d', source: 'image', requiresStack: true, requiresGrayscale: true },
171
171
  [PlotType.NAPARI_VOLUME]: { type: PlotType.NAPARI_VOLUME, label: 'Volume (napari · WebGPU)', productionLabel: 'Volume', icon: 'assets/plotting/cube-3d.svg', dimensions: '3d', source: 'image', requiresStack: true, requiresGrayscale: true },
172
172
  [PlotType.NAPARI_ISOSURFACE]: { type: PlotType.NAPARI_ISOSURFACE, label: 'Isosurface (napari · WebGPU)', productionLabel: 'Isosurface', icon: 'assets/plotting/isosurface.svg', dimensions: '3d', source: 'image', requiresStack: true, requiresGrayscale: true },
@@ -6720,6 +6720,11 @@ class VisualizerComponent {
6720
6720
  const caps = this.plotService.capabilities;
6721
6721
  const isStack = !!this.imageInfo?.isStack;
6722
6722
  const isGrayscale = !!this.imageInfo?.isGrayscale;
6723
+ // A multichannel image's bands are each single-band (scalar), so the scalar
6724
+ // plot modes (Heatmap / Surface / Volume / Isosurface) apply per channel even
6725
+ // though the RGB composite isn't grayscale — don't gate them out.
6726
+ const m0 = this.imageInfo?.imageMeta?.[0];
6727
+ const isMultichannel = (m0?.channelCount ?? 1) > 1 && (m0?.rgbChannels ?? 1) < 3;
6723
6728
  this.plotTypeOptions = this.plotService.getPlotTypeDescriptors()
6724
6729
  .filter((d) => {
6725
6730
  // Outside test mode, only the curated set (those with a productionLabel)
@@ -6730,7 +6735,7 @@ class VisualizerComponent {
6730
6735
  return false;
6731
6736
  if (d.requiresStack && !isStack)
6732
6737
  return false;
6733
- if (d.requiresGrayscale && !isGrayscale)
6738
+ if (d.requiresGrayscale && !isGrayscale && !isMultichannel)
6734
6739
  return false;
6735
6740
  return true;
6736
6741
  })
@@ -18877,7 +18882,13 @@ class NapariVisualizerService extends BaseStoreVisualizer {
18877
18882
  // beyond that one file's own extent).
18878
18883
  const info = this.loaded?.imageInfo;
18879
18884
  if (this.simpleStack.isSimple(info)) {
18880
- const url = this.simpleStack.urlFor(info, z);
18885
+ // Serverless multichannel volume: fetch the requested channel's OWN plane
18886
+ // (channelUrls[z][channel]) so each band stays distinct — the client-side
18887
+ // analog of the server's per-channel /tile?channel=c. Else the z-anchor URL.
18888
+ const chUrls = info.channelUrls;
18889
+ const url = channel != null && chUrls?.[z]?.[channel] != null
18890
+ ? chUrls[z][channel]
18891
+ : this.simpleStack.urlFor(info, z);
18881
18892
  if (!url)
18882
18893
  throw new Error(`[napari-js] no URL for slice ${z}`);
18883
18894
  return this.simpleStack.fetchAsBitmap(url);
@@ -19487,8 +19498,13 @@ class NapariVisualizerService extends BaseStoreVisualizer {
19487
19498
  async mountVolume(viewer, info, plotType) {
19488
19499
  const token = this.loadToken; // bail before rendering if a Cancel / new plot bumps this
19489
19500
  const desc = await this.ensureDescriptor();
19490
- const channelCount = desc?.channels ?? 1;
19491
- const multichannel = !!desc?.multichannel && channelCount > 1;
19501
+ // Serverless multichannel (tiled:false + channelUrls): no tile descriptor, so
19502
+ // derive the channel count from imageMeta and assemble each band from its own
19503
+ // channelUrls[z][c] plane (fetchSlice does the per-channel routing).
19504
+ const simpleMc = this.simpleStack.isSimple(info) && !!info?.channelUrls?.length
19505
+ && (info?.imageMeta?.[0]?.channelCount ?? 1) > 1;
19506
+ const channelCount = simpleMc ? info.imageMeta[0].channelCount : (desc?.channels ?? 1);
19507
+ const multichannel = simpleMc || (!!desc?.multichannel && channelCount > 1);
19492
19508
  const res = volumeResolutionFor(this.resolutionScale);
19493
19509
  const rendering = isNapariIsosurface(plotType) ? 'iso' : 'mip';
19494
19510
  const states = this.store.currentChannelStates();