OHIF-326, export as CSV. (#1976)
This commit is contained in:
parent
68332701ab
commit
42b02a62d3
@ -33,7 +33,7 @@
|
|||||||
"@ohif/ui": "^0.50.0",
|
"@ohif/ui": "^0.50.0",
|
||||||
"cornerstone-core": "^2.3.0",
|
"cornerstone-core": "^2.3.0",
|
||||||
"cornerstone-math": "^0.1.8",
|
"cornerstone-math": "^0.1.8",
|
||||||
"cornerstone-tools": "4.19.1",
|
"cornerstone-tools": "4.20.1",
|
||||||
"cornerstone-wado-image-loader": "^3.1.2",
|
"cornerstone-wado-image-loader": "^3.1.2",
|
||||||
"dcmjs": "0.16.1",
|
"dcmjs": "0.16.1",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.3",
|
||||||
|
|||||||
@ -33,7 +33,9 @@ const EllipticalRoi = {
|
|||||||
SeriesInstanceUID
|
SeriesInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
const { start, end } = measurementData.handles;
|
const { cachedStats, handles } = measurementData;
|
||||||
|
|
||||||
|
const { start, end } = handles;
|
||||||
|
|
||||||
const halfXLength = Math.abs(start.x - end.x) / 2;
|
const halfXLength = Math.abs(start.x - end.x) / 2;
|
||||||
const halfYLength = Math.abs(start.y - end.y) / 2;
|
const halfYLength = Math.abs(start.y - end.y) / 2;
|
||||||
@ -60,6 +62,20 @@ const EllipticalRoi = {
|
|||||||
points.push({ x: center.x + halfXLength, y: center.y });
|
points.push({ x: center.x + halfXLength, y: center.y });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let meanSUV;
|
||||||
|
let stdDevSUV;
|
||||||
|
|
||||||
|
if (
|
||||||
|
cachedStats &&
|
||||||
|
cachedStats.meanStdDevSUV &&
|
||||||
|
cachedStats.meanStdDevSUV.mean !== 0
|
||||||
|
) {
|
||||||
|
const { meanStdDevSUV } = cachedStats;
|
||||||
|
|
||||||
|
meanSUV = meanStdDevSUV.mean;
|
||||||
|
stdDevSUV = meanStdDevSUV.stdDev;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: measurementData.id,
|
id: measurementData.id,
|
||||||
SOPInstanceUID,
|
SOPInstanceUID,
|
||||||
@ -70,10 +86,11 @@ const EllipticalRoi = {
|
|||||||
label: measurementData.label,
|
label: measurementData.label,
|
||||||
description: measurementData.description,
|
description: measurementData.description,
|
||||||
unit: measurementData.unit,
|
unit: measurementData.unit,
|
||||||
area:
|
area: cachedStats && cachedStats.area,
|
||||||
measurementData.cachedStats &&
|
mean: cachedStats && cachedStats.mean,
|
||||||
measurementData.cachedStats
|
stdDev: cachedStats && cachedStats.stdDev,
|
||||||
.area /* TODO: Add concept names instead (descriptor) */,
|
meanSUV,
|
||||||
|
stdDevSUV,
|
||||||
type: getValueTypeFromToolType(tool),
|
type: getValueTypeFromToolType(tool),
|
||||||
points,
|
points,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^0.50.0",
|
"@ohif/core": "^0.50.0",
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.2.8",
|
||||||
"cornerstone-tools": "4.19.1",
|
"cornerstone-tools": "4.20.1",
|
||||||
"dcmjs": "0.16.1",
|
"dcmjs": "0.16.1",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^0.50.0",
|
"@ohif/core": "^0.50.0",
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.2.8",
|
||||||
"cornerstone-tools": "4.19.0",
|
"cornerstone-tools": "4.20.1",
|
||||||
"dcmjs": "0.16.1",
|
"dcmjs": "0.16.1",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
|
|||||||
@ -33,7 +33,7 @@
|
|||||||
"@ohif/ui": "^0.50.0",
|
"@ohif/ui": "^0.50.0",
|
||||||
"cornerstone-core": "^2.3.0",
|
"cornerstone-core": "^2.3.0",
|
||||||
"cornerstone-math": "^0.1.8",
|
"cornerstone-math": "^0.1.8",
|
||||||
"cornerstone-tools": "4.19.1",
|
"cornerstone-tools": "4.20.1",
|
||||||
"cornerstone-wado-image-loader": "^3.1.2",
|
"cornerstone-wado-image-loader": "^3.1.2",
|
||||||
"dcmjs": "0.16.1",
|
"dcmjs": "0.16.1",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.3",
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^0.50.0",
|
"@ohif/core": "^0.50.0",
|
||||||
"cornerstone-tools": "4.19.0",
|
"cornerstone-tools": "4.20.1",
|
||||||
"dcmjs": "0.16.1",
|
"dcmjs": "0.16.1",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
|
|||||||
164
extensions/measurement-tracking/src/_shared/downloadCSVReport.js
Normal file
164
extensions/measurement-tracking/src/_shared/downloadCSVReport.js
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import { DicomMetadataStore } from '@ohif/core';
|
||||||
|
|
||||||
|
export default function downloadCSVReport(
|
||||||
|
measurementData,
|
||||||
|
StudyInstanceUID,
|
||||||
|
referencedSeriesInstanceUIDs,
|
||||||
|
MeasurementService
|
||||||
|
) {
|
||||||
|
const { VALUE_TYPES } = MeasurementService;
|
||||||
|
|
||||||
|
if (measurementData.length === 0) {
|
||||||
|
// Prevent download of report with no measurements.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const seriesMetadataMap = {};
|
||||||
|
|
||||||
|
referencedSeriesInstanceUIDs.forEach(SeriesInstanceUID => {
|
||||||
|
seriesMetadataMap[SeriesInstanceUID] = DicomMetadataStore.getSeries(
|
||||||
|
StudyInstanceUID,
|
||||||
|
SeriesInstanceUID
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const rows = [
|
||||||
|
[
|
||||||
|
'Patient ID',
|
||||||
|
'Patient Name',
|
||||||
|
'Study UID',
|
||||||
|
'Series UID',
|
||||||
|
'SOP Instance UID',
|
||||||
|
'Label',
|
||||||
|
'Long Axis (mm)',
|
||||||
|
'Short Axis (mm)',
|
||||||
|
'Length (mm)',
|
||||||
|
'Area (mmˆ2)',
|
||||||
|
'Mean',
|
||||||
|
'StDev',
|
||||||
|
'Units',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
measurementData.forEach(measurement => {
|
||||||
|
switch (measurement.type) {
|
||||||
|
case VALUE_TYPES.BIDIRECTIONAL:
|
||||||
|
rows.push(_getBidirectionalRow(measurement, seriesMetadataMap));
|
||||||
|
break;
|
||||||
|
case VALUE_TYPES.ELLIPSE:
|
||||||
|
rows.push(_getEllipseRow(measurement, seriesMetadataMap));
|
||||||
|
break;
|
||||||
|
case VALUE_TYPES.POINT:
|
||||||
|
rows.push(_getAdditionalFindingRow(measurement, seriesMetadataMap));
|
||||||
|
break;
|
||||||
|
case VALUE_TYPES.POLYLINE:
|
||||||
|
rows.push(_getLengthRow(measurement, seriesMetadataMap));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let csvContent =
|
||||||
|
'data:text/csv;charset=utf-8,' + rows.map(row => row.join(',')).join('\n');
|
||||||
|
|
||||||
|
_createAndDownloadFile(csvContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getBidirectionalRow(measurement, seriesMetadataMap) {
|
||||||
|
const commonRowItems = _getCommonRowItems(measurement, seriesMetadataMap);
|
||||||
|
|
||||||
|
return [
|
||||||
|
...commonRowItems,
|
||||||
|
measurement.longestDiameter, // Long Axis (mm)
|
||||||
|
measurement.shortestDiameter, // Short Axis (mm)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getCommonRowItems(measurement, seriesMetadataMap) {
|
||||||
|
const seriesMetadata = seriesMetadataMap[measurement.referenceSeriesUID];
|
||||||
|
const firstInstance = seriesMetadata.instances[0];
|
||||||
|
|
||||||
|
const row = [
|
||||||
|
firstInstance.PatientID, // Patient ID
|
||||||
|
firstInstance.PatientName.Alphabetic, // PatientName
|
||||||
|
measurement.referenceStudyUID, // StudyInstanceUID
|
||||||
|
measurement.referenceSeriesUID, // SeriesInstanceUID
|
||||||
|
measurement.SOPInstanceUID, // SOPInstanceUID
|
||||||
|
measurement.label || '', // Label
|
||||||
|
];
|
||||||
|
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getAdditionalFindingRow(measurement, seriesMetadataMap) {
|
||||||
|
const commonRowItems = _getCommonRowItems(measurement, seriesMetadataMap);
|
||||||
|
|
||||||
|
return [...commonRowItems];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getEllipseRow(measurement, seriesMetadataMap) {
|
||||||
|
const commonRowItems = _getCommonRowItems(measurement, seriesMetadataMap);
|
||||||
|
const blankColumns = ['', '', ''];
|
||||||
|
|
||||||
|
const { area, mean, stdDev, unit } = _getEllipseStatsWithUnits(
|
||||||
|
measurement,
|
||||||
|
seriesMetadataMap
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
...commonRowItems,
|
||||||
|
...blankColumns,
|
||||||
|
area, // Area (mmˆ2)
|
||||||
|
mean, // Mean
|
||||||
|
stdDev, // StDev
|
||||||
|
unit,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getEllipseStatsWithUnits(measurement, seriesMetadataMap) {
|
||||||
|
const seriesMetadata = seriesMetadataMap[measurement.referenceSeriesUID];
|
||||||
|
const firstInstance = seriesMetadata.instances[0];
|
||||||
|
const modality = firstInstance.Modality;
|
||||||
|
|
||||||
|
switch (modality) {
|
||||||
|
case 'CT':
|
||||||
|
return {
|
||||||
|
mean: `${measurement.mean.toFixed(1)}`,
|
||||||
|
stdDev: `${measurement.stdDev.toFixed(1)}`,
|
||||||
|
area: measurement.area.toFixed(1),
|
||||||
|
unit: 'HU',
|
||||||
|
};
|
||||||
|
case 'PT':
|
||||||
|
if (measurement.meanSUV) {
|
||||||
|
return {
|
||||||
|
mean: `${measurement.meanSUV.toFixed(1)}`,
|
||||||
|
stdDev: `${measurement.stdDevSUV.toFixed(1)}`,
|
||||||
|
area: measurement.area.toFixed(1),
|
||||||
|
unit: 'SUV',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// If no SUV calculation then fallback to default.
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
mean: `${measurement.mean.toFixed(1)}`,
|
||||||
|
stdDev: `${measurement.stdDev.toFixed(1)}`,
|
||||||
|
area: measurement.area.toFixed(1),
|
||||||
|
unit: 'Pixel Values',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getLengthRow(measurement, seriesMetadataMap) {
|
||||||
|
const commonRowItems = _getCommonRowItems(measurement, seriesMetadataMap);
|
||||||
|
const blankColumns = ['', ''];
|
||||||
|
|
||||||
|
return [...commonRowItems, ...blankColumns, measurement.length.toFixed(1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _createAndDownloadFile(csvContent) {
|
||||||
|
const encodedUri = encodeURI(csvContent);
|
||||||
|
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.setAttribute('href', encodedUri);
|
||||||
|
link.setAttribute('download', 'MeasurementReport.csv');
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
@ -6,15 +6,9 @@ function ActionButtons({ onExportClick, onCreateReportClick }) {
|
|||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ButtonGroup color="black" size="inherit">
|
<ButtonGroup color="black" size="inherit">
|
||||||
<Button className="text-base px-2 py-2" onClick={onExportClick}>Export</Button>
|
<Button className="text-base px-2 py-2" onClick={onExportClick}>
|
||||||
<IconButton
|
Export
|
||||||
className="px-2"
|
</Button>
|
||||||
color="black"
|
|
||||||
size="initial"
|
|
||||||
variant="outlined"
|
|
||||||
>
|
|
||||||
<Icon name="arrow-down" />
|
|
||||||
</IconButton>
|
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<Button
|
<Button
|
||||||
className="ml-2 text-base"
|
className="ml-2 text-base"
|
||||||
|
|||||||
@ -7,12 +7,13 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
useViewportGrid,
|
useViewportGrid,
|
||||||
} from '@ohif/ui';
|
} from '@ohif/ui';
|
||||||
import { DicomMetadataStore, DICOMSR, utils } from '@ohif/core';
|
import { DicomMetadataStore, utils } from '@ohif/core';
|
||||||
import { useDebounce } from '@hooks';
|
import { useDebounce } from '@hooks';
|
||||||
import ActionButtons from './ActionButtons';
|
import ActionButtons from './ActionButtons';
|
||||||
import { useTrackedMeasurements } from '../../getContextModule';
|
import { useTrackedMeasurements } from '../../getContextModule';
|
||||||
import createReportDialogPrompt from '../../_shared/createReportDialogPrompt';
|
import createReportDialogPrompt from '../../_shared/createReportDialogPrompt';
|
||||||
import RESPONSES from '../../_shared/PROMPT_RESPONSES';
|
import RESPONSES from '../../_shared/PROMPT_RESPONSES';
|
||||||
|
import downloadCSVReport from '../../_shared/downloadCSVReport';
|
||||||
|
|
||||||
const { formatDate } = utils;
|
const { formatDate } = utils;
|
||||||
|
|
||||||
@ -144,21 +145,12 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
|
|||||||
trackedSeries.includes(m.referenceSeriesUID)
|
trackedSeries.includes(m.referenceSeriesUID)
|
||||||
);
|
);
|
||||||
|
|
||||||
const promptResult = await createReportDialogPrompt(UIDialogService);
|
downloadCSVReport(
|
||||||
|
trackedMeasurements,
|
||||||
if (promptResult.action === RESPONSES.CREATE_REPORT) {
|
trackedStudy,
|
||||||
const additionalFindings = ['ArrowAnnotate'];
|
trackedSeries,
|
||||||
|
MeasurementService
|
||||||
const SeriesDescription =
|
);
|
||||||
// isUndefinedOrEmpty
|
|
||||||
promptResult.value === undefined || promptResult.value === ''
|
|
||||||
? 'Research Derived Series' // default
|
|
||||||
: promptResult.value; // provided value
|
|
||||||
|
|
||||||
DICOMSR.downloadReport(trackedMeasurements, additionalFindings, {
|
|
||||||
SeriesDescription,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const jumpToImage = ({ id, isActive }) => {
|
const jumpToImage = ({ id, isActive }) => {
|
||||||
|
|||||||
@ -57,7 +57,7 @@
|
|||||||
"@ohif/core": "^2.9.6",
|
"@ohif/core": "^2.9.6",
|
||||||
"@ohif/ui": "^2.0.0",
|
"@ohif/ui": "^2.0.0",
|
||||||
"cornerstone-core": "^2.3.0",
|
"cornerstone-core": "^2.3.0",
|
||||||
"cornerstone-tools": "4.19.0",
|
"cornerstone-tools": "4.20.1",
|
||||||
"cornerstone-wado-image-loader": "^3.1.2",
|
"cornerstone-wado-image-loader": "^3.1.2",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.3",
|
||||||
"gh-pages": "^2.0.1",
|
"gh-pages": "^2.0.1",
|
||||||
|
|||||||
@ -31,7 +31,7 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"cornerstone-core": "^2.3.0",
|
"cornerstone-core": "^2.3.0",
|
||||||
"cornerstone-tools": "4.19.1",
|
"cornerstone-tools": "4.20.1",
|
||||||
"cornerstone-wado-image-loader": "^3.1.2",
|
"cornerstone-wado-image-loader": "^3.1.2",
|
||||||
"dicom-parser": "^1.8.3"
|
"dicom-parser": "^1.8.3"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -40,6 +40,8 @@ const MEASUREMENT_SCHEMA_KEYS = [
|
|||||||
'type',
|
'type',
|
||||||
'unit',
|
'unit',
|
||||||
'area', // TODO: Add concept names instead (descriptor)
|
'area', // TODO: Add concept names instead (descriptor)
|
||||||
|
'mean',
|
||||||
|
'stdDev',
|
||||||
'length',
|
'length',
|
||||||
'shortestDiameter',
|
'shortestDiameter',
|
||||||
'longestDiameter',
|
'longestDiameter',
|
||||||
|
|||||||
@ -68,7 +68,7 @@
|
|||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"core-js": "^3.2.1",
|
"core-js": "^3.2.1",
|
||||||
"cornerstone-math": "^0.1.8",
|
"cornerstone-math": "^0.1.8",
|
||||||
"cornerstone-tools": "4.19.1",
|
"cornerstone-tools": "4.20.1",
|
||||||
"cornerstone-wado-image-loader": "^3.1.2",
|
"cornerstone-wado-image-loader": "^3.1.2",
|
||||||
"dcmjs": "0.16.1",
|
"dcmjs": "0.16.1",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.3",
|
||||||
|
|||||||
16
yarn.lock
16
yarn.lock
@ -7921,18 +7921,10 @@ cornerstone-math@^0.1.8:
|
|||||||
resolved "https://registry.yarnpkg.com/cornerstone-math/-/cornerstone-math-0.1.8.tgz#68ab1f9e4fdcd7c5cb23a0d2eb4263f9f894f1c5"
|
resolved "https://registry.yarnpkg.com/cornerstone-math/-/cornerstone-math-0.1.8.tgz#68ab1f9e4fdcd7c5cb23a0d2eb4263f9f894f1c5"
|
||||||
integrity sha512-x7NEQHBtVG7j1yeyj/aRoKTpXv1Vh2/H9zNLMyqYJDtJkNng8C4Q8M3CgZ1qer0Yr7eVq2x+Ynmj6kfOm5jXKw==
|
integrity sha512-x7NEQHBtVG7j1yeyj/aRoKTpXv1Vh2/H9zNLMyqYJDtJkNng8C4Q8M3CgZ1qer0Yr7eVq2x+Ynmj6kfOm5jXKw==
|
||||||
|
|
||||||
cornerstone-tools@4.19.0:
|
cornerstone-tools@4.20.1:
|
||||||
version "4.19.0"
|
version "4.20.1"
|
||||||
resolved "https://registry.yarnpkg.com/cornerstone-tools/-/cornerstone-tools-4.19.0.tgz#4c1cb0729fdf5ba5f3c2b83172395738dc8a972b"
|
resolved "https://registry.yarnpkg.com/cornerstone-tools/-/cornerstone-tools-4.20.1.tgz#90f6c28356622eac037683b19f9175f85d33dff7"
|
||||||
integrity sha512-Q8fgx4eN9F0TG84UXprMWjU4YpF/Vhs7ux67b0nV/E5SbyCip7yple4SfDbABB8oxVqzOdKpBnICMNTZNAyyKg==
|
integrity sha512-SyQsrg3bnGHUvWGZpUq7ia7/gj12S9zQFK2VMhpyAYxK9pWz+PRv8HBi0eEe/fWGAo/Vc62IRkdP3uF9MJlqiQ==
|
||||||
dependencies:
|
|
||||||
"@babel/runtime" "7.1.2"
|
|
||||||
cornerstone-math "0.1.7"
|
|
||||||
|
|
||||||
cornerstone-tools@4.19.1:
|
|
||||||
version "4.19.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/cornerstone-tools/-/cornerstone-tools-4.19.1.tgz#8a3e9473495b83e9f88dcbad99ac79eae65a81d4"
|
|
||||||
integrity sha512-ltobIAUEgnPswhWLSAm05HRP7pw99m07RNBYuoN4NInDD2ctvk3HxAjM1CAV2GO1zv8yMwKOaE/j2m70Na12mQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "7.1.2"
|
"@babel/runtime" "7.1.2"
|
||||||
cornerstone-math "0.1.7"
|
cornerstone-math "0.1.7"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user