feat(image capture): Enhance SEG and RTSS Support with Dependency Updates and Bug Fixes (#4924)

This commit is contained in:
Alireza authored and GitHub committed 2025-04-04 12:11:29 -04:00
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],
});
+3 -3
View File
@@ -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"
}
}
+7 -7
View File
@@ -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",
+24 -3
View File
@@ -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>
+11 -2
View File
@@ -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,
+14 -6
View File
@@ -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"
+1 -1
View File
@@ -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);
+2 -2
View File
@@ -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",
+17 -7
View File
@@ -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,
};
};
@@ -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 });
+5 -54
View File
@@ -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>
);
};
+9 -34
View File
@@ -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,
},
+2 -17
View File
@@ -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}
/>
</>
);
};