* Set measurement as active

* Add basic dialog component

* Update styles and prop types

* Jump to image

* Update dialog

* Format date

* Update summary to use primary modalities

* delete empty studySummary page

* add missing PropTypes import

* fix for accidental sync'd viewports

* CR Updates

* CR Update avoid cache of measurements

* Use get measurement

* Add comment to edit function

* Remove request and merge modalities from series

* Update dependency: react-cornerstone-viewport@2.3.9

react-cornerstone-viewport
  * @ohif/extension-cornerstone: 2.3.8 → 2.3.9
  * @ohif/extension-dicom-sr: 2.3.8 → 2.3.9

* Update extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js

Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>

* Add debugs

* remove console.debug

* Update frameindex to imageindex

* Use new rcv debounced function

* Fix hovering

* Update dialog text

* Dismiss dialogs on esc

* Update dependency: react-cornerstone-viewport@3.0.0

react-cornerstone-viewport
  * @ohif/extension-cornerstone: 2.3.9 → 3.0.0
  * @ohif/extension-dicom-sr: 2.3.9 → 3.0.0

* OHIF-237

* remove console.debug

* Remove dead code brought in from rebase

* EmptyViewport should not display a message

* Remove more dead code from rebase

* fix variable naming

* remove unused hook

* bump yarn lock

* formatting

* Add arrow dialog

* Filter based on trackedSeries

Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
This commit is contained in:
Igor OctavianoandDanny Brown authored and GitHub committed 2020-07-02 16:53:38 -04:00
1 parent ae05de18af
commit a156eadcb7
34 files changed
+603 -758

No files matched your search

@@ -1,12 +1,20 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { StudySummary, MeasurementTable } from '@ohif/ui';
import { DicomMetadataStore, DICOMSR } from '@ohif/core';
import {
StudySummary,
MeasurementTable,
Dialog,
Input,
useViewportGrid,
} from '@ohif/ui';
import { DicomMetadataStore, DICOMSR, utils } from '@ohif/core';
import { useDebounce } from '@hooks';
import ActionButtons from './ActionButtons';
import { useTrackedMeasurements } from '../../getContextModule';
import createReportAsync from './../../_shared/createReportAsync.js';
const { formatDate } = utils;
const DISPLAY_STUDY_SUMMARY_INITIAL_VALUE = {
key: undefined, //
date: undefined, // '07-Sep-2010',
@@ -15,6 +23,7 @@ const DISPLAY_STUDY_SUMMARY_INITIAL_VALUE = {
};
function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
const [viewportGrid, viewportGridService] = useViewportGrid();
const [measurementChangeTimestamp, setMeasurementsUpdated] = useState(
Date.now().toString()
);
@@ -22,11 +31,8 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
measurementChangeTimestamp,
200
);
const { MeasurementService } = servicesManager.services;
const [
trackedMeasurements,
sendTrackedMeasurementsEvent,
] = useTrackedMeasurements();
const { MeasurementService, UINotificationService, UIDialogService, DisplaySetService } = servicesManager.services;
const [trackedMeasurements, sendTrackedMeasurementsEvent] = useTrackedMeasurements();
const { trackedStudy, trackedSeries } = trackedMeasurements.context;
const [displayStudySummary, setDisplayStudySummary] = useState(
DISPLAY_STUDY_SUMMARY_INITIAL_VALUE
@@ -53,25 +59,37 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
debouncedMeasurementChangeTimestamp,
]);
// ~~ DisplayStudySummary
useEffect(() => {
const updateDisplayStudySummary = async () => {
if (trackedMeasurements.matches('tracking')) {
const StudyInstanceUID = trackedStudy;
const studyMeta = DicomMetadataStore.getStudy(StudyInstanceUID);
const instanceMeta = studyMeta.series[0].instances[0];
const { Modality, StudyDate, StudyDescription } = instanceMeta;
const { StudyDate, StudyDescription } = instanceMeta;
const modalities = new Set();
studyMeta.series.forEach(series => {
if (trackedSeries.includes(series.SeriesInstanceUID)) {
modalities.add(series.instances[0].Modality);
}
});
const modality = Array.from(modalities).join('/');
if (displayStudySummary.key !== StudyInstanceUID) {
setDisplayStudySummary({
key: StudyInstanceUID,
date: StudyDate, // TODO: Format: '07-Sep-2010'
modality: Modality,
modality,
description: StudyDescription,
});
}
} else if (trackedStudy === '' || trackedStudy === undefined) {
setDisplayStudySummary(DISPLAY_STUDY_SUMMARY_INITIAL_VALUE);
}
};
// ~~ DisplayStudySummary
useEffect(() => {
updateDisplayStudySummary();
}, [displayStudySummary.key, trackedMeasurements, trackedStudy]);
// TODO: Better way to consolidated, debounce, check on change?
@@ -129,12 +147,106 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
DICOMSR.downloadReport(trackedMeasurements, dataSource);
}
const jumpToImage = ({ id, isActive }) => {
const measurement = MeasurementService.getMeasurement(id);
const { referenceSeriesUID, SOPInstanceUID } = measurement;
const displaySets = DisplaySetService.getDisplaySetsForSeries(referenceSeriesUID);
const displaySet = displaySets.find(ds => {
return ds.images && ds.images.some(i => i.SOPInstanceUID === SOPInstanceUID)
});
const imageIndex = displaySet.images.map(i => i.SOPInstanceUID).indexOf(SOPInstanceUID);
viewportGridService.setDisplaysetForViewport({
viewportIndex: viewportGrid.activeViewportIndex,
displaySetInstanceUID: displaySet.displaySetInstanceUID,
imageIndex
});
onMeasurementItemClickHandler({ id, isActive });
};
const onMeasurementItemEditHandler = ({ id }) => {
const measurement = MeasurementService.getMeasurement(id);
let dialogId;
const onSubmitHandler = ({ action, value }) => {
switch (action.id) {
case 'save': {
MeasurementService.update(id, {
...measurement,
...value
});
UINotificationService.show({
title: 'Measurements',
message: 'Label updated successfully',
type: 'success'
});
}
}
UIDialogService.dismiss({ id: dialogId });
};
dialogId = UIDialogService.create({
centralize: true,
isDraggable: false,
useLastPosition: false,
showOverlay: true,
content: Dialog,
contentProps: {
title: 'Enter your annotation',
noCloseButton: true,
value: { label: measurement.label || '' },
body: ({ value, setValue }) => {
const onChangeHandler = (event) => {
event.persist();
setValue(value => ({ ...value, label: event.target.value }));
};
const onKeyPressHandler = event => {
if (event.key === 'Enter') {
onSubmitHandler({ value, action: { id: 'save' } });
}
};
return (
<div className="p-4 bg-primary-dark">
<Input
autoFocus
className="mt-2 bg-black border-primary-main"
type="text"
containerClassName="mr-2"
value={value.label}
onChange={onChangeHandler}
onKeyPress={onKeyPressHandler}
/>
</div>
);
},
actions: [
{ id: 'cancel', text: 'Cancel', type: 'secondary' },
{ id: 'save', text: 'Save', type: 'primary' },
],
onSubmit: onSubmitHandler
}
});
};
const onMeasurementItemClickHandler = ({ id, isActive }) => {
if (!isActive) {
const measurements = [...displayMeasurements];
const measurement = measurements.find(m => m.id === id);
measurements.forEach(m => m.isActive = m.id !== id ? false : true);
measurement.isActive = true;
setDisplayMeasurements(measurements);
}
};
return (
<>
<div className="overflow-x-hidden overflow-y-auto invisible-scrollbar">
{displayStudySummary.key && (
<StudySummary
date={displayStudySummary.date}
date={formatDate(displayStudySummary.date)}
modality={displayStudySummary.modality}
description={displayStudySummary.description}
/>
@@ -143,8 +255,8 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
title="Measurements"
amount={displayMeasurements.length}
data={displayMeasurements}
onClick={() => { }}
onEdit={id => alert(`Edit: ${id}`)}
onClick={jumpToImage}
onEdit={onMeasurementItemEditHandler}
/>
</div>
<div className="flex justify-center p-4">
@@ -187,8 +299,8 @@ function _mapMeasurementToDisplay(measurement, index, types) {
const { PixelSpacing, SeriesNumber, InstanceNumber } = instance;
return {
id: index + 1,
label: '(empty)', // 'Label short description',
id: measurement.id,
label: measurement.label || '(empty)',
displayText:
_getDisplayText(
measurement,
@@ -33,7 +33,6 @@ const BaseAnnotationTool = cornerstoneTools.importInternal(
);
// const cine = viewportSpecificData.cine;
// isPlaying = cine.isPlaying === true;
// frameRate = cine.cineFrameRate || frameRate;
@@ -47,7 +46,10 @@ function TrackedCornerstoneViewport({
ToolBarService,
}) {
const [trackedMeasurements] = useTrackedMeasurements();
const [{ activeViewportIndex, viewports }] = useViewportGrid();
const [
{ activeViewportIndex, viewports },
viewportGridService,
] = useViewportGrid();
// viewportIndex, onSubmit
const [viewportDialogState, viewportDialogApi] = useViewportDialog();
const [viewportData, setViewportData] = useState(null);
@@ -159,16 +161,15 @@ function TrackedCornerstoneViewport({
);
}
_getViewportData(dataSource, displaySet).then(viewportData => {
setViewportData({ ...viewportData });
});
}, [
dataSource,
displaySet,
displaySet.StudyInstanceUID,
displaySet.displaySetInstanceUID,
displaySet.frameIndex,
]);
/*
* This grabs `imageIndex from first matching
* We actually want whichever is at our `viewportIndex`
*/
const { imageIndex } = viewports[viewportIndex];
displaySet.imageIndex = imageIndex;
_getViewportData(dataSource, displaySet).then(setViewportData);
}, [dataSource, displaySet, viewports, viewportIndex]);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let childrenWithProps = null;
@@ -268,8 +269,8 @@ function TrackedCornerstoneViewport({
spacing:
PixelSpacing && PixelSpacing.length
? `${PixelSpacing[0].toFixed(2)}mm x ${PixelSpacing[1].toFixed(
2
)}mm`
2
)}mm`
: '',
scanner: ManufacturerModelName || '',
},
@@ -282,6 +283,14 @@ function TrackedCornerstoneViewport({
viewportIndex={viewportIndex}
imageIds={imageIds}
imageIdIndex={currentImageIdIndex}
onNewImageDebounceTime={700}
onNewImageDebounced={({ currentImageIdIndex }) => {
viewportGridService.setDisplaysetForViewport({
viewportIndex: activeViewportIndex,
displaySetInstanceUID: displaySet.displaySetInstanceUID,
imageIndex: currentImageIdIndex,
});
}}
// TODO: ViewportGrid Context?
isActive={true} // todo
isStackPrefetchEnabled={true} // todo
@@ -336,7 +345,7 @@ const _viewportLabels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'];
* @return {Object} CornerstoneTools Stack
*/
function _getCornerstoneStack(displaySet, dataSource) {
const { frameIndex } = displaySet;
const { imageIndex } = displaySet;
// Get stack from Stack Manager
const storedStack = StackManager.findOrCreateStack(displaySet, dataSource);
@@ -344,17 +353,22 @@ function _getCornerstoneStack(displaySet, dataSource) {
// Clone the stack here so we don't mutate it
const stack = Object.assign({}, storedStack);
stack.currentImageIdIndex = frameIndex;
stack.currentImageIdIndex = imageIndex;
return stack;
}
/**
* Builds the viewport data from a datasource and a displayset.
*
* @param {Object} dataSource
* @param {Object} displaySet
* @return {Object} viewport data
*/
async function _getViewportData(dataSource, displaySet) {
let viewportData;
const stack = _getCornerstoneStack(displaySet, dataSource);
viewportData = {
const viewportData = {
StudyInstanceUID: displaySet.StudyInstanceUID,
displaySetInstanceUID: displaySet.displaySetInstanceUID,
stack,
@@ -40,9 +40,9 @@ const ViewportOverlay = ({
{isWwwcActive && (
<div className="flex flex-row">
<span className="mr-1">W:</span>
<span className="font-thin ml-1 mr-2">{windowWidth.toFixed(0)}</span>
<span className="ml-1 mr-2 font-thin">{windowWidth.toFixed(0)}</span>
<span className="mr-1">L:</span>
<span className="font-thin ml-1">{windowCenter.toFixed(0)}</span>
<span className="ml-1 font-thin">{windowCenter.toFixed(0)}</span>
</div>
)}
</div>
@@ -51,15 +51,13 @@ const ViewportOverlay = ({
<div className="flex flex-row">
<span className="mr-1">I:</span>
<span className="font-thin">
{`${instanceNumber}/${stackSize}`}
{`${instanceNumber} ${imageIndex}/${stackSize}`}
</span>
</div>
)}
</div>
<div className={classnames(overlay, bottomRight)}>
</div>
<div className={classnames(overlay, bottomLeft)}>
</div>
<div className={classnames(overlay, bottomRight)}></div>
<div className={classnames(overlay, bottomLeft)}></div>
</div>
);
};