diff --git a/.circleci/config.yml b/.circleci/config.yml
index 6bf4b7692be..3130f457e45 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -13,18 +13,18 @@ version: 2.1
##
orbs:
codecov: codecov/codecov@1.0.5
- cypress: cypress-io/cypress@1.26.0
-executors:
- # Custom executor to override Cypress config
- deploy-to-prod-executor:
- docker:
- - image: cimg/node:16.14
- environment:
- CYPRESS_BASE_URL: https://ohif-staging.netlify.com/
- chrome-and-pacs:
- docker:
- # Primary container image where all steps run.
- - image: 'cypress/browsers:node16.14.2-slim-chrome103-ff102'
+ cypress: cypress-io/cypress@3
+# executors:
+# # Custom executor to override Cypress config
+# deploy-to-prod-executor:
+# docker:
+# - image: cimg/node:16.14
+# environment:
+# CYPRESS_BASE_URL: https://ohif-staging.netlify.com/
+# chrome-and-pacs:
+# docker:
+# # Primary container image where all steps run.
+# - image: 'cypress/browsers:node18.12.0-chrome106-ff106'
defaults: &defaults
docker:
@@ -363,32 +363,20 @@ jobs:
fi
workflows:
- version: 2
-
PR_CHECKS:
jobs:
- UNIT_TESTS
- # E2E: PWA
- cypress/run:
name: 'E2E: PWA'
- executor: chrome-and-pacs
- browser: chrome
- pre-steps:
- - run: |
- # Clear yarn cache; use yarn from image (update image to update yarn)
- rm -rf ~/.yarn
- yarn -v
- yarn: true
- record: true
- store_artifacts: true
- working_directory: platform/app
- build: yarn test:data
- start: yarn run test:e2e:serve
- spec: 'cypress/integration/**/*'
- wait-on: 'http://localhost:3000'
- cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
- no-workspace: true # Don't persist workspace
+ start-command: yarn run test:data && yarn run test:e2e:serve
+ install-browsers: true
+ cypress-command:
+ 'npx wait-on@latest http://localhost:3000 && cd platform/app && npx cypress run
+ --record --browser chrome --parallel'
+ package-manager: 'yarn'
+ cypress-cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
+ cypress-cache-path: '~/.cache/Cypress'
post-steps:
- store_artifacts:
path: platform/app/cypress/screenshots
@@ -399,34 +387,34 @@ workflows:
requires:
- UNIT_TESTS
- PR_OPTIONAL_VISUAL_TESTS:
- jobs:
- - AWAIT_APPROVAL:
- type: approval
- # Update hub.docker.org
- - cypress/run:
- name: 'Generate Percy Snapshots'
- executor: cypress/browsers-chrome76
- browser: chrome
- pre-steps:
- - run: 'rm -rf ~/.yarn && yarn -v && yarn global add wait-on'
- yarn: true
- store_artifacts: false
- working_directory: platform/app
- build:
- yarn test:data && npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js
- yarn run build
- # start server --> verify running --> percy + chrome + cypress
- command: yarn run test:e2e:dist
- cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
- no-workspace: true # Don't persist workspace
- post-steps:
- - store_artifacts:
- path: platform/app/cypress/screenshots
- - store_artifacts:
- path: platform/app/cypress/videos
- requires:
- - AWAIT_APPROVAL
+ # PR_OPTIONAL_VISUAL_TESTS:
+ # jobs:
+ # - AWAIT_APPROVAL:
+ # type: approval
+ # # Update hub.docker.org
+ # - cypress/run:
+ # name: 'Generate Percy Snapshots'
+ # executor: cypress/browsers-chrome76
+ # browser: chrome
+ # pre-steps:
+ # - run: 'rm -rf ~/.yarn && yarn -v && yarn global add wait-on'
+ # yarn: true
+ # store_artifacts: false
+ # working_directory: platform/app
+ # build:
+ # yarn test:data && npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js
+ # yarn run build
+ # # start server --> verify running --> percy + chrome + cypress
+ # command: yarn run test:e2e:dist
+ # cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
+ # no-workspace: true # Don't persist workspace
+ # post-steps:
+ # - store_artifacts:
+ # path: platform/app/cypress/screenshots
+ # - store_artifacts:
+ # path: platform/app/cypress/videos
+ # requires:
+ # - AWAIT_APPROVAL
# Our master branch deploys to viewer-dev.ohif.org, the viewer.ohif.org is
# deployed from the release branch which is more stable and less frequently updated.
diff --git a/extensions/cornerstone-dicom-rt/src/index.tsx b/extensions/cornerstone-dicom-rt/src/index.tsx
index a8541a303b4..953c9a7c7da 100644
--- a/extensions/cornerstone-dicom-rt/src/index.tsx
+++ b/extensions/cornerstone-dicom-rt/src/index.tsx
@@ -2,7 +2,6 @@ import { id } from './id';
import React from 'react';
import { Types } from '@ohif/core';
import getSopClassHandlerModule from './getSopClassHandlerModule';
-import hydrateRTDisplaySet from './utils/_hydrateRT';
const Component = React.lazy(() => {
return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneRTViewport');
@@ -60,4 +59,3 @@ const extension: Types.Extensions.Extension = {
};
export default extension;
-export { hydrateRTDisplaySet };
diff --git a/extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts b/extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts
deleted file mode 100644
index d61e9e18654..00000000000
--- a/extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-async function _hydrateRTDisplaySet({ rtDisplaySet, viewportId, servicesManager }) {
- const { segmentationService, hangingProtocolService, viewportGridService } =
- servicesManager.services;
-
- const displaySetInstanceUID = rtDisplaySet.referencedDisplaySetInstanceUID;
-
- let segmentationId = null;
-
- // We need the hydration to notify panels about the new segmentation added
- const suppressEvents = false;
-
- segmentationId = await segmentationService.createSegmentationForRTDisplaySet(
- rtDisplaySet,
- segmentationId,
- suppressEvents
- );
-
- segmentationService.hydrateSegmentation(rtDisplaySet.displaySetInstanceUID);
-
- const { viewports } = viewportGridService.getState();
-
- const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
- viewportId,
- displaySetInstanceUID
- );
-
- viewportGridService.setDisplaySetsForViewports(updatedViewports);
-
- // Todo: fix this after we have a better way for stack viewport segmentations
-
- // check every viewport in the viewports to see if the displaySetInstanceUID
- // is being displayed, if so we need to update the viewport to use volume viewport
- // (if already is not using it) since Cornerstone3D currently only supports
- // volume viewport for segmentation
- viewports.forEach(viewport => {
- if (viewport.viewportId === viewportId) {
- return;
- }
-
- const shouldDisplaySeg = segmentationService.shouldRenderSegmentation(
- viewport.displaySetInstanceUIDs,
- rtDisplaySet.displaySetInstanceUID
- );
-
- if (shouldDisplaySeg) {
- updatedViewports.push({
- viewportId: viewport.viewportId,
- displaySetInstanceUIDs: viewport.displaySetInstanceUIDs,
- viewportOptions: {
- // Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component
- // Used for segmentation hydration right now, since the logic to decide whether
- // a viewport needs to render a segmentation lives inside the CornerstoneViewportService
- // so we need to re-render (force update via change of the needsRerendering) so that React
- // does the diffing and decides we should render this again (although the id and element has not changed)
- // so that the CornerstoneViewportService can decide whether to render the segmentation or not.
- needsRerendering: true,
- initialImageOptions: {
- preset: 'middle',
- },
- },
- });
- }
- });
-
- // Do the entire update at once
- viewportGridService.setDisplaySetsForViewports(updatedViewports);
- return true;
-}
-
-export default _hydrateRTDisplaySet;
diff --git a/extensions/cornerstone-dicom-rt/src/utils/initRTToolGroup.ts b/extensions/cornerstone-dicom-rt/src/utils/initRTToolGroup.ts
index aaa27c28f89..f47f0089d8a 100644
--- a/extensions/cornerstone-dicom-rt/src/utils/initRTToolGroup.ts
+++ b/extensions/cornerstone-dicom-rt/src/utils/initRTToolGroup.ts
@@ -1,7 +1,7 @@
function createRTToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) {
const { tools } = customizationService.get('cornerstone.overlayViewportTools') ?? {};
- return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, {});
+ return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools);
}
export default createRTToolGroupAndAddTools;
diff --git a/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts b/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts
index c6069abc159..91492cbfbed 100644
--- a/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts
+++ b/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts
@@ -1,5 +1,4 @@
import { ButtonEnums } from '@ohif/ui';
-import hydrateRTDisplaySet from './_hydrateRT';
const RESPONSE = {
NO_NEVER: -1,
@@ -13,6 +12,7 @@ function promptHydrateRT({
viewportId,
toolGroupId = 'default',
preHydrateCallbacks,
+ hydrateRTDisplaySet,
}) {
const { uiViewportDialogService } = servicesManager.services;
diff --git a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx
index 3dd9588d7db..e9c7450c69a 100644
--- a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx
+++ b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx
@@ -3,11 +3,9 @@ import PropTypes from 'prop-types';
import OHIF, { utils } from '@ohif/core';
import { ViewportActionBar, useViewportGrid, LoadingIndicatorTotalPercent } from '@ohif/ui';
-import _hydrateRTdisplaySet from '../utils/_hydrateRT';
import promptHydrateRT from '../utils/promptHydrateRT';
import _getStatusComponent from './_getStatusComponent';
import createRTToolGroupAndAddTools from '../utils/initRTToolGroup';
-import _hydrateRTDisplaySet from '../utils/_hydrateRT';
const { formatDate } = utils;
const RT_TOOLGROUP_BASE_NAME = 'RTToolGroup';
@@ -95,6 +93,13 @@ function OHIFCornerstoneRTViewport(props) {
});
}, [viewportGrid]);
+ const hydrateRTDisplaySet = ({ rtDisplaySet, viewportId }) => {
+ commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
+ displaySets: [rtDisplaySet],
+ viewportId,
+ });
+ };
+
const getCornerstoneViewport = useCallback(() => {
const { component: Component } = extensionManager.getModuleEntry(
'@ohif/extension-cornerstone.viewportModule.cornerstone'
@@ -154,6 +159,7 @@ function OHIFCornerstoneRTViewport(props) {
viewportId,
rtDisplaySet,
preHydrateCallbacks: [storePresentationState],
+ hydrateRTDisplaySet,
}).then(isHydrated => {
if (isHydrated) {
setIsHydrated(true);
@@ -295,10 +301,9 @@ function OHIFCornerstoneRTViewport(props) {
// presentation state (w/l and invert) and then opens the RT. If we don't store
// the presentation state, the viewport will be reset to the default presentation
storePresentationState();
- const isHydrated = await _hydrateRTDisplaySet({
+ const isHydrated = await hydrateRTDisplaySet({
rtDisplaySet,
viewportId,
- servicesManager,
});
setIsHydrated(isHydrated);
diff --git a/extensions/cornerstone-dicom-seg/package.json b/extensions/cornerstone-dicom-seg/package.json
index 19118a58def..7e8132cb247 100644
--- a/extensions/cornerstone-dicom-seg/package.json
+++ b/extensions/cornerstone-dicom-seg/package.json
@@ -43,6 +43,7 @@
"react-router-dom": "^6.8.1"
},
"dependencies": {
+ "@cornerstonejs/tools": "^1.16.4",
"@babel/runtime": "^7.20.13",
"react-color": "^2.19.3"
}
diff --git a/extensions/cornerstone-dicom-seg/src/commandsModule.ts b/extensions/cornerstone-dicom-seg/src/commandsModule.ts
new file mode 100644
index 00000000000..7cffb83f764
--- /dev/null
+++ b/extensions/cornerstone-dicom-seg/src/commandsModule.ts
@@ -0,0 +1,383 @@
+import dcmjs from 'dcmjs';
+import { createReportDialogPrompt } from '@ohif/extension-default';
+import { ServicesManager, Types } from '@ohif/core';
+import { cache, metaData } from '@cornerstonejs/core';
+import { segmentation as cornerstoneToolsSegmentation } from '@cornerstonejs/tools';
+import { adaptersSEG, helpers } from '@cornerstonejs/adapters';
+import { DicomMetadataStore } from '@ohif/core';
+
+import {
+ updateViewportsForSegmentationRendering,
+ getUpdatedViewportsForSegmentation,
+ getTargetViewport,
+} from './utils/hydrationUtils';
+
+const {
+ Cornerstone3D: {
+ Segmentation: { generateLabelMaps2DFrom3D, generateSegmentation },
+ },
+} = adaptersSEG;
+
+const { downloadDICOMData } = helpers;
+
+const commandsModule = ({
+ servicesManager,
+ extensionManager,
+}: Types.Extensions.ExtensionParams): Types.Extensions.CommandsModule => {
+ const {
+ uiNotificationService,
+ segmentationService,
+ uiDialogService,
+ displaySetService,
+ viewportGridService,
+ } = (servicesManager as ServicesManager).services;
+
+ const actions = {
+ /**
+ * Retrieves a list of viewports that require updates in preparation for segmentation rendering.
+ * This function evaluates viewports based on their compatibility with the provided segmentation's
+ * frame of reference UID and appends them to the updated list if they should render the segmentation.
+ *
+ * @param {Object} params - Parameters for the function.
+ * @param params.viewportId - the ID of the viewport to be updated.
+ * @param params.servicesManager - The services manager
+ * @param params.referencedDisplaySetInstanceUID - Optional UID for the referenced display set instance.
+ *
+ * @returns {Array} Returns an array of viewports that require updates for segmentation rendering.
+ */
+ getUpdatedViewportsForSegmentation,
+ /**
+ * Creates an empty segmentation for a specified viewport.
+ * It first checks if the display set associated with the viewport is reconstructable.
+ * If not, it raises a notification error. Otherwise, it creates a new segmentation
+ * for the display set after handling the necessary steps for making the viewport
+ * a volume viewport first
+ *
+ * @param {Object} params - Parameters for the function.
+ * @param params.viewportId - the target viewport ID.
+ *
+ */
+ createEmptySegmentationForViewport: async ({ viewportId }) => {
+ const viewport = getTargetViewport({ viewportId, viewportGridService });
+ // Todo: add support for multiple display sets
+ const displaySetInstanceUID = viewport.displaySetInstanceUIDs[0];
+
+ const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
+
+ if (!displaySet.isReconstructable) {
+ uiNotificationService.show({
+ title: 'Segmentation',
+ message: 'Segmentation is not supported for non-reconstructible displaysets yet',
+ type: 'error',
+ });
+ return;
+ }
+
+ updateViewportsForSegmentationRendering({
+ viewportId,
+ servicesManager,
+ loadFn: async () => {
+ const currentSegmentations = segmentationService.getSegmentations();
+ const segmentationId = await segmentationService.createSegmentationForDisplaySet(
+ displaySetInstanceUID,
+ { label: `Segmentation ${currentSegmentations.length + 1}` }
+ );
+
+ const toolGroupId = viewport.viewportOptions.toolGroupId;
+
+ await segmentationService.addSegmentationRepresentationToToolGroup(
+ toolGroupId,
+ segmentationId
+ );
+
+ // Add only one segment for now
+ segmentationService.addSegment(segmentationId, {
+ toolGroupId,
+ segmentIndex: 1,
+ properties: {
+ label: 'Segment 1',
+ },
+ });
+
+ return segmentationId;
+ },
+ });
+ },
+ /**
+ * Loads segmentations for a specified viewport.
+ * The function prepares the viewport for rendering, then loads the segmentation details.
+ * Additionally, if the segmentation has scalar data, it is set for the corresponding label map volume.
+ *
+ * @param {Object} params - Parameters for the function.
+ * @param params.segmentations - Array of segmentations to be loaded.
+ * @param params.viewportId - the target viewport ID.
+ *
+ */
+ loadSegmentationsForViewport: async ({ segmentations, viewportId }) => {
+ updateViewportsForSegmentationRendering({
+ viewportId,
+ servicesManager,
+ loadFn: async () => {
+ // Todo: handle adding more than one segmentation
+ const viewport = getTargetViewport({ viewportId, viewportGridService });
+ const displaySetInstanceUID = viewport.displaySetInstanceUIDs[0];
+
+ const segmentation = segmentations[0];
+ const segmentationId = segmentation.id;
+ const label = segmentation.label;
+ const segments = segmentation.segments;
+
+ delete segmentation.segments;
+
+ await segmentationService.createSegmentationForDisplaySet(displaySetInstanceUID, {
+ segmentationId,
+ label,
+ });
+
+ if (segmentation.scalarData) {
+ const labelmapVolume = segmentationService.getLabelmapVolume(segmentationId);
+ labelmapVolume.scalarData.set(segmentation.scalarData);
+ }
+
+ segmentationService.addOrUpdateSegmentation(segmentation);
+
+ const toolGroupId = viewport.viewportOptions.toolGroupId;
+ await segmentationService.addSegmentationRepresentationToToolGroup(
+ toolGroupId,
+ segmentationId
+ );
+
+ segments.forEach(segment => {
+ if (segment === null) {
+ return;
+ }
+ segmentationService.addSegment(segmentationId, {
+ segmentIndex: segment.segmentIndex,
+ toolGroupId,
+ properties: {
+ color: segment.color,
+ label: segment.label,
+ opacity: segment.opacity,
+ isLocked: segment.isLocked,
+ visibility: segment.isVisible,
+ active: segmentation.activeSegmentIndex === segment.segmentIndex,
+ },
+ });
+ });
+
+ if (segmentation.centroidsIJK) {
+ segmentationService.setCentroids(segmentation.id, segmentation.centroidsIJK);
+ }
+
+ return segmentationId;
+ },
+ });
+ },
+ /**
+ * Loads segmentation display sets for a specified viewport.
+ * Depending on the modality of the display set (SEG or RTSTRUCT),
+ * it chooses the appropriate service function to create
+ * the segmentation for the display set.
+ * The function then prepares the viewport for rendering segmentation.
+ *
+ * @param {Object} params - Parameters for the function.
+ * @param params.viewportId - ID of the viewport where the segmentation display sets should be loaded.
+ * @param params.displaySets - Array of display sets to be loaded for segmentation.
+ *
+ */
+ loadSegmentationDisplaySetsForViewport: async ({ viewportId, displaySets }) => {
+ // Todo: handle adding more than one segmentation
+ const displaySet = displaySets[0];
+
+ updateViewportsForSegmentationRendering({
+ viewportId,
+ servicesManager,
+ referencedDisplaySetInstanceUID: displaySet.referencedDisplaySetInstanceUID,
+ loadFn: async () => {
+ const segDisplaySet = displaySet;
+ const suppressEvents = false;
+ const serviceFunction =
+ segDisplaySet.Modality === 'SEG'
+ ? 'createSegmentationForSEGDisplaySet'
+ : 'createSegmentationForRTDisplaySet';
+
+ const boundFn = segmentationService[serviceFunction].bind(segmentationService);
+ const segmentationId = await boundFn(segDisplaySet, null, suppressEvents);
+
+ return segmentationId;
+ },
+ });
+ },
+ /**
+ * Generates a segmentation from a given segmentation ID.
+ * This function retrieves the associated segmentation and
+ * its referenced volume, extracts label maps from the
+ * segmentation volume, and produces segmentation data
+ * alongside associated metadata.
+ *
+ * @param {Object} params - Parameters for the function.
+ * @param params.segmentationId - ID of the segmentation to be generated.
+ * @param params.options - Optional configuration for the generation process.
+ *
+ * @returns Returns the generated segmentation data.
+ */
+ generateSegmentation: ({ segmentationId, options = {} }) => {
+ const segmentation = cornerstoneToolsSegmentation.state.getSegmentation(segmentationId);
+
+ const { referencedVolumeId } = segmentation.representationData.LABELMAP;
+
+ const segmentationVolume = cache.getVolume(segmentationId);
+ const referencedVolume = cache.getVolume(referencedVolumeId);
+ const referencedImages = referencedVolume.getCornerstoneImages();
+
+ const labelmapObj = generateLabelMaps2DFrom3D(segmentationVolume);
+
+ // Generate fake metadata as an example
+ labelmapObj.metadata = [];
+
+ const segmentationInOHIF = segmentationService.getSegmentation(segmentationId);
+ labelmapObj.segmentsOnLabelmap.forEach(segmentIndex => {
+ // segmentation service already has a color for each segment
+ const segment = segmentationInOHIF?.segments[segmentIndex];
+ const { label, color } = segment;
+
+ const RecommendedDisplayCIELabValue = dcmjs.data.Colors.rgb2DICOMLAB(
+ color.slice(0, 3).map(value => value / 255)
+ ).map(value => Math.round(value));
+
+ const segmentMetadata = {
+ SegmentNumber: segmentIndex.toString(),
+ SegmentLabel: label,
+ SegmentAlgorithmType: 'MANUAL',
+ SegmentAlgorithmName: 'OHIF Brush',
+ RecommendedDisplayCIELabValue,
+ SegmentedPropertyCategoryCodeSequence: {
+ CodeValue: 'T-D0050',
+ CodingSchemeDesignator: 'SRT',
+ CodeMeaning: 'Tissue',
+ },
+ SegmentedPropertyTypeCodeSequence: {
+ CodeValue: 'T-D0050',
+ CodingSchemeDesignator: 'SRT',
+ CodeMeaning: 'Tissue',
+ },
+ };
+ labelmapObj.metadata[segmentIndex] = segmentMetadata;
+ });
+
+ const generatedSegmentation = generateSegmentation(
+ referencedImages,
+ labelmapObj,
+ metaData,
+ options
+ );
+
+ return generatedSegmentation;
+ },
+ /**
+ * Downloads a segmentation based on the provided segmentation ID.
+ * This function retrieves the associated segmentation and
+ * uses it to generate the corresponding DICOM dataset, which
+ * is then downloaded with an appropriate filename.
+ *
+ * @param {Object} params - Parameters for the function.
+ * @param params.segmentationId - ID of the segmentation to be downloaded.
+ *
+ */
+ downloadSegmentation: ({ segmentationId }) => {
+ const segmentationInOHIF = segmentationService.getSegmentation(segmentationId);
+ const generatedSegmentation = actions.generateSegmentation({
+ segmentationId,
+ });
+
+ downloadDICOMData(generatedSegmentation.dataset, `${segmentationInOHIF.label}`);
+ },
+ /**
+ * Stores a segmentation based on the provided segmentationId into a specified data source.
+ * The SeriesDescription is derived from user input or defaults to the segmentation label,
+ * and in its absence, defaults to 'Research Derived Series'.
+ *
+ * @param {Object} params - Parameters for the function.
+ * @param params.segmentationId - ID of the segmentation to be stored.
+ * @param params.dataSource - Data source where the generated segmentation will be stored.
+ *
+ * @returns {Object|void} Returns the naturalized report if successfully stored,
+ * otherwise throws an error.
+ */
+ storeSegmentation: async ({ segmentationId, dataSource }) => {
+ const promptResult = await createReportDialogPrompt(uiDialogService, {
+ extensionManager,
+ });
+
+ if (promptResult.action !== 1 && promptResult.value) {
+ return;
+ }
+
+ const segmentation = segmentationService.getSegmentation(segmentationId);
+
+ if (!segmentation) {
+ throw new Error('No segmentation found');
+ }
+
+ const { label } = segmentation;
+ const SeriesDescription = promptResult.value || label || 'Research Derived Series';
+
+ const generatedData = actions.generateSegmentation({
+ segmentationId,
+ options: {
+ SeriesDescription,
+ },
+ });
+
+ if (!generatedData || !generatedData.dataset) {
+ throw new Error('Error during segmentation generation');
+ }
+
+ const { dataset: naturalizedReport } = generatedData;
+
+ await dataSource.store.dicom(naturalizedReport);
+
+ // The "Mode" route listens for DicomMetadataStore changes
+ // When a new instance is added, it listens and
+ // automatically calls makeDisplaySets
+
+ // add the information for where we stored it to the instance as well
+ naturalizedReport.wadoRoot = dataSource.getConfig().wadoRoot;
+
+ DicomMetadataStore.addInstances([naturalizedReport], true);
+
+ return naturalizedReport;
+ },
+ };
+
+ const definitions = {
+ getUpdatedViewportsForSegmentation: {
+ commandFn: actions.getUpdatedViewportsForSegmentation,
+ },
+ loadSegmentationDisplaySetsForViewport: {
+ commandFn: actions.loadSegmentationDisplaySetsForViewport,
+ },
+ loadSegmentationsForViewport: {
+ commandFn: actions.loadSegmentationsForViewport,
+ },
+ createEmptySegmentationForViewport: {
+ commandFn: actions.createEmptySegmentationForViewport,
+ },
+ generateSegmentation: {
+ commandFn: actions.generateSegmentation,
+ },
+ downloadSegmentation: {
+ commandFn: actions.downloadSegmentation,
+ },
+ storeSegmentation: {
+ commandFn: actions.storeSegmentation,
+ },
+ };
+
+ return {
+ actions,
+ definitions,
+ };
+};
+
+export default commandsModule;
diff --git a/extensions/cornerstone-dicom-seg/src/getPanelModule.tsx b/extensions/cornerstone-dicom-seg/src/getPanelModule.tsx
new file mode 100644
index 00000000000..e626c87d7bf
--- /dev/null
+++ b/extensions/cornerstone-dicom-seg/src/getPanelModule.tsx
@@ -0,0 +1,70 @@
+import React from 'react';
+
+import { useAppConfig } from '@state';
+import PanelSegmentation from './panels/PanelSegmentation';
+import SegmentationToolbox from './panels/SegmentationToolbox';
+
+const getPanelModule = ({ commandsManager, servicesManager, extensionManager, configuration }) => {
+ const { customizationService } = servicesManager.services;
+
+ const wrappedPanelSegmentation = configuration => {
+ const [appConfig] = useAppConfig();
+
+ const disableEditingForMode = customizationService.get('segmentation.disableEditing');
+
+ return (
+
Are you sure you want to delete this report?
-This action cannot be undone.
+This action cannot be undone.