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

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.
@@ -3223,6 +3223,9 @@ class VisualizerStore {
3223
3223
  // metadata on load (1 "Intensity" channel for grayscale; R/G/B for RGB). The
3224
3224
  // Channels & Histogram pane edits these and both backends recolor live.
3225
3225
  this.channelStates$ = new BehaviorSubject([]);
3226
+ /** Index of the channel selected in the Channels pane — the ONE band a
3227
+ * single-scalar 3D view (the napari Surface) follows for a multichannel image. */
3228
+ this.selectedChannel$ = new BehaviorSubject(0);
3226
3229
  // The derived defaults (full window, gamma 1, default tint) for the current
3227
3230
  // image, kept so "Reset" can restore a channel's window/gamma/colour.
3228
3231
  this.defaultChannelStates = [];
@@ -3372,6 +3375,7 @@ class VisualizerStore {
3372
3375
  this.defaultChannelStates = next.map((c) => ({ ...c }));
3373
3376
  if (next.length !== this.channelStates$.value.length) {
3374
3377
  this.channelStates$.next(next);
3378
+ this.selectedChannel$.next(0); // new channel structure → reset the selected band
3375
3379
  }
3376
3380
  }
3377
3381
  /** Channels for an image:
@@ -3410,6 +3414,13 @@ class VisualizerStore {
3410
3414
  currentChannelStates() {
3411
3415
  return this.channelStates$.value;
3412
3416
  }
3417
+ /** The channel selected in the pane (drives a single-scalar 3D Surface). */
3418
+ getSelectedChannel() { return this.selectedChannel$.asObservable(); }
3419
+ currentSelectedChannel() { return this.selectedChannel$.value; }
3420
+ setSelectedChannel(index) {
3421
+ if (this.selectedChannel$.value !== index)
3422
+ this.selectedChannel$.next(index);
3423
+ }
3413
3424
  /** Patch one channel by index (immutable update). */
3414
3425
  setChannelState(index, partial) {
3415
3426
  const next = this.channelStates$.value.map((c) => c.index === index ? { ...c, ...partial, index: c.index } : c);
@@ -6251,6 +6262,7 @@ class ChannelHistogramComponent {
6251
6262
  }
6252
6263
  selectChannel(ch) {
6253
6264
  this.selected = ch;
6265
+ this.api.setSelectedChannel(ch.index); // a single-scalar 3D Surface follows the selected band
6254
6266
  this.histRetries = 0;
6255
6267
  this.loadHistogram();
6256
6268
  }
@@ -18710,6 +18722,8 @@ class NapariVisualizerService extends BaseStoreVisualizer {
18710
18722
  this.surfaceLayer = null;
18711
18723
  /** Which band the surface samples (a channel index for multichannel, else the composite). */
18712
18724
  this.surfaceChannel = undefined;
18725
+ /** True when the mounted Surface follows one band of a multichannel image. */
18726
+ this.surfaceMultichannel = false;
18713
18727
  /** Pre-loaded per-slice luminance planes (already decimated to the surface grid), keyed by z,
18714
18728
  * so the stack slider rebuilds the surface instantly. Filled by {@link preloadSurfacePlanes}. */
18715
18729
  this.surfacePlanes = new Map();
@@ -19894,9 +19908,15 @@ class NapariVisualizerService extends BaseStoreVisualizer {
19894
19908
  */
19895
19909
  async mountSurface(viewer) {
19896
19910
  const desc = await this.ensureDescriptor();
19897
- const multichannel = !!desc?.multichannel && (desc?.channels ?? 1) > 1;
19898
- const states = this.store.currentChannelStates();
19899
- this.surfaceChannel = multichannel ? states.find((s) => s.visible)?.index ?? 0 : undefined;
19911
+ const info = this.loaded?.imageInfo;
19912
+ // Serverless multichannel (tiled:false + channelUrls) has no descriptor — detect
19913
+ // it from imageMeta so the Surface still follows one band.
19914
+ const simpleMc = this.simpleStack.isSimple(info) && !!info?.channelUrls?.length
19915
+ && (info?.imageMeta?.[0]?.channelCount ?? 1) > 1;
19916
+ const multichannel = simpleMc || (!!desc?.multichannel && (desc?.channels ?? 1) > 1);
19917
+ this.surfaceMultichannel = multichannel;
19918
+ // A height field is single-scalar → follow the pane-SELECTED channel.
19919
+ this.surfaceChannel = multichannel ? this.store.currentSelectedChannel() : undefined;
19900
19920
  this.surfaceMaxGrid = surfaceResolutionFor(this.resolutionScale).maxGrid;
19901
19921
  this.imageMode = 'grayscale';
19902
19922
  this.volumeMultichannel = false;
@@ -20121,13 +20141,25 @@ class NapariVisualizerService extends BaseStoreVisualizer {
20121
20141
  this.store.getReverseScale(),
20122
20142
  this.store.getInvert(),
20123
20143
  this.store.getChannelStates(),
20124
- ]).subscribe(([colormap, reverse, invert, channels]) => {
20144
+ this.store.getSelectedChannel(),
20145
+ ]).subscribe(([colormap, reverse, invert, channels, selected]) => {
20125
20146
  this.currentColormap = colormap ?? null;
20126
20147
  this.currentReverse = reverse;
20127
20148
  this.invertEnabled = invert;
20128
20149
  const layer = this.surfaceLayer;
20129
20150
  if (!layer || !this.viewer)
20130
20151
  return;
20152
+ // Selected channel changed (multichannel): re-fetch THAT band's planes and
20153
+ // rebuild the height-field for the current slice.
20154
+ if (this.surfaceMultichannel && selected !== this.surfaceChannel) {
20155
+ this.surfaceChannel = selected;
20156
+ void (async () => {
20157
+ await this.preloadSurfacePlanes(this.viewer);
20158
+ if (this.viewer)
20159
+ await this.buildSurface(this.viewer, this.loaded?.z ?? 0);
20160
+ })().catch((err) => console.error('[napari-js] surface channel switch failed:', err));
20161
+ return;
20162
+ }
20131
20163
  const st = this.surfaceState(channels);
20132
20164
  const win = [st?.min ?? 0, st?.max ?? 255];
20133
20165
  const windowChanged = !this.surfaceWindow || win[0] !== this.surfaceWindow[0] || win[1] !== this.surfaceWindow[1];
@@ -21282,6 +21314,7 @@ class RoutingVisualizerService {
21282
21314
  /** Export the underlying data (16-bit multi-band TIFF) via the active backend. */
21283
21315
  exportData() { this.renderer().exportData(); }
21284
21316
  getChannels$() { return this.store.getChannelStates(); }
21317
+ setSelectedChannel(index) { this.store.setSelectedChannel(index); }
21285
21318
  setChannelState(index, partial) {
21286
21319
  this.store.setChannelState(index, partial);
21287
21320
  }