Skip to content

Commit

Permalink
feat: Start using group filtering to define measurements table layout (
Browse files Browse the repository at this point in the history
  • Loading branch information
wayfarer3130 authored Jan 10, 2025
1 parent bc73de9 commit 82440e8
Show file tree
Hide file tree
Showing 16 changed files with 356 additions and 209 deletions.
28 changes: 25 additions & 3 deletions extensions/cornerstone-dicom-sr/src/commandsModule.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { metaData, utilities } from '@cornerstonejs/core';

import OHIF, { DicomMetadataStore } from '@ohif/core';
import OHIF, { DicomMetadataStore, utils } from '@ohif/core';
import dcmjs from 'dcmjs';
import { adaptersSR } from '@cornerstonejs/adapters';
import { showLabelAnnotationPopup, colorPickerDialog } from '@ohif/extension-default';

import getFilteredCornerstoneToolState from './utils/getFilteredCornerstoneToolState';
import hydrateStructuredReport from './utils/hydrateStructuredReport';
Expand All @@ -11,7 +12,6 @@ const { MeasurementReport } = adaptersSR.Cornerstone3D;
const { log } = OHIF;

/**
*
* @param measurementData An array of measurements from the measurements service
* that you wish to serialize.
* @param additionalFindingTypes toolTypes that should be stored with labels as Findings
Expand Down Expand Up @@ -43,8 +43,30 @@ const _generateReport = (measurementData, additionalFindingTypes, options = {})

const commandsModule = (props: withAppTypes) => {
const { servicesManager, extensionManager, commandsManager } = props;
const { customizationService, displaySetService, viewportGridService } = servicesManager.services;
const { customizationService, measurementService, viewportGridService, uiDialogService } =
servicesManager.services;

const actions = {
changeColorMeasurement: ({ uid }) => {
// When this gets supported, it probably belongs in cornerstone, not sr
throw new Error('Unsupported operation: changeColorMeasurement');
// const { color } = measurementService.getMeasurement(uid);
// const rgbaColor = {
// r: color[0],
// g: color[1],
// b: color[2],
// a: color[3] / 255.0,
// };
// colorPickerDialog(uiDialogService, rgbaColor, (newRgbaColor, actionId) => {
// if (actionId === 'cancel') {
// return;
// }

// const color = [newRgbaColor.r, newRgbaColor.g, newRgbaColor.b, newRgbaColor.a * 255.0];
// segmentationService.setSegmentColor(viewportId, segmentationId, segmentIndex, color);
// });
},

/**
*
* @param measurementData An array of measurements from the measurements service
Expand Down
77 changes: 76 additions & 1 deletion extensions/cornerstone/src/commandsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
annotation,
} from '@cornerstonejs/tools';

import { Types as OhifTypes } from '@ohif/core';
import { Types as OhifTypes, utils } from '@ohif/core';
import i18n from '@ohif/i18n';
import {
callLabelAutocompleteDialog,
Expand Down Expand Up @@ -295,6 +295,60 @@ function commandsModule({
measurementService.update(updatedMeasurement.uid, updatedMeasurement, true);
},

/**
* Jumps to the specified (by uid) measurement in the active viewport.
* Also marks any provided display measurements isActive value
*/
jumpToMeasurement: ({ uid, displayMeasurements = [] }) => {
measurementService.jumpToMeasurement(viewportGridService.getActiveViewportId(), uid);
for (const measurement of displayMeasurements) {
measurement.isActive = measurement.uid === uid;
}
},

removeMeasurement: ({ uid }) => {
measurementService.remove(uid);
},

renameMeasurement: ({ uid }) => {
const labelConfig = customizationService.get('measurementLabels');
const measurement = measurementService.getMeasurement(uid);
showLabelAnnotationPopup(measurement, uiDialogService, labelConfig).then(val => {
measurementService.update(
uid,
{
...val,
},
true
);
});
},

toggleLockMeasurement: ({ uid }) => {
measurementService.toggleLockMeasurement(uid);
},

toggleVisibilityMeasurement: ({ uid }) => {
measurementService.toggleVisibilityMeasurement(uid);
},

/**
* Clear the measurements
*/
clearMeasurements: options => {
const { measurementFilter } = options;
measurementService.clearMeasurements(
measurementFilter ? measurementFilter.bind(options) : null
);
},

/**
* Download the CSV report for the measurements.
*/
downloadCSVMeasurementsReport: ({ measurementFilter }) => {
utils.downloadCSVReport(measurementService.getMeasurements(measurementFilter));
},

// Retrieve value commands
getActiveViewportEnabledElement: _getActiveViewportEnabledElement,

Expand Down Expand Up @@ -1281,6 +1335,27 @@ function commandsModule({
updateMeasurement: {
commandFn: actions.updateMeasurement,
},
clearMeasurements: {
commandFn: actions.clearMeasurements,
},
jumpToMeasurement: {
commandFn: actions.jumpToMeasurement,
},
removeMeasurement: {
commandFn: actions.removeMeasurement,
},
renameMeasurement: {
commandFn: actions.renameMeasurement,
},
toggleLockMeasurement: {
commandFn: actions.toggleLockMeasurement,
},
toggleVisibilityMeasurement: {
commandFn: actions.toggleVisibilityMeasurement,
},
downloadCSVMeasurementsReport: {
commandFn: actions.downloadCSVMeasurementsReport,
},
setViewportWindowLevel: {
commandFn: actions.setViewportWindowLevel,
},
Expand Down
25 changes: 25 additions & 0 deletions extensions/cornerstone/src/components/StudySummaryFromMetadata.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { DicomMetadataStore, utils } from '@ohif/core';
import { StudySummary } from '@ohif/ui-next';

const { formatDate } = utils;

export function StudySummaryFromMetadata({ StudyInstanceUID }) {
if (!StudyInstanceUID) {
return null;
}
const studyMeta = DicomMetadataStore.getStudy(StudyInstanceUID);
if (!studyMeta?.series?.length) {
return null;
}

const instanceMeta = studyMeta.series[0].instances[0];
const { StudyDate, StudyDescription } = instanceMeta;

return (
<StudySummary
date={formatDate(StudyDate)}
description={StudyDescription}
></StudySummary>
);
}
4 changes: 2 additions & 2 deletions extensions/cornerstone/src/getPanelModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
import { Toolbox } from '@ohif/ui-next';
import PanelSegmentation from './panels/PanelSegmentation';
import ActiveViewportWindowLevel from './components/ActiveViewportWindowLevel';
import PanelMeasurementTable from './panels/PanelMeasurement';
import PanelMeasurement from './panels/PanelMeasurement';

const getPanelModule = ({ commandsManager, servicesManager, extensionManager }: withAppTypes) => {
const wrappedPanelSegmentation = ({ configuration }) => {
Expand Down Expand Up @@ -59,7 +59,7 @@ const getPanelModule = ({ commandsManager, servicesManager, extensionManager }:

const wrappedPanelMeasurement = ({ configuration }) => {
return (
<PanelMeasurementTable
<PanelMeasurement
commandsManager={commandsManager}
servicesManager={servicesManager}
extensionManager={extensionManager}
Expand Down
5 changes: 1 addition & 4 deletions extensions/cornerstone/src/hooks/useMeasurements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@ export function useMeasurements(servicesManager, { measurementFilter }) {

useEffect(() => {
const updateDisplayMeasurements = () => {
let measurements = measurementService.getMeasurements();
if (measurementFilter) {
measurements = measurements.filter(measurementFilter);
}
let measurements = measurementService.getMeasurements(measurementFilter);
const mappedMeasurements = measurements.map(m =>
mapMeasurementToDisplay(m, displaySetService)
);
Expand Down
2 changes: 2 additions & 0 deletions extensions/cornerstone/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import PanelSegmentation from './panels/PanelSegmentation';
import PanelMeasurement from './panels/PanelMeasurement';
import DicomUpload from './components/DicomUpload/DicomUpload';
import { useSegmentations } from './hooks/useSegmentations';
import { StudySummaryFromMetadata } from './components/StudySummaryFromMetadata';

const { imageRetrieveMetadataProvider } = cornerstone.utilities;

Expand Down Expand Up @@ -254,5 +255,6 @@ export {
PanelSegmentation,
PanelMeasurement,
DicomUpload,
StudySummaryFromMetadata,
};
export default cornerstoneExtension;
119 changes: 42 additions & 77 deletions extensions/cornerstone/src/panels/PanelMeasurement.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
import React, { useEffect, useRef } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { utils } from '@ohif/core';
import { useViewportGrid } from '@ohif/ui-next';
import { MeasurementTable } from '@ohif/ui-next';
import debounce from 'lodash.debounce';
import { useMeasurements } from '../hooks/useMeasurements';
import { showLabelAnnotationPopup, colorPickerDialog } from '@ohif/extension-default';

export default function PanelMeasurementTable({
const {
filterAdditionalFindings: filterAdditionalFinding,
filterOr,
filterAny,
} = utils.MeasurementFilters;

export type withAppAndFilters = withAppTypes & {
measurementFilter: (item) => boolean;
};

export default function PanelMeasurement({
servicesManager,
commandsManager,
customHeader,
measurementFilter,
}: withAppTypes): React.ReactNode {
measurementFilter = filterAny,
}: withAppAndFilters): React.ReactNode {
const measurementsPanelRef = useRef(null);

const [viewportGrid] = useViewportGrid();
const { measurementService, customizationService, uiDialogService } = servicesManager.services;
const { measurementService } = servicesManager.services;

const displayMeasurements = useMeasurements(servicesManager, {
measurementFilter,
Expand All @@ -27,75 +37,37 @@ export default function PanelMeasurementTable({
}
}, [displayMeasurements.length]);

const onMeasurementItemClickHandler = (uid: string, isActive: boolean) => {
if (isActive) {
return;
}

const measurements = [...displayMeasurements];
const measurement = measurements.find(m => m.uid === uid);

measurements.forEach(m => (m.isActive = m.uid !== uid ? false : true));
measurement.isActive = true;
};

const jumpToImage = (uid: string) => {
measurementService.jumpToMeasurement(viewportGrid.activeViewportId, uid);
onMeasurementItemClickHandler(uid, true);
};

const removeMeasurement = (uid: string) => {
measurementService.remove(uid);
};

const renameMeasurement = (uid: string) => {
jumpToImage(uid);
const labelConfig = customizationService.get('measurementLabels');
const measurement = measurementService.getMeasurement(uid);
showLabelAnnotationPopup(measurement, uiDialogService, labelConfig).then(val => {
measurementService.update(
uid,
{
...val,
},
true
);
});
};

const changeColorMeasurement = (uid: string) => {
const { color } = measurementService.getMeasurement(uid);
const rgbaColor = {
r: color[0],
g: color[1],
b: color[2],
a: color[3] / 255.0,
const bindCommand = (name: string | string[], options?) => {
return (uid: string) => {
commandsManager.run(name, { ...options, uid });
};
colorPickerDialog(uiDialogService, rgbaColor, (newRgbaColor, actionId) => {
if (actionId === 'cancel') {
return;
}

const color = [newRgbaColor.r, newRgbaColor.g, newRgbaColor.b, newRgbaColor.a * 255.0];
// segmentationService.setSegmentColor(viewportId, segmentationId, segmentIndex, color);
});
};

const toggleLockMeasurement = (uid: string) => {
measurementService.toggleLockMeasurement(uid);
};
const jumpToImage = bindCommand('jumpToMeasurement', { displayMeasurements });
const removeMeasurement = bindCommand('removeMeasurement');
const renameMeasurement = bindCommand(['jumpToMeasurement', 'renameMeasurement'], {
displayMeasurements,
});
const toggleLockMeasurement = bindCommand('toggleLockMeasurement');
const toggleVisibilityMeasurement = bindCommand('toggleVisibilityMeasurement');

const toggleVisibilityMeasurement = (uid: string) => {
measurementService.toggleVisibilityMeasurement(uid);
};
const additionalFilter = filterAdditionalFinding(measurementService);

const measurements = displayMeasurements.filter(
dm => dm.measurementType !== measurementService.VALUE_TYPES.POINT && dm.referencedImageId
item => !additionalFilter(item) && measurementFilter(item)
);
const additionalFindings = displayMeasurements.filter(
dm => dm.measurementType === measurementService.VALUE_TYPES.POINT && dm.referencedImageId
item => additionalFilter(item) && measurementFilter(item)
);

const onArgs = {
onClick: jumpToImage,
onDelete: removeMeasurement,
onToggleVisibility: toggleVisibilityMeasurement,
onToggleLocked: toggleLockMeasurement,
onRename: renameMeasurement,
};

return (
<>
<div
Expand All @@ -104,13 +76,10 @@ export default function PanelMeasurementTable({
data-cy={'trackedMeasurements-panel'}
>
<MeasurementTable
key="tracked"
title="Measurements"
data={measurements}
onClick={jumpToImage}
onDelete={removeMeasurement}
onToggleVisibility={toggleVisibilityMeasurement}
onToggleLocked={toggleLockMeasurement}
onRename={renameMeasurement}
{...onArgs}
// onColor={changeColorMeasurement}
>
<MeasurementTable.Header>
Expand All @@ -129,14 +98,10 @@ export default function PanelMeasurementTable({
</MeasurementTable>
{additionalFindings.length > 0 && (
<MeasurementTable
key="additional"
data={additionalFindings}
title="Additional Findings"
onClick={jumpToImage}
onDelete={removeMeasurement}
onToggleVisibility={toggleVisibilityMeasurement}
onToggleLocked={toggleLockMeasurement}
onRename={renameMeasurement}
// onColor={changeColorMeasurement}
{...onArgs}
>
<MeasurementTable.Body />
</MeasurementTable>
Expand Down
Loading

0 comments on commit 82440e8

Please sign in to comment.