feat(ecg): add DICOM ECG waveform extension (#5856)
* feat(ecg): add DICOM ECG waveform extension Introduce @ohif/extension-dicom-ecg for rendering DICOM waveform (ECG) data. Register the extension in the basic mode and pluginConfig.json, and remove ECG from NON_IMAGE_MODALITIES so waveform display sets are handled by the new viewport. * refactor(ecg): move ECG support into cornerstone extension per review feedback - Remove standalone dicom-ecg extension; fold all ECG functionality into the cornerstone extension as requested by reviewer - Add ECG SOP class handler (DicomEcgSopClassHandler) to the cornerstone extension getSopClassHandlerModule, registering ECG waveform metadata via genericMetadataProvider on display set creation - Move ECG helpers (buildEcgModule, decodeInt16Multiplex, base64ToArrayBuffer) into extensions/cornerstone/src/utils/ecgMetadata.ts - Handle ECGViewport in CornerstoneViewportService._setDisplaySets by detecting ECGViewport instanceof and calling setEcg(imageId) directly, so OHIFCornerstoneViewport can be used without a custom ECG viewport component - Add ECG support to getCornerstoneViewportType utility - Update basic mode to reference the cornerstone extension's ECG SOP handler and use the base cornerstone viewport for ECG display sets - Migrate ecgMetadata and getCornerstoneViewportType tests * chore: revert bun.lock to upstream origin/master --------- Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
1 parent
30bb5d0652
commit
70f76aeba1
8 files changed
+466
-5
No files matched your search
@@ -3,6 +3,7 @@ import i18n from '@ohif/i18n';
|
||||
import { utilities as csUtils, Enums as csEnums } from '@cornerstonejs/core';
|
||||
import dcmjs from 'dcmjs';
|
||||
import { dicomWebUtils } from '@ohif/extension-default';
|
||||
import { buildEcgModule } from './utils/ecgMetadata';
|
||||
|
||||
const { MetadataModules } = csEnums;
|
||||
const { utils } = OHIF;
|
||||
@@ -155,6 +156,84 @@ export function getDicomMicroscopySopClassHandler({ servicesManager, extensionMa
|
||||
};
|
||||
}
|
||||
|
||||
export function getSopClassHandlerModule(params) {
|
||||
return [getDicomMicroscopySopClassHandler(params)];
|
||||
/**
|
||||
* DICOM Waveform SOP Class UIDs for ECG / cardiac electrophysiology.
|
||||
* Reference: https://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_B.5.html
|
||||
*/
|
||||
const ECG_SOP_CLASS_UIDS = {
|
||||
TWELVE_LEAD_ECG_WAVEFORM_STORAGE: '1.2.840.10008.5.1.4.1.1.9.1.1',
|
||||
GENERAL_ECG_WAVEFORM_STORAGE: '1.2.840.10008.5.1.4.1.1.9.1.2',
|
||||
AMBULATORY_ECG_WAVEFORM_STORAGE: '1.2.840.10008.5.1.4.1.1.9.1.3',
|
||||
HEMODYNAMIC_WAVEFORM_STORAGE: '1.2.840.10008.5.1.4.1.1.9.2.1',
|
||||
CARDIAC_ELECTROPHYSIOLOGY_WAVEFORM_STORAGE: '1.2.840.10008.5.1.4.1.1.9.3.1',
|
||||
};
|
||||
|
||||
const ecgSopClassUids = Object.values(ECG_SOP_CLASS_UIDS);
|
||||
|
||||
const DicomEcgSOPClassHandlerId =
|
||||
'@ohif/extension-cornerstone.sopClassHandlerModule.DicomEcgSopClassHandler';
|
||||
|
||||
function _getEcgDisplaySetsFromSeries(instances, servicesManager) {
|
||||
const { userAuthenticationService } = servicesManager.services;
|
||||
|
||||
return instances.map(instance => {
|
||||
const { Modality, SOPInstanceUID } = instance;
|
||||
const { SeriesDescription, SeriesNumber, SeriesDate } = instance;
|
||||
const { SeriesInstanceUID, StudyInstanceUID, SOPClassUID } = instance;
|
||||
const imageId = instance.imageId;
|
||||
|
||||
// Register ECG metadata in the OHIF metadata provider so that
|
||||
// Cornerstone's ECGViewport can retrieve it via metaData.get('ecgModule', imageId).
|
||||
if (imageId) {
|
||||
const ecgModule = buildEcgModule(instance, userAuthenticationService);
|
||||
if (ecgModule) {
|
||||
csUtils.genericMetadataProvider.addRaw(imageId, {
|
||||
type: MetadataModules.ECG,
|
||||
metadata: ecgModule,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
Modality,
|
||||
displaySetInstanceUID: utils.guid(),
|
||||
SeriesDescription,
|
||||
SeriesNumber,
|
||||
SeriesDate,
|
||||
SOPInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
SOPClassHandlerId: DicomEcgSOPClassHandlerId,
|
||||
SOPClassUID,
|
||||
referencedImages: null,
|
||||
measurements: null,
|
||||
viewportType: csEnums.ViewportType.ECG,
|
||||
instances: [instance],
|
||||
instance,
|
||||
thumbnailSrc: null,
|
||||
isDerivedDisplaySet: false,
|
||||
isLoaded: false,
|
||||
sopClassUids: ecgSopClassUids,
|
||||
numImageFrames: 0,
|
||||
numInstances: 1,
|
||||
imageIds: imageId ? [imageId] : [],
|
||||
supportsWindowLevel: false,
|
||||
label: SeriesDescription || 'ECG',
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function getDicomEcgSopClassHandler({ servicesManager }) {
|
||||
const getDisplaySetsFromSeries = instances =>
|
||||
_getEcgDisplaySetsFromSeries(instances, servicesManager);
|
||||
|
||||
return {
|
||||
name: 'DicomEcgSopClassHandler',
|
||||
sopClassUids: ecgSopClassUids,
|
||||
getDisplaySetsFromSeries,
|
||||
};
|
||||
}
|
||||
|
||||
export function getSopClassHandlerModule(params) {
|
||||
return [getDicomMicroscopySopClassHandler(params), getDicomEcgSopClassHandler(params)];
|
||||
}
|
||||
Reference in new issue
Block a user