Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Start using group filtering to define measurements table layout #4501

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
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
37 changes: 34 additions & 3 deletions extensions/cornerstone-dicom-sr/src/commandsModule.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
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';

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 @@ -42,8 +42,29 @@ const _generateReport = (measurementData, additionalFindingTypes, options = {})

const commandsModule = (props: withAppTypes) => {
const { servicesManager } = props;
const { customizationService } = 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 All @@ -61,6 +82,13 @@ const commandsModule = (props: withAppTypes) => {
window.location.assign(objectUrl);
},

/**
* Download the CSV report for the measurements.
*/
downloadCSVMeasurementsReport: ({ measurementFilter }) => {
wayfarer3130 marked this conversation as resolved.
Show resolved Hide resolved
utils.downloadCSVReport(measurementService.getMeasurements(measurementFilter));
},

/**
*
* @param measurementData An array of measurements from the measurements service
Expand Down Expand Up @@ -132,6 +160,9 @@ const commandsModule = (props: withAppTypes) => {
storeMeasurements: {
commandFn: actions.storeMeasurements,
},
downloadCSVMeasurementsReport: {
commandFn: actions.downloadCSVMeasurementsReport,
},
};

return {
Expand Down
61 changes: 61 additions & 0 deletions extensions/cornerstone/src/commandsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,49 @@ 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: ({ measurementFilter }) => {
measurementService.clearMeasurements(measurementFilter);
},

// Retrieve value commands
getActiveViewportEnabledElement: _getActiveViewportEnabledElement,

Expand Down Expand Up @@ -1252,6 +1295,24 @@ 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,
},
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 }) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use dcmjs style namings? so StudyInstanceUID for the prop? below you are actually doing const { StudyDate, StudyDescription } = instanceMeta;

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>
);
}
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;
118 changes: 40 additions & 78 deletions extensions/cornerstone/src/panels/PanelMeasurement.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
import React, { useEffect, useRef } from 'react';
import { useViewportGrid } from '@ohif/ui';
import React, { useEffect, useRef, useState } from 'react';
import { utils } from '@ohif/core';
import { MeasurementTable } from '@ohif/ui-next';
import debounce from 'lodash.debounce';
import { useMeasurements } from '../hooks/useMeasurements';
import { showLabelAnnotationPopup, colorPickerDialog } from '@ohif/extension-default';

const { filterAdditionalFinding, filterOr, filterAny } = utils.MeasurementFilters;

export type withAppAndFilters = withAppTypes & {
measurementFilters: Record<string, (item) => boolean>;
};

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

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

const displayMeasurements = useMeasurements(servicesManager, {
measurementFilter,
measurementFilter: filterAny,
});

useEffect(() => {
Expand All @@ -27,75 +32,39 @@ 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, options?) => {
return (uid: string) => {
commandsManager.runCommand(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('run', {
commands: ['jumpToMeasurement', 'renameMeasurement'],
displayMeasurements,
});
const toggleLockMeasurement = bindCommand('toggleLockMeasurement');
wayfarer3130 marked this conversation as resolved.
Show resolved Hide resolved
const toggleVisibilityMeasurement = bindCommand('toggleVisibilityMeasurement');

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

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

const onArgs = {
onClick: jumpToImage,
wayfarer3130 marked this conversation as resolved.
Show resolved Hide resolved
onDelete: removeMeasurement,
onToggleVisibility: toggleVisibilityMeasurement,
onToggleLocked: toggleLockMeasurement,
onRename: renameMeasurement,
};

return (
<>
<div
Expand All @@ -104,13 +73,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 +95,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
Loading