import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; 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'; import setCornerstoneMeasurementActive from '../../_shared/setCornerstoneMeasurementActive'; const { formatDate } = utils; const DISPLAY_STUDY_SUMMARY_INITIAL_VALUE = { key: undefined, // date: undefined, // '07-Sep-2010', modality: undefined, // 'CT', description: undefined, // 'CHEST/ABD/PELVIS W CONTRAST', }; function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { const [viewportGrid, viewportGridService] = useViewportGrid(); const [measurementChangeTimestamp, setMeasurementsUpdated] = useState( Date.now().toString() ); const debouncedMeasurementChangeTimestamp = useDebounce( measurementChangeTimestamp, 200 ); const { MeasurementService, UIDialogService, DisplaySetService, } = servicesManager.services; const [ trackedMeasurements, sendTrackedMeasurementsEvent, ] = useTrackedMeasurements(); const { trackedStudy, trackedSeries } = trackedMeasurements.context; const [displayStudySummary, setDisplayStudySummary] = useState( DISPLAY_STUDY_SUMMARY_INITIAL_VALUE ); const [displayMeasurements, setDisplayMeasurements] = useState([]); useEffect(() => { const measurements = MeasurementService.getMeasurements(); const filteredMeasurements = measurements.filter( m => trackedStudy === m.referenceStudyUID && trackedSeries.includes(m.referenceSeriesUID) ); const mappedMeasurements = filteredMeasurements.map((m, index) => _mapMeasurementToDisplay(m, index, MeasurementService.VALUE_TYPES) ); setDisplayMeasurements(mappedMeasurements); // eslint-ignore-next-line }, [ MeasurementService, trackedStudy, trackedSeries, debouncedMeasurementChangeTimestamp, ]); const updateDisplayStudySummary = async () => { if (trackedMeasurements.matches('tracking')) { const StudyInstanceUID = trackedStudy; const studyMeta = DicomMetadataStore.getStudy(StudyInstanceUID); const instanceMeta = studyMeta.series[0].instances[0]; 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, description: StudyDescription, }); } } else if (trackedStudy === '' || trackedStudy === undefined) { setDisplayStudySummary(DISPLAY_STUDY_SUMMARY_INITIAL_VALUE); } }; // ~~ DisplayStudySummary useEffect(() => { updateDisplayStudySummary(); }, [ displayStudySummary.key, trackedMeasurements, trackedStudy, updateDisplayStudySummary, ]); // TODO: Better way to consolidated, debounce, check on change? // Are we exposing the right API for measurementService? // This watches for ALL MeasurementService changes. It updates a timestamp, // which is debounced. After a brief period of inactivity, this triggers // a re-render where we grab up-to-date measurements. useEffect(() => { const added = MeasurementService.EVENTS.MEASUREMENT_ADDED; const updated = MeasurementService.EVENTS.MEASUREMENT_UPDATED; const removed = MeasurementService.EVENTS.MEASUREMENT_REMOVED; const cleared = MeasurementService.EVENTS.MEASUREMENTS_CLEARED; const subscriptions = []; [added, updated, removed, cleared].forEach(evt => { subscriptions.push( MeasurementService.subscribe(evt, () => { setMeasurementsUpdated(Date.now().toString()); }).unsubscribe ); }); return () => { subscriptions.forEach(unsub => { unsub(); }); }; }, [MeasurementService, sendTrackedMeasurementsEvent]); function createReport() { // TODO -> Eventually deal with multiple dataSources. // Would need some way of saying which one is the "push" dataSource const dataSources = extensionManager.getDataSources(); const dataSource = dataSources[0]; const measurements = MeasurementService.getMeasurements(); const trackedMeasurements = measurements.filter( m => trackedStudy === m.referenceStudyUID && trackedSeries.includes(m.referenceSeriesUID) ); return createReportAsync(servicesManager, dataSource, trackedMeasurements); } function exportReport() { const measurements = MeasurementService.getMeasurements(); const trackedMeasurements = measurements.filter( m => trackedStudy === m.referenceStudyUID && trackedSeries.includes(m.referenceSeriesUID) ); const additionalFindings = ['ArrowAnnotate']; DICOMSR.downloadReport(trackedMeasurements, additionalFindings); } const jumpToImage = ({ id, isActive }) => { MeasurementService.jumpToMeasurement(viewportGrid.activeViewportIndex, id); 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, }); } } 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 (