Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
04a85c1
Added formpanel to segmentation mode and added form change listener i…
Adithyan-Dinesh-Trenser Jan 2, 2024
c232176
Jump to referenced displaySet if segmentation is selected. ALso chang…
Adithyan-Dinesh-Trenser Jan 10, 2024
9bd8ac3
Removed stack segmentation cache when segmentation is removed. Includ…
Adithyan-Dinesh-Trenser Jan 10, 2024
9ae0a9d
Fixed the colorLUT index issue. This Issue was found in the upstream.
Adithyan-Dinesh-Trenser Jan 10, 2024
4fb2196
Deployed to production
Adithyan-Dinesh-Trenser Jan 12, 2024
09ac407
Skipped CORS and segment default color usage warnings and reorganized…
Adithyan-Dinesh-Trenser Jan 12, 2024
9a435f0
save to the same segmentation file if modified.
Adithyan-Dinesh-Trenser Jan 16, 2024
676347d
Changed the min size of segmentation brush and eraser to 0.01
Adithyan-Dinesh-Trenser Jan 17, 2024
bb31c0f
Removed the old segmentation file from fileManager when saving to sam…
Adithyan-Dinesh-Trenser Jan 17, 2024
590da1a
updated the cornerstone deployment branch
Adithyan-Dinesh-Trenser Jan 17, 2024
54428b6
Implemented segment focus, modified segment brush unit and default si…
Adithyan-Dinesh-Trenser Feb 2, 2024
e790e42
Skiped label dialog in auto saving flow even for new segmentations.
Adithyan-Dinesh-Trenser Feb 5, 2024
8659cf6
Made Min and max brush size configurable. Truncated segmentation labe…
Adithyan-Dinesh-Trenser Feb 22, 2024
e95d606
Changed the configurable brush sizes unit to mm
Adithyan-Dinesh-Trenser Feb 28, 2024
d2eca14
Fixed the issue of min/ max brush sizes not using configured values w…
Adithyan-Dinesh-Trenser Mar 1, 2024
3ea6d7d
Segmentation series description will be displayed using ImageLaterali…
Adithyan-Dinesh-Trenser Mar 6, 2024
290fda3
Corrected the logic to access referenced displayset for newly created…
Adithyan-Dinesh-Trenser Mar 8, 2024
cd59393
Added more properties to retain to segmentation file when saving segm…
Adithyan-Dinesh-Trenser Mar 15, 2024
52b3dbe
Due to script update for modifying the segmentation SeriesDescription…
Adithyan-Dinesh-Trenser Mar 18, 2024
8d20452
Disabled add segmentation using url param
Adithyan-Dinesh-Trenser Mar 20, 2024
0ea434d
Added hotkeys to activate segmentation brush and eraser tools.
Adithyan-Dinesh-Trenser Mar 25, 2024
cc13c72
Updated SeriesDate when saving segmentations instead of using the pre…
Adithyan-Dinesh-Trenser Mar 26, 2024
68c1170
Synced segmentation tools hotkeys with segmentation panel
Adithyan-Dinesh-Trenser Mar 26, 2024
f4191a4
Trigger deploy
Adithyan-Dinesh-Trenser Jun 20, 2024
9485743
Auto flipping of disoriented Mammography images on stack viewport
Adithyan-Dinesh-Trenser Sep 4, 2024
588f303
Merge pull request #17 from gradienthealth/gradienthealth/auto_flippi…
Adithyan-Dinesh-Trenser Sep 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/deploy_ghpages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Deploy viewer to github pages

on:
push:
branches: [ "gradienthealth/Stack-Segmentation" ]
branches: [ "gradienthealth/segmentation_mode_sheet_integration" ]
#pull_request:
#branches: [ "main" ]

Expand All @@ -24,7 +24,7 @@ jobs:
uses: actions/checkout@v3
with:
repository: gradienthealth/cornerstone3D-beta
ref: gradienthealth/stack-segmentation-support-with-zip-image-loader
ref: gradienthealth/segmentation_mode_sheet_integration
path: ./cornerstone3D

- name: Build cornerstone3D
Expand All @@ -37,7 +37,7 @@ jobs:
uses: actions/checkout@v3
with:
repository: gradienthealth/GradientExtensionsAndModes
ref: gradienthealth/Segmentation-with-DicomJSON
ref: gradienthealth/segmentation_mode_sheet_integration
path: ./GradientExtensionsAndModes

#- name: Build GradientExtensionsAndModes
Expand Down
42 changes: 29 additions & 13 deletions extensions/cornerstone-dicom-seg/src/commandsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
getTargetViewport,
} from './utils/hydrationUtils';
import generateLabelmaps2DFromImageIdMap from './utils/generateLabelmaps2DFromImageIdMap';
import getSegmentLabel from './utils/getSegmentLabel';

const { datasetToBlob } = dcmjs.data;

Expand Down Expand Up @@ -100,7 +101,7 @@ const commandsModule = ({
toolGroupId,
segmentIndex: 1,
properties: {
label: 'Segment 1',
label: getSegmentLabel(segmentationService.getSegmentation(segmentationId)),
},
});

Expand Down Expand Up @@ -318,28 +319,45 @@ const commandsModule = ({
* @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;
}

storeSegmentation: async ({ segmentationId, dataSource, skipLabelDialog = false }) => {
const segmentation = segmentationService.getSegmentation(segmentationId);

if (!segmentation) {
throw new Error('No segmentation found');
}
const { label, displaySetInstanceUID } = segmentation;

const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
const shouldOverWrite = displaySet && displaySet.Modality === 'SEG';

let promptResult: { action?: number; value?: string } = {};

if (!(skipLabelDialog || shouldOverWrite)) {
promptResult = await createReportDialogPrompt(uiDialogService, {
extensionManager,
});

if (promptResult.action !== 1 && !promptResult.value) {
return;
}
}

const { label } = segmentation;
const SeriesDescription = promptResult.value || label || 'Research Derived Series';
segmentation.label = SeriesDescription;

const generatedData = actions.generateSegmentation({
segmentationId,
options: {
SeriesDescription,
// Use SeriesInstanceUID, SOPInstanceUID, SeriesNumber, Manufacturer and SeriesDate
// if displaySet of the segmentation already exists.
// Study level and patient metadata will be used automatically.
...(shouldOverWrite && {
SeriesInstanceUID: displaySet.SeriesInstanceUID,
SOPInstanceUID: displaySet.instances[0].SOPInstanceUID,
SeriesNumber: displaySet.SeriesNumber,
Manufacturer: displaySet.instances[0].Manufacturer,
}),
},
});

Expand All @@ -358,8 +376,6 @@ const commandsModule = ({
// add the information for where we stored it to the instance as well
naturalizedReport.wadoRoot = dataSource.getConfig().wadoRoot;

DicomMetadataStore.addInstances([naturalizedReport], true);

return naturalizedReport;
},
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ async function _loadSegments({ extensionManager, servicesManager, segDisplaySet,
}
});

/* Skip the warning message as it is annoying on auto segmentations loading.
if (!usedRecommendedDisplayCIELabValue) {
// Display a notification about the non-utilization of RecommendedDisplayCIELabValue
uiNotificationService.show({
Expand All @@ -201,7 +202,7 @@ async function _loadSegments({ extensionManager, servicesManager, segDisplaySet,
type: 'warning',
duration: 5000,
});
}
}*/

Object.assign(segDisplaySet, results);
}
Expand Down
160 changes: 148 additions & 12 deletions extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
import { createReportAsync } from '@ohif/extension-default';
import React, { useEffect, useState, useCallback } from 'react';
import React, { useEffect, useState, useCallback, useReducer } from 'react';
import PropTypes from 'prop-types';
import { SegmentationGroupTable, LegacyButtonGroup, LegacyButton } from '@ohif/ui';

import callInputDialog from './callInputDialog';
import callColorPickerDialog from './colorPickerDialog';
import { useTranslation } from 'react-i18next';
import getSegmentLabel from '../utils/getSegmentLabel';

const savedStatusReducer = (state, action) => {
return {
...state,
...action.payload,
};
};

const SAVED_STATUS_ICON = {
SAVED: 'notifications-success',
MODIFIED: 'notifications-warning',
ERROR: 'notifications-error',
};

export default function PanelSegmentation({
servicesManager,
commandsManager,
extensionManager,
configuration,
}) {
const { segmentationService, viewportGridService, uiDialogService } = servicesManager.services;
const {
segmentationService,
viewportGridService,
uiDialogService,
displaySetService,
userAuthenticationService,
CropDisplayAreaService,
} = servicesManager.services;

const { t } = useTranslation('PanelSegmentation');

Expand All @@ -23,6 +44,7 @@ export default function PanelSegmentation({
);

const [segmentations, setSegmentations] = useState(() => segmentationService.getSegmentations());
const [savedStatusStates, dispatch] = useReducer(savedStatusReducer, {});

useEffect(() => {
// ~~ Subscription
Expand All @@ -47,7 +69,78 @@ export default function PanelSegmentation({
};
}, []);

useEffect(() => {
let changedSegmentations: any[] = [],
timerId;
const timoutInSeconds = 5;

const { unsubscribe } = segmentationService.subscribe(
segmentationService.EVENTS.SEGMENTATION_DATA_MODIFIED,
({ segmentation }) => {
clearTimeout(timerId);
dispatch({ payload: { [segmentation.id]: SAVED_STATUS_ICON.MODIFIED } });

if (
!changedSegmentations.find(
changedSegmentation => changedSegmentation.id === segmentation.id
)
) {
changedSegmentations.push(segmentation);
}

timerId = setTimeout(() => {
const datasources = extensionManager.getActiveDataSource();

const promises = changedSegmentations.map(segmentation =>
createReportAsync({
servicesManager: servicesManager,
getReport: () =>
commandsManager.runCommand('storeSegmentation', {
segmentationId: segmentation.id,
dataSource: datasources[0],
skipLabelDialog: true,
}),
reportType: 'Segmentation',
showLoadingModal: false,
throwErrors: true,
})
);

Promise.allSettled(promises).then(results => {
const payload = results.reduce((acc, result, index) => {
if (result.value) {
changedSegmentations[index].displaySetInstanceUID = result.value[0];
displaySetService.getDisplaySetByUID(result.value[0])?.getReferenceDisplaySet();
}

return {
...acc,
[changedSegmentations[index].id]:
result.status === 'fulfilled' ? SAVED_STATUS_ICON.SAVED : SAVED_STATUS_ICON.ERROR,
};
}, {});

dispatch({ payload });

const savedSegmentations = Object.keys(payload).filter(
id => payload[id] === SAVED_STATUS_ICON.SAVED
);
changedSegmentations = changedSegmentations.filter(
cs => !savedSegmentations.includes(cs.id)
);
});
}, timoutInSeconds * 1000);
}
);

return () => {
unsubscribe();
};
}, []);

const setSegmentationActive = segmentationId => {
setReferencedDisplaySet(segmentationId);

const isSegmentationActive = segmentations.find(seg => seg.id === segmentationId)?.isActive;

if (isSegmentationActive) {
Expand All @@ -57,6 +150,31 @@ export default function PanelSegmentation({
segmentationService.setActiveSegmentationForToolGroup(segmentationId);
};

// Set referenced displaySet of the segmentation to the viewport
// if it is not displayed in any of the viewports.
const setReferencedDisplaySet = segmentationId => {
const segDisplayset = displaySetService.getDisplaySetByUID(segmentationId);
if (!segDisplayset) {
return;
}

const referencedDisplaySetInstanceUID = segDisplayset.referencedDisplaySetInstanceUID;
const { viewports, activeViewportId } = viewportGridService.getState();
let referencedImageLoaded = false;
viewports.forEach(viewport => {
if (viewport.displaySetInstanceUIDs.includes(referencedDisplaySetInstanceUID)) {
referencedImageLoaded = true;
}
});

if (!referencedImageLoaded) {
viewportGridService.setDisplaySetsForViewport({
viewportId: activeViewportId,
displaySetInstanceUIDs: [referencedDisplaySetInstanceUID],
});
}
};

const getToolGroupIds = segmentationId => {
const toolGroupIds = segmentationService.getToolGroupIdsWithSegmentation(segmentationId);

Expand All @@ -68,6 +186,7 @@ export default function PanelSegmentation({
};

const onSegmentationClick = (segmentationId: string) => {
setReferencedDisplaySet(segmentationId);
segmentationService.setActiveSegmentationForToolGroup(segmentationId);
};

Expand All @@ -78,10 +197,12 @@ export default function PanelSegmentation({

const onSegmentAdd = segmentationId => {
setSegmentationActive(segmentationId);
segmentationService.addSegment(segmentationId);
const label = getSegmentLabel(segmentations.find(seg => seg.id === segmentationId));
segmentationService.addSegment(segmentationId, { properties: { label } });
};

const onSegmentClick = (segmentationId, segmentIndex) => {
setReferencedDisplaySet(segmentationId);
segmentationService.setActiveSegment(segmentationId, segmentIndex);

const toolGroupIds = getToolGroupIds(segmentationId);
Expand Down Expand Up @@ -210,16 +331,25 @@ export default function PanelSegmentation({
const storeSegmentation = async segmentationId => {
setSegmentationActive(segmentationId);
const datasources = extensionManager.getActiveDataSource();
let displaySetInstanceUIDs;

try {
displaySetInstanceUIDs = await createReportAsync({
servicesManager,
getReport: () =>
commandsManager.runCommand('storeSegmentation', {
segmentationId,
dataSource: datasources[0],
}),
reportType: 'Segmentation',
throwErrors: true,
});

const displaySetInstanceUIDs = await createReportAsync({
servicesManager,
getReport: () =>
commandsManager.runCommand('storeSegmentation', {
segmentationId,
dataSource: datasources[0],
}),
reportType: 'Segmentation',
});
dispatch({ payload: { [segmentationId]: SAVED_STATUS_ICON.SAVED } });
} catch (error) {
console.warn(error.message);
dispatch({ payload: { [segmentationId]: SAVED_STATUS_ICON.ERROR } });
}

// Show the exported report in the active viewport as read only (similar to SR)
if (displaySetInstanceUIDs) {
Expand All @@ -242,13 +372,18 @@ export default function PanelSegmentation({
});
};

const params = new URLSearchParams(window.location.search);
const showAddSegmentation = params.get('disableAddSegmentation') !== 'true';

return (
<>
<div className="ohif-scrollbar flex min-h-0 flex-auto select-none flex-col justify-between overflow-auto">
<SegmentationGroupTable
title={t('Segmentations')}
segmentations={segmentations}
savedStatusStates={savedStatusStates}
disableEditing={configuration.disableEditing}
showAddSegmentation={showAddSegmentation}
activeSegmentationId={selectedSegmentationId || ''}
onSegmentationAdd={onSegmentationAdd}
onSegmentationClick={onSegmentationClick}
Expand Down Expand Up @@ -292,6 +427,7 @@ export default function PanelSegmentation({
setFillAlphaInactive={value =>
_setSegmentationConfiguration(selectedSegmentationId, 'fillAlphaInactive', value)
}
CropDisplayAreaService={CropDisplayAreaService}
/>
</div>
</>
Expand Down
Loading