* feat: Update to use pnpm (#6031) [simulated squash-merge] * feat: load DICOM SEG images via imageLoader * feat: load DICOM SEG images via imageLoader * PR review fixes * Test fragment of compressed multiframes * fix: Removed dependencies incorrectly * Update frame as a targetted change to avoid stripping remaining params * lock * Update test to agree with the missing representation branch * test(contour): contour color change coverage (#6042) * test(contour): contour interactions delete segment (#6069) --------- Co-authored-by: Bill Wallace <wayfarer3130@gmail.com> * chore(version): Update package versions to 3.13.0-beta.98 [skip ci] * feat(testing): OHIF Test Agent Skills (#5993) * chore(version): Update package versions to 3.13.0-beta.99 [skip ci] * test(contour): Add the ContourSegmentToggleLock.spec.ts test file to test contour locking (#6072) * chore(version): Update package versions to 3.13.0-beta.100 [skip ci] * chore(testing): Flock lock for playwright tests; Pin node version for OHIF; add more workers (#6099) * update Cypress apt deps for Ubuntu Noble (drop libgconf-2-4, libasound2→libasound2t64) * chore(version): Update package versions to 3.13.0-beta.101 [skip ci] * Debug fixes * perf(seg): pass explicit frame decode concurrency (16) to SEG loader Pass an explicit concurrency value (SEG_FRAME_DECODE_CONCURRENCY = 16) into createFromDicomSegImageId rather than relying on the adapter default, so the SEG frame fetch/decode parallelism is set at the call site and is ready to become configurable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(behaviours): add behaviours section + multiframe Part 10 prefetch proposal Start a "Behaviours" docs section for documenting how the system and UI behave end-to-end (observed behaviours, design proposals, and failure modes), with an index README and a Docusaurus category. First entry is the proposal for loading a multiframe SEG as a single Part 10 instance: prefetch the whole instance (gated by loadMultiframeAsPart10RaceTimeMs), parse it with dcmjs (handling multipart/related vs raw DICOM), and register the per-frame compressed pixels into the Cornerstone3D core image cache (the single uniform frame registry) so the per-frame load path is served locally while the standard decode path is unchanged. Best-effort: falls back to per-frame fetches on any failure. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Pre-cache the image data using a full part10 file for performance * Add customizations to specify type of segmentation save * Add clearcache of the cacheData * Bump @cornerstonejs/* pins 5.1.3 -> 5.4.10 to match libs/@cornerstonejs base libs/@cornerstonejs (fix/use-imageLoader-for-seg) is based on the released cs3d 5.4.10 (merge-base with origin/main is the 5.4.10 version bump), so pin OHIF to that release. The local branch changes still reach the app via the cs3d:link symlinks; these pins keep the lockfile/npm fallback aligned. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix seg OOM * fix: file meta garbage element, bogus NumberOfFrames, unguarded source-map-loader - dicomWriter: drop the naturalized meta.TransferSyntaxUID assignment that dcmjs wrote as a garbage (0000,0000) element into every saved file's meta group (download / clipboard / local wadouri blob / local store); keep the hex 00020010 assignment, which is required for string-form _meta fallbacks. - registerNaturalizedDatasetForLocalWadouri: keep the computed frame count local so single-frame IODs (SR, RTSTRUCT) no longer gain a bogus NumberOfFrames element in their serialized form. - rsbuild.config: resolve source-map-loader opportunistically (it is not a project dependency; the rule only serves the gitignored cs3d-link workflow) so fresh clones no longer crash at config load. - Regression tests for both writer fixes (mutation-checked). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * PR review comment fixes * Update versions --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: diattamo <mmddiatta@gmail.com> Co-authored-by: ohif-bot <danny.ri.brown+ohif-bot@gmail.com> Co-authored-by: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com>
312 lines
11 KiB
TypeScript
312 lines
11 KiB
TypeScript
import React from 'react';
|
|
import * as cornerstone from '@cornerstonejs/core';
|
|
import * as cornerstoneTools from '@cornerstonejs/tools';
|
|
import {
|
|
Enums as cs3DEnums,
|
|
imageLoadPoolManager,
|
|
imageRetrievalPoolManager,
|
|
} from '@cornerstonejs/core';
|
|
import { Enums as cs3DToolsEnums } from '@cornerstonejs/tools';
|
|
import { utilities as csMetadataUtilities } from '@cornerstonejs/metadata';
|
|
import { Types } from '@ohif/core';
|
|
import Enums from './enums';
|
|
|
|
import init from './init';
|
|
import getCustomizationModule from './getCustomizationModule';
|
|
import getCommandsModule from './commandsModule';
|
|
import getHangingProtocolModule from './getHangingProtocolModule';
|
|
import getToolbarModule from './getToolbarModule';
|
|
import ToolGroupService from './services/ToolGroupService';
|
|
import SyncGroupService from './services/SyncGroupService';
|
|
import SegmentationService from './services/SegmentationService';
|
|
import CornerstoneCacheService from './services/CornerstoneCacheService';
|
|
import CornerstoneViewportService from './services/ViewportService/CornerstoneViewportService';
|
|
import ColorbarService from './services/ColorbarService';
|
|
import ViewedDataService from './services/ViewedDataService';
|
|
import * as CornerstoneExtensionTypes from './types';
|
|
|
|
import { toolNames } from './initCornerstoneTools';
|
|
import { getEnabledElement, reset as enabledElementReset, setEnabledElement } from './state';
|
|
import dicomLoaderService from './utils/dicomLoaderService';
|
|
import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement';
|
|
|
|
import { id } from './id';
|
|
import { measurementMappingUtils } from './utils/measurementServiceMappings';
|
|
import PlanarFreehandROI from './utils/measurementServiceMappings/PlanarFreehandROI';
|
|
import RectangleROI from './utils/measurementServiceMappings/RectangleROI';
|
|
import type { PublicViewportOptions } from './services/ViewportService/Viewport';
|
|
import ImageOverlayViewerTool from './tools/ImageOverlayViewerTool';
|
|
import getSOPInstanceAttributes from './utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
|
|
import { findNearbyToolData } from './utils/findNearbyToolData';
|
|
import { createFrameViewSynchronizer } from './synchronizers/frameViewSynchronizer';
|
|
import { getSopClassHandlerModule } from './getSopClassHandlerModule';
|
|
import { getDynamicVolumeInfo } from '@cornerstonejs/core/utilities';
|
|
import {
|
|
useLutPresentationStore,
|
|
usePositionPresentationStore,
|
|
useSegmentationPresentationStore,
|
|
useSynchronizersStore,
|
|
useSelectedSegmentationsForViewportStore,
|
|
} from './stores';
|
|
import { useToggleOneUpViewportGridStore } from '@ohif/extension-default';
|
|
import { useActiveViewportSegmentationRepresentations } from './hooks/useActiveViewportSegmentationRepresentations';
|
|
import { useMeasurements } from './hooks/useMeasurements';
|
|
import getPanelModule from './getPanelModule';
|
|
import PanelSegmentation from './panels/PanelSegmentation';
|
|
import PanelMeasurement from './panels/PanelMeasurement';
|
|
import { useSegmentations } from './hooks/useSegmentations';
|
|
import { StudySummaryFromMetadata } from './components/StudySummaryFromMetadata';
|
|
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
|
|
import utils from './utils';
|
|
import { useMeasurementTracking } from './hooks/useMeasurementTracking';
|
|
import { setUpSegmentationEventHandlers } from './utils/setUpSegmentationEventHandlers';
|
|
import { setUpAnnotationEventHandlers } from './utils/setUpAnnotationEventHandlers';
|
|
import update from 'immutability-helper';
|
|
export * from './components';
|
|
|
|
const { imageRetrieveMetadataProvider } = cornerstone.utilities;
|
|
|
|
const Component = React.lazy(() => {
|
|
return import(/* webpackPrefetch: true */ './Viewport/OHIFCornerstoneViewport');
|
|
});
|
|
|
|
const OHIFCornerstoneViewport = props => {
|
|
return (
|
|
<React.Suspense fallback={<div>Loading...</div>}>
|
|
<Component {...props} />
|
|
</React.Suspense>
|
|
);
|
|
};
|
|
|
|
const DEFAULT_STACK_RETRIEVE_OPTIONS = {
|
|
retrieveOptions: {
|
|
single: {
|
|
streaming: true,
|
|
decodeLevel: 1,
|
|
},
|
|
},
|
|
};
|
|
|
|
/** Normalize to immutability-helper spec: plain object → $merge, otherwise use as-is. */
|
|
const toUpdateSpec = (obj: object) =>
|
|
obj != null && typeof obj === 'object' && Object.keys(obj).some(k => k.startsWith('$'))
|
|
? obj
|
|
: { $merge: (obj ?? {}) as object };
|
|
|
|
const unsubscriptions = [];
|
|
/**
|
|
*
|
|
*/
|
|
const cornerstoneExtension: Types.Extensions.Extension = {
|
|
/**
|
|
* Only required property. Should be a unique value across all extensions.
|
|
*/
|
|
id,
|
|
|
|
onModeEnter: ({
|
|
servicesManager,
|
|
commandsManager,
|
|
extensionManager,
|
|
}: withAppTypes): void => {
|
|
const { cornerstoneViewportService, toolbarService, segmentationService } =
|
|
servicesManager.services;
|
|
|
|
const { unsubscriptions: segmentationUnsubscriptions } = setUpSegmentationEventHandlers({
|
|
servicesManager,
|
|
commandsManager,
|
|
});
|
|
unsubscriptions.push(...segmentationUnsubscriptions);
|
|
|
|
const annotationUnsubscriptions = setUpAnnotationEventHandlers();
|
|
unsubscriptions.push(...annotationUnsubscriptions);
|
|
|
|
toolbarService.registerEventForToolbarUpdate(cornerstoneViewportService, [
|
|
cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED,
|
|
]);
|
|
|
|
toolbarService.registerEventForToolbarUpdate(segmentationService, [
|
|
segmentationService.EVENTS.SEGMENTATION_REMOVED,
|
|
segmentationService.EVENTS.SEGMENTATION_MODIFIED,
|
|
segmentationService.EVENTS.SEGMENTATION_ANNOTATION_CUT_MERGE_PROCESS_COMPLETED,
|
|
]);
|
|
|
|
toolbarService.registerEventForToolbarUpdate(cornerstone.eventTarget, [
|
|
cornerstoneTools.Enums.Events.TOOL_ACTIVATED,
|
|
]);
|
|
|
|
// Configure the interleaved/HTJ2K loader
|
|
imageRetrieveMetadataProvider.clear();
|
|
// The default volume interleaved options are to interleave the
|
|
// image retrieve, but don't perform progressive loading per image
|
|
// This interleaves images and replicates them for low-resolution depth volume
|
|
// reconstruction, which progressively improves
|
|
imageRetrieveMetadataProvider.add(
|
|
'volume',
|
|
cornerstone.ProgressiveRetrieveImages.interleavedRetrieveStages
|
|
);
|
|
|
|
/**
|
|
* Stack retrieve options: read from active data source configuration.
|
|
* Pass an immutability-helper spec (e.g. { $merge: {...} } or { $set: {...} }) in
|
|
* stackRetrieveOptions to customize. Plain object is treated as $merge for backward compat.
|
|
* Set streaming: false for uncompressed DICOM that requires full file before decode.
|
|
*/
|
|
const sourceConfig = extensionManager?.getActiveDataSource?.()?.[0]?.getConfig?.() ?? {};
|
|
const config = sourceConfig.stackRetrieveOptions ?? {};
|
|
const stackOptions = update(DEFAULT_STACK_RETRIEVE_OPTIONS, toUpdateSpec(config)) as typeof DEFAULT_STACK_RETRIEVE_OPTIONS;
|
|
imageRetrieveMetadataProvider.add('stack', stackOptions);
|
|
},
|
|
getPanelModule,
|
|
onModeExit: ({ servicesManager }: withAppTypes): void => {
|
|
unsubscriptions.forEach(unsubscribe => unsubscribe());
|
|
// Clear the unsubscriptions
|
|
unsubscriptions.length = 0;
|
|
|
|
const { cineService, segmentationService } = servicesManager.services;
|
|
// Empty out the image load and retrieval pools to prevent memory leaks
|
|
// on the mode exits
|
|
Object.values(cs3DEnums.RequestType).forEach(type => {
|
|
imageLoadPoolManager.clearRequestStack(type);
|
|
imageRetrievalPoolManager.clearRequestStack(type);
|
|
});
|
|
|
|
// Release the typed metadata registry (NATURALIZED instances registered via
|
|
// prefetchPart10Instance hold full compressed Part 10 buffers that live
|
|
// outside the size-capped cornerstone image cache)
|
|
csMetadataUtilities.clearCacheData();
|
|
|
|
cineService.setIsCineEnabled(false);
|
|
|
|
enabledElementReset();
|
|
|
|
useLutPresentationStore.getState().clearLutPresentationStore();
|
|
usePositionPresentationStore.getState().clearPositionPresentationStore();
|
|
useSynchronizersStore.getState().clearSynchronizersStore();
|
|
useToggleOneUpViewportGridStore.getState().clearToggleOneUpViewportGridStore();
|
|
useSegmentationPresentationStore.getState().clearSegmentationPresentationStore();
|
|
useSelectedSegmentationsForViewportStore
|
|
.getState()
|
|
.clearSelectedSegmentationsForViewportStore();
|
|
servicesManager.services.viewedDataService?.clearViewedData();
|
|
segmentationService.removeAllSegmentations();
|
|
},
|
|
|
|
/**
|
|
* Register the Cornerstone 3D services and set them up for use.
|
|
*
|
|
* @param configuration.csToolsConfig - Passed directly to `initCornerstoneTools`
|
|
*/
|
|
preRegistration: async function (props: Types.Extensions.ExtensionParams): Promise<void> {
|
|
const { servicesManager } = props;
|
|
servicesManager.registerService(CornerstoneViewportService.REGISTRATION);
|
|
servicesManager.registerService(ToolGroupService.REGISTRATION);
|
|
servicesManager.registerService(SyncGroupService.REGISTRATION);
|
|
servicesManager.registerService(SegmentationService.REGISTRATION);
|
|
servicesManager.registerService(CornerstoneCacheService.REGISTRATION);
|
|
servicesManager.registerService(ColorbarService.REGISTRATION);
|
|
servicesManager.registerService(ViewedDataService.REGISTRATION);
|
|
|
|
const { syncGroupService } = servicesManager.services;
|
|
syncGroupService.registerCustomSynchronizer('frameview', createFrameViewSynchronizer);
|
|
|
|
await init.call(this, props);
|
|
},
|
|
getToolbarModule,
|
|
getHangingProtocolModule,
|
|
getViewportModule({ servicesManager, commandsManager }) {
|
|
const ExtendedOHIFCornerstoneViewport = props => {
|
|
const { toolbarService } = servicesManager.services;
|
|
|
|
return (
|
|
<OHIFCornerstoneViewport
|
|
{...props}
|
|
toolbarService={toolbarService}
|
|
servicesManager={servicesManager}
|
|
commandsManager={commandsManager}
|
|
/>
|
|
);
|
|
};
|
|
|
|
return [
|
|
{
|
|
name: 'cornerstone',
|
|
component: ExtendedOHIFCornerstoneViewport,
|
|
isReferenceViewable: utils.isReferenceViewable.bind(null, servicesManager),
|
|
},
|
|
];
|
|
},
|
|
getCommandsModule,
|
|
getCustomizationModule,
|
|
getUtilityModule({ servicesManager }) {
|
|
return [
|
|
{
|
|
name: 'common',
|
|
exports: {
|
|
getCornerstoneLibraries: () => {
|
|
return { cornerstone, cornerstoneTools };
|
|
},
|
|
getEnabledElement,
|
|
dicomLoaderService,
|
|
},
|
|
},
|
|
{
|
|
name: 'core',
|
|
exports: {
|
|
Enums: cs3DEnums,
|
|
},
|
|
},
|
|
{
|
|
name: 'tools',
|
|
exports: {
|
|
toolNames,
|
|
Enums: cs3DToolsEnums,
|
|
},
|
|
},
|
|
{
|
|
name: 'volumeLoader',
|
|
exports: {
|
|
getDynamicVolumeInfo,
|
|
},
|
|
},
|
|
];
|
|
},
|
|
getSopClassHandlerModule,
|
|
};
|
|
|
|
export type { PublicViewportOptions };
|
|
export {
|
|
measurementMappingUtils,
|
|
PlanarFreehandROI,
|
|
RectangleROI,
|
|
CornerstoneExtensionTypes as Types,
|
|
toolNames,
|
|
getActiveViewportEnabledElement,
|
|
setEnabledElement,
|
|
findNearbyToolData,
|
|
getEnabledElement,
|
|
ImageOverlayViewerTool,
|
|
getSOPInstanceAttributes,
|
|
dicomLoaderService,
|
|
// Export all stores
|
|
useLutPresentationStore,
|
|
usePositionPresentationStore,
|
|
useSegmentationPresentationStore,
|
|
useSynchronizersStore,
|
|
useSelectedSegmentationsForViewportStore,
|
|
Enums,
|
|
useMeasurements,
|
|
useActiveViewportSegmentationRepresentations,
|
|
useSegmentations,
|
|
PanelSegmentation,
|
|
PanelMeasurement,
|
|
StudySummaryFromMetadata,
|
|
CornerstoneViewportDownloadForm,
|
|
utils,
|
|
OHIFCornerstoneViewport,
|
|
useMeasurementTracking,
|
|
};
|
|
|
|
// Export constants
|
|
export { VOLUME_LOADER_SCHEME, DYNAMIC_VOLUME_LOADER_SCHEME } from './constants';
|
|
export default cornerstoneExtension;
|