Skip to content
Open
12 changes: 12 additions & 0 deletions extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,18 @@ const OHIFCornerstoneViewport = React.memo(
viewportOptions.needsRerendering = false;
}

const waitForViewportInfo = async () => {
let info = cornerstoneViewportService.getViewportInfo(viewportId);
let attempts = 0;
while (!info && attempts < 20) {
await new Promise(resolve => setTimeout(resolve, 0));
info = cornerstoneViewportService.getViewportInfo(viewportId);
attempts++;
}
};

await waitForViewportInfo();

cornerstoneViewportService.setViewportData(
viewportId,
viewportData,
Expand Down
35 changes: 35 additions & 0 deletions extensions/cornerstone/src/commandsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
import { isMeasurementWithinViewport } from './utils/isMeasurementWithinViewport';
import { getCenterExtent } from './utils/getCenterExtent';
import { EasingFunctionEnum } from './utils/transitions';
import { collectActiveStudyMetadata } from '../../default/src/utils/collectDicomMetadata';

const { DefaultHistoryMemo } = csUtils.HistoryMemo;
const toggleSyncFunctions = {
Expand Down Expand Up @@ -714,6 +715,39 @@ function commandsModule({
generateSegmentationCSVReport(segmentation, additionalInfo);
},

sendActiveStudyMetadata: async () => {
const json = await collectActiveStudyMetadata(servicesManager as any);
const url = await callInputDialog({
uiDialogService,
title: i18n.t('Tools:Send Metadata JSON'),
placeholder: 'https://webhook.site/65ae2152-128f-4e7f-8579-d36cbe3152eb',
defaultValue: '',
});

if (!url) {
return;
}

try {
await fetch(url as any, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(json),
});
uiNotificationService.show({
title: 'Metadata',
message: 'Metadata JSON sent',
type: 'success',
});
} catch (e) {
uiNotificationService.show({
title: 'Metadata',
message: 'Failed to send metadata JSON',
type: 'error',
});
}
},

// Retrieve value commands
getActiveViewportEnabledElement: _getActiveViewportEnabledElement,

Expand Down Expand Up @@ -2508,6 +2542,7 @@ function commandsModule({
interpolateLabelmap: actions.interpolateLabelmap,
runSegmentBidirectional: actions.runSegmentBidirectional,
downloadCSVSegmentationReport: actions.downloadCSVSegmentationReport,
sendActiveStudyMetadata: { commandFn: actions.sendActiveStudyMetadata },
toggleSegmentPreviewEdit: actions.toggleSegmentPreviewEdit,
toggleSegmentSelect: actions.toggleSegmentSelect,
acceptPreview: actions.acceptPreview,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,15 +415,19 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
// same viewport/element and just change the viewportData for it (drag and drop etc.)
// the disableElement storePresentation handle would not be called in this case
// and we would lose the presentation.
this.storePresentation({ viewportId: viewportInfo.getViewportId() });
if (viewportInfo) {
this.storePresentation({ viewportId: viewportInfo.getViewportId() });
}

// Todo: i don't like this here, move it
this.servicesManager.services.segmentationService.clearSegmentationRepresentations(
viewportInfo.getViewportId()
);
if (viewportInfo) {
this.servicesManager.services.segmentationService.clearSegmentationRepresentations(
viewportInfo.getViewportId()
);
}

if (!viewportInfo) {
throw new Error('element is not enabled for the given viewportId');
return;
}

// override the viewportOptions and displaySetOptions with the public ones
Expand Down Expand Up @@ -714,7 +718,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
// is being used to navigate to the initial view position for measurement
// navigation and other navigation forcing specific views.
let initialImageIndexToUse =
presentations?.positionPresentation?.initialImageIndex ?? <number>initialImageIndex;
presentations?.positionPresentation?.initialImageIndex ?? (initialImageIndex as number);

const { rotation, flipHorizontal, displayArea } = viewportInfo.getViewportOptions();

Expand Down Expand Up @@ -1330,7 +1334,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
return;
}

const { segmentationService } = this.servicesManager.services;
const { segmentationService, displaySetService } = this.servicesManager.services;

segmentationPresentation.forEach((presentationItem: SegmentationPresentationItem) => {
const { segmentationId, type, hydrated } = presentationItem;
Expand All @@ -1349,6 +1353,21 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
})();

if (hydrated) {
const existingSegmentation = segmentationService.getSegmentation(segmentationId);
if (!existingSegmentation) {
const segDisplaySet = displaySetService.getDisplaySetByUID(segmentationId);
type LoadableDisplaySet = { load?: (args?: unknown) => Promise<unknown> };
const segLoader = (segDisplaySet as LoadableDisplaySet)?.load;
if (typeof segLoader === 'function') {
segLoader({}).then(() => {
segmentationService.addSegmentationRepresentation(viewport.id, {
segmentationId,
type: representationType,
});
});
}
return;
}
segmentationService.addSegmentationRepresentation(viewport.id, {
segmentationId,
type: representationType,
Expand Down
34 changes: 32 additions & 2 deletions extensions/default/src/Panels/StudyBrowser/PanelStudyBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { PanelStudyBrowserHeader } from './PanelStudyBrowserHeader';
import { defaultActionIcons } from './constants';
import MoreDropdownMenu from '../../Components/MoreDropdownMenu';
import { CallbackCustomization } from 'platform/core/src/types';

import { collectActiveStudyMetadata } from '../../utils/collectDicomMetadata';
const { sortStudyInstances, formatDate, createStudyBrowserTabs } = utils;

const thumbnailNoImageModalities = ['SR', 'SEG', 'RTSTRUCT', 'RTPLAN', 'RTDOSE', 'DOC', 'PMAP'];
Expand Down Expand Up @@ -74,6 +74,17 @@ function PanelStudyBrowser({

const mapDisplaySetsWithState = customMapDisplaySets || _mapDisplaySets;

const goToSegmentation = useCallback(() => {
const { StudyInstanceUID } = displaySetService.activeDisplaySets[0] || {};
if (!StudyInstanceUID) {
return;
}
const query = new URLSearchParams();
query.append('StudyInstanceUIDs', StudyInstanceUID);
query.append('datasources', 'dicomlocal');
navigate(`/segmentation/dicomlocal?${query.toString()}`);
}, [displaySetService, navigate]);

const onDoubleClickThumbnailHandler = useCallback(
async displaySetInstanceUID => {
const customHandler = customizationService.getCustomization(
Expand Down Expand Up @@ -101,9 +112,21 @@ function PanelStudyBrowser({
servicesManager,
isHangingProtocolLayout,
customizationService,
extensionManager,
onDoubleClickThumbnailHandlerCallBack,
]
);

const downloadMetadata = useCallback(async () => {
const json = await collectActiveStudyMetadata(servicesManager);
const blob = new Blob([JSON.stringify(json, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'metadata.json';
a.click();
URL.revokeObjectURL(url);
}, [servicesManager]);
// ~~ studyDisplayList
useEffect(() => {
// Fetch all studies for the patient in each primary study
Expand All @@ -114,7 +137,6 @@ function PanelStudyBrowser({
}

fetchedStudiesRef.current.add(StudyInstanceUID);

// current study qido
const qidoForStudyUID = await dataSource.query.studies.search({
studyInstanceUid: StudyInstanceUID,
Expand Down Expand Up @@ -412,6 +434,14 @@ function PanelStudyBrowser({
className="bg-black"
thickness="2px"
/>
<div className="p-2">
<button
onClick={downloadMetadata}
className="bg-primary rounded px-3 py-1 text-white"
>
Download metadata JSON
</button>
</div>
</>

<StudyBrowser
Expand Down
4 changes: 2 additions & 2 deletions extensions/default/src/commandsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ const commandsModule = ({
*/
addDisplaySetAsLayer: ({ viewportId, displaySetInstanceUID, removeFirst = false }) => {
if (!viewportId) {
const { activeViewportId } = servicesManager.services.viewportGridService.getState();
viewportId = activeViewportId;
const { activeViewportId } = servicesManager.services.viewportGridService.getState();
viewportId = activeViewportId;
}

if (!viewportId || !displaySetInstanceUID) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ export default {
({ activeViewportId, servicesManager, commandsManager, isHangingProtocolLayout }) =>
async displaySetInstanceUID => {
const { hangingProtocolService, uiNotificationService } = servicesManager.services;
const { displaySetService } = servicesManager.services;
let updatedViewports = [];
const viewportId = activeViewportId;

console.log('double click', { displaySetInstanceUID });
try {
updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
viewportId,
Expand All @@ -71,6 +72,31 @@ export default {
type: 'error',
duration: 3000,
});
return;
}

const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
console.log({
segUID: displaySetInstanceUID,
referencedUID: displaySet?.referencedDisplaySetInstanceUID,
});
if (displaySet?.Modality === 'SEG') {
try {
await commandsManager.run('hydrateSecondaryDisplaySet', {
displaySet,
viewportId,
});
} catch (error) {
console.log('error', error);
console.warn(error);
uiNotificationService.show({
title: i18n.t('StudyBrowser:SEG Hydration'),
message: i18n.t('StudyBrowser:The segmentation could not be loaded.'),
type: 'error',
duration: 3000,
});
return;
}
}

commandsManager.run('setDisplaySetsForViewports', {
Expand Down
2 changes: 2 additions & 0 deletions extensions/default/src/getCustomizationModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import reportDialogCustomization from './customizations/reportDialogCustomizatio
import hotkeyBindingsCustomization from './customizations/hotkeyBindingsCustomization';
import onboardingCustomization from './customizations/onboardingCustomization';
import instanceSortingCriteriaCustomization from './customizations/instanceSortingCriteriaCustomization';
import missingReferenceDisplaySetHandlerCustomization from './customizations/missingReferenceDisplaySetHandler';
/**
*
* Note: this is an example of how the customization module can be used
Expand Down Expand Up @@ -71,6 +72,7 @@ export default function getCustomizationModule({ servicesManager, extensionManag
...hotkeyBindingsCustomization,
...onboardingCustomization,
...instanceSortingCriteriaCustomization,
...missingReferenceDisplaySetHandlerCustomization,
},
},
];
Expand Down
Loading