diff --git a/src/components/SlideViewer.tsx b/src/components/SlideViewer.tsx index 0a7e0a4f..0433ddbd 100644 --- a/src/components/SlideViewer.tsx +++ b/src/components/SlideViewer.tsx @@ -2098,6 +2098,53 @@ class SlideViewer extends React.Component { }) } + /** + * Keep the side-panel segment switch in sync when the overlay's visibility + * is toggled from the in-viewport legend (dicom-microscopy-viewer already + * applied the change, so we only mirror it into component state). + */ + onSegmentVisibilityChanged = (event: CustomEventInit): void => { + const detail = event.detail as + | { segmentUID?: string; isVisible?: boolean } + | undefined + if (detail?.segmentUID == null || detail.isVisible == null) { + return + } + const { segmentUID, isVisible } = detail + this.setState((state) => { + const visibleSegmentUIDs = new Set(state.visibleSegmentUIDs) + if (isVisible) { + visibleSegmentUIDs.add(segmentUID) + } else { + visibleSegmentUIDs.delete(segmentUID) + } + return { visibleSegmentUIDs } + }) + } + + /** + * Keep the side-panel mapping switch in sync when the overlay's visibility + * is toggled from the in-viewport legend. + */ + onMappingVisibilityChanged = (event: CustomEventInit): void => { + const detail = event.detail as + | { mappingUID?: string; isVisible?: boolean } + | undefined + if (detail?.mappingUID == null || detail.isVisible == null) { + return + } + const { mappingUID, isVisible } = detail + this.setState((state) => { + const visibleMappingUIDs = new Set(state.visibleMappingUIDs) + if (isVisible) { + visibleMappingUIDs.add(mappingUID) + } else { + visibleMappingUIDs.delete(mappingUID) + } + return { visibleMappingUIDs } + }) + } + onLoadingStarted = (_event: CustomEventInit): void => { this.setState({ isLoading: true }) } @@ -2270,6 +2317,14 @@ class SlideViewer extends React.Component { 'dicommicroscopyviewer_frame_loading_ended', this.onFrameLoadingEnded, ) + document.body.removeEventListener( + 'dicommicroscopyviewer_segment_visibility_changed', + this.onSegmentVisibilityChanged, + ) + document.body.removeEventListener( + 'dicommicroscopyviewer_parameter_mapping_visibility_changed', + this.onMappingVisibilityChanged, + ) document.body.removeEventListener('keyup', this.onKeyUp) document.body.removeEventListener('keyup', this.onKeyDown) window.removeEventListener('resize', this.onWindowResize) @@ -2398,6 +2453,14 @@ class SlideViewer extends React.Component { 'dicommicroscopyviewer_frame_loading_error', this.onFrameLoadingError, ) + document.body.addEventListener( + 'dicommicroscopyviewer_segment_visibility_changed', + this.onSegmentVisibilityChanged, + ) + document.body.addEventListener( + 'dicommicroscopyviewer_parameter_mapping_visibility_changed', + this.onMappingVisibilityChanged, + ) document.body.addEventListener('keyup', this.onKeyUp) document.body.addEventListener('keydown', this.onKeyDown) window.addEventListener('beforeunload', this.componentCleanup) diff --git a/src/utils/distinctOverlayColormaps.ts b/src/utils/distinctOverlayColormaps.ts index 69867989..0627d861 100644 --- a/src/utils/distinctOverlayColormaps.ts +++ b/src/utils/distinctOverlayColormaps.ts @@ -3,21 +3,18 @@ import * as dmv from 'dicom-microscopy-viewer' import { getSegmentationType, getSegmentColor } from './segmentColors' -const COLORMAP_ORDER = [ - dmv.color.ColormapNames.VIRIDIS, - dmv.color.ColormapNames.MAGMA, - dmv.color.ColormapNames.INFERNO, - dmv.color.ColormapNames.HOT, - dmv.color.ColormapNames.BLUE_RED, - dmv.color.ColormapNames.GRAY, - dmv.color.ColormapNames.PHASE, - dmv.color.ColormapNames.PORTLAND, -] as const - -function buildPaletteForName( - name: (typeof COLORMAP_ORDER)[number], +/** + * Build a perceptually distinct, single-hue palette for the overlay at + * `index`. Multi-hue scientific color maps (viridis, magma, inferno, hot, …) + * all share a dark → bright-yellow ramp and are therefore hard to tell apart + * and to match against the legend; single-hue ramps give each overlay a + * clearly different, easily named color. + * See https://github.com/ImagingDataCommons/dicom-microscopy-viewer/issues/240. + */ +function buildDistinctPalette( + index: number, ): dmv.color.PaletteColorLookupTable { - const data = dmv.color.createColormap({ name, bins: 2 ** 8 }) + const data = dmv.color.createDistinctColormap({ index, bins: 2 ** 8 }) return dmv.color.buildPaletteColorLookupTable({ data, firstValueMapped: 0, @@ -56,9 +53,8 @@ export function applyDistinctFractionalSegmentPalettes( return } - const name = COLORMAP_ORDER[paletteIndex % COLORMAP_ORDER.length] + const table = buildDistinctPalette(paletteIndex) paletteIndex += 1 - const table = buildPaletteForName(name) const style = volumeViewer.getSegmentStyle(seg.uid) volumeViewer.setSegmentStyle(seg.uid, { opacity: style.opacity, @@ -79,8 +75,7 @@ export function applyDistinctParametricMapPalettes( } mappings.forEach((mapping, i) => { - const name = COLORMAP_ORDER[i % COLORMAP_ORDER.length] - const table = buildPaletteForName(name) + const table = buildDistinctPalette(i) const style = volumeViewer.getParameterMappingStyle(mapping.uid) volumeViewer.setParameterMappingStyle(mapping.uid, { opacity: style.opacity, diff --git a/types/dicom-microscopy-viewer/index.d.ts b/types/dicom-microscopy-viewer/index.d.ts index 47a29b64..e13032fd 100644 --- a/types/dicom-microscopy-viewer/index.d.ts +++ b/types/dicom-microscopy-viewer/index.d.ts @@ -803,6 +803,11 @@ declare module 'dicom-microscopy-viewer' { bins: number }): number[][] + export function createDistinctColormap (options: { + index: number + bins: number + }): number[][] + export interface PaletteColorLookupTableOptions { uid: string redDescriptor: number[]