feat(image capture): Enhance SEG and RTSS Support with Dependency Updates and Bug Fixes (#4924)
This commit is contained in:
1 parent
479e280fdf
commit
fc33bd0d59
57 files changed
+1247
-935
No files matched your search
@@ -46,8 +46,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/adapters": "^3.8.5",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"react-color": "^2.19.3"
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/adapters": "^3.8.5",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"react-color": "^2.19.3"
|
||||
}
|
||||
|
||||
@@ -305,9 +305,6 @@ const commandsModule = ({
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
loadSegmentationDisplaySetsForViewport: {
|
||||
commandFn: actions.loadSegmentationDisplaySetsForViewport,
|
||||
},
|
||||
loadSegmentationsForViewport: {
|
||||
commandFn: actions.loadSegmentationsForViewport,
|
||||
},
|
||||
|
||||
@@ -47,7 +47,7 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||
|
||||
// States
|
||||
let selectedSegmentObjectIndex: number = 0;
|
||||
const selectedSegmentObjectIndex: number = 0;
|
||||
const { setPositionPresentation } = usePositionPresentationStore();
|
||||
|
||||
// Hydration means that the SEG is opened and segments are loaded into the
|
||||
@@ -160,7 +160,7 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
displaySetInstanceUID: referencedDisplaySet.displaySetInstanceUID,
|
||||
});
|
||||
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
|
||||
viewportId,
|
||||
displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID],
|
||||
});
|
||||
|
||||
@@ -46,9 +46,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/tools": "^3.8.4",
|
||||
"@cornerstonejs/adapters": "^3.8.5",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@cornerstonejs/tools": "^3.8.5",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
@@ -42,8 +42,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/tools": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@cornerstonejs/tools": "^3.8.5",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.8.4",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.8.5",
|
||||
"@ohif/core": "3.10.0-beta.148",
|
||||
"@ohif/ui": "3.10.0-beta.148",
|
||||
"dcmjs": "*",
|
||||
@@ -51,12 +51,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.8.4",
|
||||
"@cornerstonejs/ai": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.8.4",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.8.4",
|
||||
"@cornerstonejs/tools": "^3.8.4",
|
||||
"@cornerstonejs/adapters": "^3.8.5",
|
||||
"@cornerstonejs/ai": "^3.8.5",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.8.5",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.8.5",
|
||||
"@cornerstonejs/tools": "^3.8.5",
|
||||
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
|
||||
@@ -35,6 +35,7 @@ import { toolNames } from './initCornerstoneTools';
|
||||
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
|
||||
import { updateSegmentBidirectionalStats } from './utils/updateSegmentationStats';
|
||||
import { generateSegmentationCSVReport } from './utils/generateSegmentationCSVReport';
|
||||
import { getUpdatedViewportsForSegmentation } from './utils/hydrationUtils';
|
||||
|
||||
const { DefaultHistoryMemo } = csUtils.HistoryMemo;
|
||||
const toggleSyncFunctions = {
|
||||
@@ -495,7 +496,6 @@ function commandsModule({
|
||||
|
||||
downloadCSVSegmentationReport: ({ segmentationId }) => {
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
const cachedStats = segmentation.cachedStats;
|
||||
|
||||
const { representationData } = segmentation;
|
||||
const { Labelmap } = representationData;
|
||||
@@ -1021,6 +1021,10 @@ function commandsModule({
|
||||
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||
|
||||
if (!toolGroup.hasTool(toolName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prevConfig = toolGroup?.getToolConfiguration(toolName);
|
||||
toolGroup?.setToolConfiguration(
|
||||
toolName,
|
||||
@@ -1539,7 +1543,8 @@ function commandsModule({
|
||||
DefaultHistoryMemo.redo();
|
||||
},
|
||||
toggleSegmentPreviewEdit: ({ toggle }) => {
|
||||
const labelmapTools = getLabelmapTools({ toolGroupService });
|
||||
let labelmapTools = getLabelmapTools({ toolGroupService });
|
||||
labelmapTools = labelmapTools.filter(tool => !tool.toolName.includes('Eraser'));
|
||||
labelmapTools.forEach(tool => {
|
||||
tool.configuration = {
|
||||
...tool.configuration,
|
||||
@@ -1562,7 +1567,8 @@ function commandsModule({
|
||||
});
|
||||
},
|
||||
toggleUseCenterSegmentIndex: ({ toggle }) => {
|
||||
const labelmapTools = getLabelmapTools({ toolGroupService });
|
||||
let labelmapTools = getLabelmapTools({ toolGroupService });
|
||||
labelmapTools = labelmapTools.filter(tool => !tool.toolName.includes('Eraser'));
|
||||
labelmapTools.forEach(tool => {
|
||||
tool.configuration = {
|
||||
...tool.configuration,
|
||||
@@ -1707,6 +1713,20 @@ function commandsModule({
|
||||
const activeSegmentation = segmentationService.getActiveSegmentation(activeViewportId);
|
||||
segmentationService.addSegment(activeSegmentation.segmentationId);
|
||||
},
|
||||
loadSegmentationDisplaySetsForViewport: ({ viewportId, displaySetInstanceUIDs }) => {
|
||||
const updatedViewports = getUpdatedViewportsForSegmentation({
|
||||
viewportId,
|
||||
servicesManager,
|
||||
displaySetInstanceUIDs,
|
||||
});
|
||||
|
||||
updatedViewports.forEach(viewport => {
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
viewportId: viewport.viewportId,
|
||||
displaySetInstanceUIDs: viewport.displaySetInstanceUIDs,
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
@@ -1985,6 +2005,7 @@ function commandsModule({
|
||||
increaseBrushSize: actions.increaseBrushSize,
|
||||
decreaseBrushSize: actions.decreaseBrushSize,
|
||||
addNewSegment: actions.addNewSegment,
|
||||
loadSegmentationDisplaySetsForViewport: actions.loadSegmentationDisplaySetsForViewport,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -303,7 +303,7 @@ function DicomUploadProgress({
|
||||
<span
|
||||
className={classNames(
|
||||
NO_WRAP_ELLIPSIS_CLASS_NAMES,
|
||||
'text-primary-active hover:text-primary-light active:text-aqua-pale ml-auto cursor-pointer'
|
||||
'text-primary hover:text-primary-light active:text-aqua-pale ml-auto cursor-pointer'
|
||||
)}
|
||||
onClick={cancelAllUploads}
|
||||
>
|
||||
|
||||
@@ -89,7 +89,7 @@ const DicomUploadProgressItem = memo(
|
||||
)}
|
||||
<div className="ml-auto flex cursor-pointer">
|
||||
<Icons.Close
|
||||
className="text-primary-active self-center"
|
||||
className="text-primary self-center"
|
||||
onClick={cancelUpload}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -82,6 +82,7 @@ const stackRetrieveOptions = {
|
||||
},
|
||||
};
|
||||
|
||||
const unsubscriptions = [];
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -142,6 +143,8 @@ const cornerstoneExtension: Types.Extensions.Extension = {
|
||||
useToggleOneUpViewportGridStore.getState().clearToggleOneUpViewportGridStore();
|
||||
useSegmentationPresentationStore.getState().clearSegmentationPresentationStore();
|
||||
segmentationService.removeAllSegmentations();
|
||||
|
||||
unsubscriptions.forEach(unsubscribe => unsubscribe());
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -149,7 +152,7 @@ const cornerstoneExtension: Types.Extensions.Extension = {
|
||||
*
|
||||
* @param configuration.csToolsConfig - Passed directly to `initCornerstoneTools`
|
||||
*/
|
||||
preRegistration: function (props: Types.Extensions.ExtensionParams): Promise<void> {
|
||||
preRegistration: async function (props: Types.Extensions.ExtensionParams): Promise<void> {
|
||||
const { servicesManager, serviceProvidersManager } = props;
|
||||
servicesManager.registerService(CornerstoneViewportService.REGISTRATION);
|
||||
servicesManager.registerService(ToolGroupService.REGISTRATION);
|
||||
@@ -167,7 +170,13 @@ const cornerstoneExtension: Types.Extensions.Extension = {
|
||||
const { syncGroupService } = servicesManager.services;
|
||||
syncGroupService.registerCustomSynchronizer('frameview', createFrameViewSynchronizer);
|
||||
|
||||
return init.call(this, props);
|
||||
const initResult = await init.call(this, props);
|
||||
|
||||
unsubscriptions.push(...initResult.unsubscriptions);
|
||||
|
||||
return {
|
||||
...initResult,
|
||||
};
|
||||
},
|
||||
getToolbarModule,
|
||||
getHangingProtocolModule,
|
||||
|
||||
@@ -193,13 +193,14 @@ export default async function init({
|
||||
});
|
||||
|
||||
// Setup segmentation event handlers
|
||||
setupSegmentationDataModifiedHandler({
|
||||
segmentationService,
|
||||
customizationService,
|
||||
commandsManager,
|
||||
});
|
||||
const { unsubscribe: unsubscribeSegmentationDataModifiedHandler } =
|
||||
setupSegmentationDataModifiedHandler({
|
||||
segmentationService,
|
||||
customizationService,
|
||||
commandsManager,
|
||||
});
|
||||
|
||||
setupSegmentationModifiedHandler({
|
||||
const { unsubscribe: unsubscribeSegmentationModifiedHandler } = setupSegmentationModifiedHandler({
|
||||
segmentationService,
|
||||
});
|
||||
|
||||
@@ -299,6 +300,13 @@ export default async function init({
|
||||
|
||||
// Call this function when initializing
|
||||
initializeWebWorkerProgressHandler(servicesManager.services.uiNotificationService);
|
||||
|
||||
const unsubscriptions = [
|
||||
unsubscribeSegmentationDataModifiedHandler,
|
||||
unsubscribeSegmentationModifiedHandler,
|
||||
];
|
||||
|
||||
return { unsubscriptions };
|
||||
}
|
||||
|
||||
function initializeWebWorkerProgressHandler(uiNotificationService) {
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import { SegmentationTable } from '@ohif/ui-next';
|
||||
import { useActiveViewportSegmentationRepresentations } from '../hooks/useActiveViewportSegmentationRepresentations';
|
||||
import { metaData } from '@cornerstonejs/core';
|
||||
import { useSystem } from '@ohif/core/src';
|
||||
|
||||
export default function PanelSegmentation({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
children,
|
||||
}: withAppTypes) {
|
||||
export default function PanelSegmentation({ children }: withAppTypes) {
|
||||
const { commandsManager, servicesManager } = useSystem();
|
||||
const { customizationService, displaySetService } = servicesManager.services;
|
||||
|
||||
const { segmentationsWithRepresentations, disabled } =
|
||||
|
||||
@@ -427,6 +427,8 @@ class SegmentationService extends PubSubService {
|
||||
...metaData.get('instance', image.referencedImageId),
|
||||
}));
|
||||
|
||||
segDisplaySet.imageIds = derivedImageIds;
|
||||
|
||||
// We should parse the segmentation as separate slices to support overlapping segments.
|
||||
// This parsing should occur in the CornerstoneJS library adapters.
|
||||
// For now, we use the volume returned from the library and chop it here.
|
||||
@@ -513,7 +515,7 @@ class SegmentationService extends PubSubService {
|
||||
type: LABELMAP,
|
||||
data: {
|
||||
imageIds: derivedImageIds,
|
||||
referencedVolumeId: this._getVolumeIdForDisplaySet(referencedDisplaySet),
|
||||
// referencedVolumeId: this._getVolumeIdForDisplaySet(referencedDisplaySet),
|
||||
referencedImageIds: imageIds as string[],
|
||||
},
|
||||
},
|
||||
@@ -566,9 +568,10 @@ class SegmentationService extends PubSubService {
|
||||
|
||||
const referencedImageIds = referencedDisplaySet.imageIds;
|
||||
// find the first image id that contains a referenced SOP instance UID
|
||||
const firstSegmentedSliceImageId = referencedImageIds.find(imageId =>
|
||||
referencedImageIdsWithGeometry.some(referencedId => imageId.includes(referencedId))
|
||||
);
|
||||
const firstSegmentedSliceImageId =
|
||||
referencedImageIds?.find(imageId =>
|
||||
referencedImageIdsWithGeometry.some(referencedId => imageId.includes(referencedId))
|
||||
) || null;
|
||||
|
||||
rtDisplaySet.firstSegmentedSliceImageId = firstSegmentedSliceImageId;
|
||||
// Map ROI contours to RT Struct Data
|
||||
|
||||
@@ -742,6 +742,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
// If you call loadStudyMetadata and it's not in the DicomMetadataStore cache, it should fire
|
||||
// a request through the data source?
|
||||
// (This call may or may not create sub-requests for series metadata)
|
||||
const { displaySetService } = this.servicesManager.services;
|
||||
const volumeInputArray = [];
|
||||
const displaySetOptionsArray = viewportInfo.getDisplaySetOptions();
|
||||
const { hangingProtocolService } = this.servicesManager.services;
|
||||
@@ -750,7 +751,13 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
const displaySetInstanceUIDs = [];
|
||||
|
||||
for (const [index, data] of viewportData.data.entries()) {
|
||||
const { volume, imageIds, displaySetInstanceUID } = data;
|
||||
const { imageIds, displaySetInstanceUID } = data;
|
||||
let volume = data.volume;
|
||||
|
||||
if (!volume) {
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
volume = csToolsUtils.getOrCreateImageVolume(displaySet.images.map(image => image.imageId));
|
||||
}
|
||||
|
||||
displaySetInstanceUIDs.push(displaySetInstanceUID);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import html2canvas from 'html2canvas';
|
||||
import { getEnabledElement, StackViewport, BaseVolumeViewport } from '@cornerstonejs/core';
|
||||
import { ToolGroupManager } from '@cornerstonejs/tools';
|
||||
import { ToolGroupManager, segmentation, Enums } from '@cornerstonejs/tools';
|
||||
import { getEnabledElement as OHIFgetEnabledElement } from '../state';
|
||||
import { useSystem } from '@ohif/core/src';
|
||||
|
||||
@@ -103,6 +103,9 @@ const CornerstoneViewportDownloadForm = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const segmentationRepresentations =
|
||||
segmentation.state.getViewportSegmentationRepresentations(activeViewportId);
|
||||
|
||||
const { viewport } = activeViewportEnabledElement;
|
||||
const downloadViewport = renderingEngine.getViewport(VIEWPORT_ID);
|
||||
|
||||
@@ -113,20 +116,48 @@ const CornerstoneViewportDownloadForm = ({
|
||||
|
||||
await downloadViewport.setStack([imageId]);
|
||||
downloadViewport.setProperties(properties);
|
||||
|
||||
return {
|
||||
width: Math.min(width || DEFAULT_SIZE, MAX_TEXTURE_SIZE),
|
||||
height: Math.min(height || DEFAULT_SIZE, MAX_TEXTURE_SIZE),
|
||||
};
|
||||
} else if (downloadViewport instanceof BaseVolumeViewport) {
|
||||
const volumeIds = viewport.getAllVolumeIds();
|
||||
downloadViewport.setVolumes([{ volumeId: volumeIds[0] }]);
|
||||
|
||||
return {
|
||||
width: Math.min(width || DEFAULT_SIZE, MAX_TEXTURE_SIZE),
|
||||
height: Math.min(height || DEFAULT_SIZE, MAX_TEXTURE_SIZE),
|
||||
};
|
||||
}
|
||||
|
||||
if (segmentationRepresentations.length > 0) {
|
||||
segmentationRepresentations.forEach(segRepresentation => {
|
||||
const { segmentationId, colorLUTIndex, type } = segRepresentation;
|
||||
if (type === Enums.SegmentationRepresentations.Labelmap) {
|
||||
segmentation.addLabelmapRepresentationToViewportMap({
|
||||
[downloadViewport.id]: [
|
||||
{
|
||||
segmentationId,
|
||||
type: Enums.SegmentationRepresentations.Labelmap,
|
||||
config: {
|
||||
colorLUTOrIndex: colorLUTIndex,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (type === Enums.SegmentationRepresentations.Contour) {
|
||||
segmentation.addContourRepresentationToViewportMap({
|
||||
[downloadViewport.id]: [
|
||||
{
|
||||
segmentationId,
|
||||
type: Enums.SegmentationRepresentations.Contour,
|
||||
config: {
|
||||
colorLUTOrIndex: colorLUTIndex,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
width: Math.min(width || DEFAULT_SIZE, MAX_TEXTURE_SIZE),
|
||||
height: Math.min(height || DEFAULT_SIZE, MAX_TEXTURE_SIZE),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error loading image:', error);
|
||||
}
|
||||
@@ -167,6 +198,10 @@ const CornerstoneViewportDownloadForm = ({
|
||||
setTimeout(() => {
|
||||
handleLoadImage(viewportDimensions.width, viewportDimensions.height);
|
||||
handleToggleAnnotations(showAnnotations);
|
||||
// we need a resize here to make suer annotations world to canvas
|
||||
// are properly calculated
|
||||
renderingEngine.resize();
|
||||
renderingEngine.render();
|
||||
}, 100);
|
||||
}
|
||||
}, [viewportDimensions, showAnnotations]);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
function getUpdatedViewportsForSegmentation({
|
||||
viewportId,
|
||||
servicesManager,
|
||||
displaySetInstanceUIDs,
|
||||
}: withAppTypes) {
|
||||
const { hangingProtocolService, viewportGridService } = servicesManager.services;
|
||||
|
||||
const { isHangingProtocolLayout } = viewportGridService.getState();
|
||||
|
||||
const viewport = getTargetViewport({ viewportId, viewportGridService });
|
||||
const targetViewportId = viewport.viewportOptions.viewportId;
|
||||
|
||||
const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
|
||||
targetViewportId,
|
||||
displaySetInstanceUIDs[0],
|
||||
isHangingProtocolLayout
|
||||
);
|
||||
|
||||
return updatedViewports.filter(v => v.viewportOptions?.viewportType !== 'volume3d');
|
||||
}
|
||||
|
||||
const getTargetViewport = ({ viewportId, viewportGridService }) => {
|
||||
const { viewports, activeViewportId } = viewportGridService.getState();
|
||||
const targetViewportId = viewportId || activeViewportId;
|
||||
|
||||
const viewport = viewports.get(targetViewportId);
|
||||
|
||||
return viewport;
|
||||
};
|
||||
|
||||
export { getUpdatedViewportsForSegmentation };
|
||||
@@ -9,10 +9,15 @@ export function setupSegmentationDataModifiedHandler({
|
||||
customizationService,
|
||||
commandsManager,
|
||||
}) {
|
||||
segmentationService.subscribeDebounced(
|
||||
const { unsubscribe } = segmentationService.subscribeDebounced(
|
||||
segmentationService.EVENTS.SEGMENTATION_DATA_MODIFIED,
|
||||
async ({ segmentationId }) => {
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
|
||||
if (!segmentation) {
|
||||
return;
|
||||
}
|
||||
|
||||
const readableText = customizationService.getCustomization('panelSegmentation.readableText');
|
||||
|
||||
// Check for segments with bidirectional measurements and update them
|
||||
@@ -46,17 +51,23 @@ export function setupSegmentationDataModifiedHandler({
|
||||
},
|
||||
1000
|
||||
);
|
||||
|
||||
return { unsubscribe };
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the handler for segmentation modification events
|
||||
*/
|
||||
export function setupSegmentationModifiedHandler({ segmentationService }) {
|
||||
segmentationService.subscribe(
|
||||
const { unsubscribe } = segmentationService.subscribe(
|
||||
segmentationService.EVENTS.SEGMENTATION_MODIFIED,
|
||||
async ({ segmentationId }) => {
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
|
||||
if (!segmentation) {
|
||||
return;
|
||||
}
|
||||
|
||||
const annotationState = cornerstoneTools.annotation.state.getAllAnnotations();
|
||||
const bidirectionalAnnotations = annotationState.filter(
|
||||
annotation =>
|
||||
@@ -90,4 +101,6 @@ export function setupSegmentationModifiedHandler({ segmentationService }) {
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return { unsubscribe };
|
||||
}
|
||||
@@ -113,7 +113,7 @@ function DataSourceConfigurationModalComponent({
|
||||
: 'border border-dashed border-secondary-light';
|
||||
|
||||
const getSelectedItemTextClasses = itemIndex =>
|
||||
itemIndex <= selectedItems.length ? 'text-primary-light' : 'text-primary-active';
|
||||
itemIndex <= selectedItems.length ? 'text-primary-light' : 'text-primary';
|
||||
|
||||
const getErrorComponent = (): ReactElement => {
|
||||
return (
|
||||
|
||||
@@ -20,7 +20,7 @@ function PanelStudyBrowserHeader({
|
||||
<div className={'flex h-[24px] w-full select-none justify-center self-center text-[14px]'}>
|
||||
<div className="flex w-full items-center gap-[10px]">
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="text-primary-active flex items-center space-x-1">
|
||||
<div className="text-primary flex items-center space-x-1">
|
||||
{actionIcons.map((icon: actionIcon, index) =>
|
||||
React.createElement(Icons[icon.iconName] || Icons.MissingIcon, {
|
||||
key: index,
|
||||
|
||||
@@ -44,9 +44,9 @@ function HeaderPatientInfo({ servicesManager, appConfig }: withAppTypes) {
|
||||
onClick={handleOnClick}
|
||||
>
|
||||
{isMixedPatients ? (
|
||||
<Icons.MultiplePatients className="text-primary-active" />
|
||||
<Icons.MultiplePatients className="text-primary" />
|
||||
) : (
|
||||
<Icons.Patient className="text-primary-active" />
|
||||
<Icons.Patient className="text-primary" />
|
||||
)}
|
||||
<div className="flex flex-col justify-center">
|
||||
{expanded ? (
|
||||
@@ -61,12 +61,12 @@ function HeaderPatientInfo({ servicesManager, appConfig }: withAppTypes) {
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-primary-active self-center text-[13px]">
|
||||
<div className="text-primary self-center text-[13px]">
|
||||
{isMixedPatients ? 'Multiple Patients' : 'Patient'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Icons.ArrowLeft className={`text-primary-active ${expanded ? 'rotate-180' : ''}`} />
|
||||
<Icons.ArrowLeft className={`text-primary ${expanded ? 'rotate-180' : ''}`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ function ViewerHeader({ appConfig }: withAppTypes<{ appConfig: AppTypes.Config }
|
||||
)
|
||||
}
|
||||
UndoRedo={
|
||||
<div className="text-primary-active flex cursor-pointer items-center">
|
||||
<div className="text-primary flex cursor-pointer items-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="hover:bg-primary-dark"
|
||||
|
||||
@@ -85,7 +85,7 @@ export function Toolbox({ buttonSectionId, title }: { buttonSectionId: string; t
|
||||
{CustomConfigComponent && (
|
||||
<div className="ml-auto mr-2">
|
||||
<Icons.Settings
|
||||
className="text-primary-active h-4 w-4"
|
||||
className="text-primary h-4 w-4"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
setShowConfig(!showConfig);
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cornerstonejs/core": "^3.8.4",
|
||||
"@cornerstonejs/tools": "^3.8.4",
|
||||
"@cornerstonejs/core": "^3.8.5",
|
||||
"@cornerstonejs/tools": "^3.8.5",
|
||||
"@ohif/core": "3.10.0-beta.148",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.148",
|
||||
"@ohif/extension-default": "3.10.0-beta.148",
|
||||
|
||||
@@ -2,7 +2,8 @@ import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { DicomMetadataStore } from '@ohif/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Input, Label, PanelSection, Separator } from '@ohif/ui-next';
|
||||
import { Button, Input, Label, PanelSection } from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core/src';
|
||||
|
||||
const DEFAULT_MEATADATA = {
|
||||
PatientWeight: null,
|
||||
@@ -37,7 +38,10 @@ const InputRow = ({ children, className, ...props }) => {
|
||||
|
||||
// InputRow sub-components
|
||||
InputRow.Label = ({ children, unit, className, ...props }) => (
|
||||
<Label className={`min-w-32 flex-shrink-0 ${className || ''}`} {...props}>
|
||||
<Label
|
||||
className={`min-w-32 flex-shrink-0 ${className || ''}`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{unit && <span className="text-muted-foreground"> {unit}</span>}
|
||||
</Label>
|
||||
@@ -54,7 +58,8 @@ InputRow.Input = ({ className, ...props }) => (
|
||||
InputRow.Label.displayName = 'InputRow.Label';
|
||||
InputRow.Input.displayName = 'InputRow.Input';
|
||||
|
||||
export default function PanelPetSUV({ servicesManager, commandsManager }: withAppTypes) {
|
||||
export default function PanelPetSUV({ servicesManager }: withAppTypes) {
|
||||
const { commandsManager } = useSystem();
|
||||
const { t } = useTranslation('PanelSUV');
|
||||
const { displaySetService, toolGroupService, toolbarService, hangingProtocolService } =
|
||||
servicesManager.services;
|
||||
@@ -192,7 +197,9 @@ export default function PanelPetSUV({ servicesManager, commandsManager }: withAp
|
||||
<InputRow>
|
||||
<InputRow.Label unit="bq">{t('Total Dose')}</InputRow.Label>
|
||||
<InputRow.Input
|
||||
value={metadata.RadiopharmaceuticalInformationSequence.RadionuclideTotalDose || ''}
|
||||
value={
|
||||
metadata.RadiopharmaceuticalInformationSequence.RadionuclideTotalDose || ''
|
||||
}
|
||||
onChange={e => {
|
||||
handleMetadataChange({
|
||||
RadiopharmaceuticalInformationSequence: {
|
||||
@@ -206,7 +213,9 @@ export default function PanelPetSUV({ servicesManager, commandsManager }: withAp
|
||||
<InputRow>
|
||||
<InputRow.Label unit="s">{t('Half Life')}</InputRow.Label>
|
||||
<InputRow.Input
|
||||
value={metadata.RadiopharmaceuticalInformationSequence.RadionuclideHalfLife || ''}
|
||||
value={
|
||||
metadata.RadiopharmaceuticalInformationSequence.RadionuclideHalfLife || ''
|
||||
}
|
||||
onChange={e => {
|
||||
handleMetadataChange({
|
||||
RadiopharmaceuticalInformationSequence: {
|
||||
@@ -221,7 +230,8 @@ export default function PanelPetSUV({ servicesManager, commandsManager }: withAp
|
||||
<InputRow.Label unit="s">{t('Injection Time')}</InputRow.Label>
|
||||
<InputRow.Input
|
||||
value={
|
||||
metadata.RadiopharmaceuticalInformationSequence.RadiopharmaceuticalStartTime || ''
|
||||
metadata.RadiopharmaceuticalInformationSequence
|
||||
.RadiopharmaceuticalStartTime || ''
|
||||
}
|
||||
onChange={e => {
|
||||
handleMetadataChange({
|
||||
@@ -269,4 +279,4 @@ PanelPetSUV.propTypes = {
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
};
|
||||
+3
-4
@@ -2,11 +2,10 @@ import React, { useEffect } from 'react';
|
||||
import { useActiveViewportSegmentationRepresentations } from '@ohif/extension-cornerstone';
|
||||
import { handleROIThresholding } from '../../utils/handleROIThresholding';
|
||||
import { debounce } from '@ohif/core/src/utils';
|
||||
import { useSystem } from '@ohif/core/src';
|
||||
|
||||
export default function PanelRoiThresholdSegmentation({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
}: withAppTypes) {
|
||||
export default function PanelRoiThresholdSegmentation() {
|
||||
const { commandsManager, servicesManager } = useSystem();
|
||||
const { segmentationService } = servicesManager.services;
|
||||
const { segmentationsWithRepresentations: segmentationsInfo } =
|
||||
useActiveViewportSegmentationRepresentations({ servicesManager });
|
||||
|
||||
@@ -1,62 +1,13 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
PanelSegmentation,
|
||||
useActiveViewportSegmentationRepresentations,
|
||||
} from '@ohif/extension-cornerstone';
|
||||
import { Button, Icons } from '@ohif/ui-next';
|
||||
import { PanelSegmentation } from '@ohif/extension-cornerstone';
|
||||
import PanelROIThresholdExport from './PanelROIThresholdSegmentation/PanelROIThresholdExport';
|
||||
|
||||
export default function PanelTMTV({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
configuration,
|
||||
}: withAppTypes) {
|
||||
export default function PanelTMTV({ configuration }: withAppTypes) {
|
||||
return (
|
||||
<>
|
||||
<PanelSegmentation
|
||||
servicesManager={servicesManager}
|
||||
commandsManager={commandsManager}
|
||||
extensionManager={extensionManager}
|
||||
configuration={configuration}
|
||||
>
|
||||
<ExportCSV
|
||||
servicesManager={servicesManager}
|
||||
commandsManager={commandsManager}
|
||||
/>
|
||||
<PanelSegmentation configuration={configuration}>
|
||||
<PanelROIThresholdExport />
|
||||
</PanelSegmentation>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const ExportCSV = ({ servicesManager, commandsManager }: withAppTypes) => {
|
||||
const { segmentationsWithRepresentations: representations } =
|
||||
useActiveViewportSegmentationRepresentations({ servicesManager });
|
||||
|
||||
const tmtv = representations[0]?.segmentation.cachedStats?.tmtv;
|
||||
|
||||
const segmentations = representations.map(representation => representation.segmentation);
|
||||
|
||||
if (!segmentations.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-8 w-full items-center rounded pr-0.5">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="pl-1.5"
|
||||
onClick={() => {
|
||||
commandsManager.runCommand('exportTMTVReportCSV', {
|
||||
segmentations,
|
||||
tmtv,
|
||||
config: {},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Icons.Download />
|
||||
<span className="pl-1">CSV</span>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -8,7 +8,6 @@ import createAndDownloadTMTVReport from './utils/createAndDownloadTMTVReport';
|
||||
|
||||
import dicomRTAnnotationExport from './utils/dicomRTAnnotationExport/RTStructureSet';
|
||||
|
||||
import { getWebWorkerManager } from '@cornerstonejs/core';
|
||||
import { Enums } from '@cornerstonejs/tools';
|
||||
import { utils } from '@ohif/core';
|
||||
|
||||
@@ -22,32 +21,6 @@ const ROI_THRESHOLD_MANUAL_TOOL_IDS = [
|
||||
'CircleROIStartEndThreshold',
|
||||
];
|
||||
|
||||
const workerManager = getWebWorkerManager();
|
||||
|
||||
// Register the task
|
||||
|
||||
function getVolumesFromSegmentation(segmentationId) {
|
||||
const csSegmentation = csTools.segmentation.state.getSegmentation(segmentationId);
|
||||
const labelmapData = csSegmentation.representationData[
|
||||
SegmentationRepresentations.Labelmap
|
||||
] as csTools.Types.LabelmapToolOperationDataVolume;
|
||||
|
||||
const { volumeId, referencedVolumeId } = labelmapData;
|
||||
const labelmapVolume = cs.cache.getVolume(volumeId);
|
||||
const referencedVolume = cs.cache.getVolume(referencedVolumeId);
|
||||
|
||||
return { labelmapVolume, referencedVolume };
|
||||
}
|
||||
|
||||
function getLabelmapVolumeFromSegmentation(segmentation) {
|
||||
const { representationData } = segmentation;
|
||||
const { volumeId } = representationData[
|
||||
SegmentationRepresentations.Labelmap
|
||||
] as csTools.Types.LabelmapToolOperationDataVolume;
|
||||
|
||||
return cs.cache.getVolume(volumeId);
|
||||
}
|
||||
|
||||
const commandsModule = ({ servicesManager, commandsManager, extensionManager }: withAppTypes) => {
|
||||
const {
|
||||
viewportGridService,
|
||||
@@ -265,8 +238,14 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
|
||||
segmentations,
|
||||
});
|
||||
|
||||
let total_tlg = 0;
|
||||
for (const segmentationId in segReport) {
|
||||
const report = segReport[segmentationId];
|
||||
const tlg = report['namedStats_lesionGlycolysis'];
|
||||
total_tlg += tlg.value;
|
||||
}
|
||||
const additionalReportRows = [
|
||||
{ key: 'Total Lesion Glycolysis', value: { tlg: tlg.toFixed(4) } },
|
||||
{ key: 'Total Lesion Glycolysis', value: { tlg: total_tlg.toFixed(4) } },
|
||||
{ key: 'Threshold Configuration', value: { ...config } },
|
||||
];
|
||||
|
||||
@@ -382,9 +361,8 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
|
||||
continue;
|
||||
}
|
||||
|
||||
const referencedVolumeId = labelmapVolume.referencedVolumeId;
|
||||
|
||||
const referencedVolume = cs.cache.getVolume(referencedVolumeId);
|
||||
const referencedVolume =
|
||||
csTools.utilities.segmentation.getReferenceVolumeForSegmentationVolume(segmentationId);
|
||||
|
||||
if (!referencedVolume) {
|
||||
report[id] = segReport;
|
||||
@@ -476,9 +454,6 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
|
||||
thresholdSegmentationByRectangleROITool: {
|
||||
commandFn: actions.thresholdSegmentationByRectangleROITool,
|
||||
},
|
||||
getTotalLesionGlycolysis: {
|
||||
commandFn: actions.getTotalLesionGlycolysis,
|
||||
},
|
||||
calculateTMTV: {
|
||||
commandFn: actions.calculateTMTV,
|
||||
},
|
||||
|
||||
@@ -5,13 +5,7 @@ import PanelTMTV from './Panels/PanelTMTV';
|
||||
|
||||
function getPanelModule({ commandsManager, extensionManager, servicesManager }) {
|
||||
const wrappedPanelPetSuv = () => {
|
||||
return (
|
||||
<PanelPetSUV
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
extensionManager={extensionManager}
|
||||
/>
|
||||
);
|
||||
return <PanelPetSUV />;
|
||||
};
|
||||
|
||||
const wrappedROIThresholdToolbox = () => {
|
||||
@@ -24,12 +18,7 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager })
|
||||
};
|
||||
|
||||
const wrappedROIThresholdExport = () => {
|
||||
return (
|
||||
<PanelROIThresholdExport
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
/>
|
||||
);
|
||||
return <PanelROIThresholdExport />;
|
||||
};
|
||||
|
||||
const wrappedPanelTMTV = () => {
|
||||
@@ -43,10 +32,6 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager })
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
/>
|
||||
<PanelROIThresholdExport
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user