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: group measurements by study #4617

5 changes: 2 additions & 3 deletions extensions/cornerstone-dicom-sr/src/commandsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@ const _generateReport = (measurementData, additionalFindingTypes, options = {})
};

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

const actions = {
changeColorMeasurement: ({ uid }) => {
Expand Down
91 changes: 61 additions & 30 deletions extensions/cornerstone/src/panels/PanelMeasurement.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useRef } from 'react';
import { utils } from '@ohif/core';
import { MeasurementTable } from '@ohif/ui-next';
import debounce from 'lodash.debounce';
import { useMeasurements } from '../hooks/useMeasurements';

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

export type withAppAndFilters = withAppTypes & {
measurementFilters: Record<string, (item) => boolean>;
groupingFunction: (groupedMeasurements: Map<string, object[]>, item) => Map<string, object[]>;
title: string;
};

export default function PanelMeasurementTable({
servicesManager,
commandsManager,
customHeader,
measurementFilters = { measurementFilter: filterAny },
pedrokohler marked this conversation as resolved.
Show resolved Hide resolved
groupingFunction,
title,
}: withAppAndFilters): React.ReactNode {
const measurementsPanelRef = useRef(null);

const { measurementService, customizationService } = servicesManager.services;
const { measurementService, displaySetService } = servicesManager.services;

const displayMeasurements = useMeasurements(servicesManager, {
measurementFilter: measurementFilters.measurementFilter.bind(measurementFilters),
Expand Down Expand Up @@ -48,12 +52,29 @@ export default function PanelMeasurementTable({

const additionalFilter = filterAdditionalFinding(measurementService);

const { measurementFilter: trackedFilter } = measurementFilters;
const measurements = displayMeasurements.filter(
item => !additionalFilter(item) && trackedFilter(item)
);
const { measurementFilter } = measurementFilters;
const defaultGroupingFunction = (groupedMeasurements, item) => {
const displaySet = displaySetService.getDisplaySetByUID(item.displaySetInstanceUID);
const key = displaySet.instances[0].StudyDescription;
pedrokohler marked this conversation as resolved.
Show resolved Hide resolved

if (!groupedMeasurements.has(key)) {
groupedMeasurements.set(key, [item]);
return groupedMeasurements;
}

const oldValues = groupedMeasurements.get(key);
oldValues.push(item);
return groupedMeasurements;
};

const effectiveGroupingFunction = groupingFunction ?? defaultGroupingFunction;

const measurements = displayMeasurements
.filter(item => !additionalFilter(item) && measurementFilter(item))
.reduce(effectiveGroupingFunction, new Map<string, object[]>());

const additionalFindings = displayMeasurements.filter(
item => additionalFilter(item) && trackedFilter(item)
item => additionalFilter(item) && measurementFilter(item)
);

const onArgs = {
Expand All @@ -69,29 +90,39 @@ export default function PanelMeasurementTable({
<div
className="invisible-scrollbar overflow-y-auto overflow-x-hidden"
ref={measurementsPanelRef}
data-cy={'trackedMeasurements-panel'}
data-cy={'measurements-panel'}
pedrokohler marked this conversation as resolved.
Show resolved Hide resolved
>
<MeasurementTable
key="tracked"
title="Measurements"
data={measurements}
{...onArgs}
// onColor={changeColorMeasurement}
>
<MeasurementTable.Header>
{customHeader && (
<>
{typeof customHeader === 'function'
? customHeader({
additionalFindings,
measurements,
})
: customHeader}
</>
)}
</MeasurementTable.Header>
<MeasurementTable.Body />
</MeasurementTable>
{Array.from(measurements).length === 0 && additionalFindings.length === 0 ? (
<div className="text-primary-light mb-1 flex flex-1 items-center px-2 py-2 text-base">
No measurements
</div>
) : (
<></>
)}
{Array.from(measurements).map(([key, value]) => {
return (
<MeasurementTable
key={`${key}`}
title={title ? title : `Measurements for ${key}`}
data={value}
{...onArgs}
>
<MeasurementTable.Header>
{customHeader && (
<>
{typeof customHeader === 'function'
? customHeader({
additionalFindings,
measurements,
})
: customHeader}
</>
)}
</MeasurementTable.Header>
<MeasurementTable.Body />
</MeasurementTable>
);
})}
{additionalFindings.length > 0 && (
pedrokohler marked this conversation as resolved.
Show resolved Hide resolved
<MeasurementTable
key="additional"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { useEffect, useState } from 'react';
import { DicomMetadataStore, utils } from '@ohif/core';
import { utils } from '@ohif/core';
import { useViewportGrid } from '@ohif/ui';
import { Button, Icons } from '@ohif/ui-next';
import { PanelMeasurement, StudySummaryFromMetadata } from '@ohif/extension-cornerstone';
import { useTrackedMeasurements } from '../getContextModule';
import { useTranslation } from 'react-i18next';

const { filterAny, filterNone, filterNot, filterTracked } = utils.MeasurementFilters;
const { filterAny, filterNot, filterTracked } = utils.MeasurementFilters;

function PanelMeasurementTableTracking({
servicesManager,
Expand All @@ -28,7 +28,7 @@ function PanelMeasurementTableTracking({
});

useEffect(() => {
let updatedMeasurementFilters = { ...measurementFilters };
const updatedMeasurementFilters = { ...measurementFilters };
if (trackedMeasurements.matches('tracking') && trackedStudy) {
updatedMeasurementFilters.measurementFilter = filterTracked(trackedStudy, trackedSeries);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you fix the measurementFilter to be just a single filter, and make it independent of the tracked study.

} else {
Expand All @@ -53,6 +53,7 @@ function PanelMeasurementTableTracking({
extensionManager={extensionManager}
commandsManager={commandsManager}
measurementFilters={measurementFilters}
title="Measurements"
Copy link
Contributor

Choose a reason for hiding this comment

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

The grouping function should get passed in here to PanelMeasurements, and should be a grouping function that groups things into "Tracked Measurements" with a group header being the study header, and the body being the default PanelMeasurements sub-grouping.

Copy link
Contributor

Choose a reason for hiding this comment

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

Probably the default grouping function for PanelMeasurements is fine, just passing in the top level selector which selects only tracked measurements, and that should ONLY use series instance UID so that when we add multiple studies to PanelMeasurement it just automatically creates multiple sub-groups.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Filtering measurements to include only tracked ones is understandable—that makes sense.

However, I don’t understand why, or even what it means, to 'group' measurements by 'tracked measurements.' Could you clarify?

customHeader={({ additionalFindings, measurements }) => {
const disabled = additionalFindings.length === 0 && measurements.length === 0;

Expand Down
2 changes: 1 addition & 1 deletion platform/app/cypress/support/aliases.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function initCommonElementsAliases(skipMarkers) {
cy.get('[data-cy="trackedMeasurements-btn"]').click();

// TODO: Panels are not in DOM when closed, move this somewhere else
cy.get('[data-cy="trackedMeasurements-panel"]').as('measurementsPanel');
cy.get('[data-cy="measurements-panel"]').as('measurementsPanel');
cy.get('[data-cy="panelSegmentation-btn"]').as('segmentationPanel');
cy.get('[data-cy="studyBrowser-panel"]').as('seriesPanel');
cy.get('[data-cy="viewport-overlay-top-right"]').as('viewportInfoTopRight');
Expand Down