fix(SR): Added support for spline and live wire SR items. (#5870)

* fix(SR): Added support for spline and live wire SR items.

* Apply suggestion from @greptile-apps[bot]

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Add a script to checkout a worktree for test builds

* fix: Allow download for testing sr validator

* Remove script that wasn't intended to be included

* Bump CS3D version.

* PR comments - simplify code and use single codepath for download

* Allow both download and save buttons for SEG and RTSTRUCT

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-03-17 08:59:47 -04:00
1 parent 69d8ad63f3
commit 1d4802c2a3
13 files changed
+220 -160

No files matched your search

+66 -1
View File
@@ -1,4 +1,7 @@
import { Types, DicomMetadataStore } from '@ohif/core';
import { Types, DicomMetadataStore, utils } from '@ohif/core';
import dcmjs from 'dcmjs';
const { downloadBlob } = utils;
import { ContextMenuController } from './CustomizableContextMenu';
import DicomTagBrowser from './DicomTagBrowser/DicomTagBrowser';
@@ -768,6 +771,67 @@ const commandsModule = ({
setTimeout(() => actions.scrollActiveThumbnailIntoView(), 0);
},
/**
* Creates a store function based on the data source type.
* @param dataSource - 'download', 'copyToClipboard', or a named data source
* @param defaultFileName - Default filename for download/clipboard
* @param defaultContentType - Default content type for clipboard
* @returns A store function, or null if no valid store exists
*/
createStoreFunction: ({ dataSource, defaultFileName, defaultContentType }) => {
if (dataSource === 'download') {
return async dicom => {
const instances = Array.isArray(dicom) ? dicom : [dicom];
DicomMetadataStore.addInstances(instances, true);
if (instances.length !== 1) {
throw new Error('Download only supports a single DICOM instance');
}
const reportBlob = dcmjs.data.datasetToBlob(instances[0]);
downloadBlob(reportBlob, { filename: defaultFileName || 'dicom.dcm' });
};
}
if (dataSource === 'copyToClipboard') {
return async dicom => {
const instances = Array.isArray(dicom) ? dicom : [dicom];
DicomMetadataStore.addInstances(instances, true);
if (instances.length !== 1) {
throw new Error('Copy to clipboard only supports a single DICOM instance');
}
const reportBlob = dcmjs.data.datasetToBlob(instances[0]);
const type = defaultContentType || 'application/dicom';
await navigator.clipboard.write([
new ClipboardItem({ [type]: new Blob([reportBlob], { type }) }),
]);
};
}
// DICOM STOW path — resolve the named data source
const dataSources = extensionManager.getDataSources(dataSource);
const resolvedDataSource = dataSources?.[0];
if (!resolvedDataSource?.store?.dicom) {
return null;
}
return async (dicom, { dicomDict } = {}) => {
const instances = Array.isArray(dicom) ? dicom : [dicom];
const config = resolvedDataSource.getConfig?.();
if (config?.wadoRoot) {
instances.forEach(instance => {
instance.wadoRoot = config.wadoRoot;
});
}
DicomMetadataStore.addInstances(instances, true);
for (const instance of instances) {
await resolvedDataSource.store.dicom(instance, null, dicomDict);
}
const studyUIDs = new Set(instances.map(i => i.StudyInstanceUID).filter(Boolean));
for (const uid of studyUIDs) {
resolvedDataSource.deleteStudyMetadataPromise(uid);
}
};
},
};
const definitions = {
@@ -796,6 +860,7 @@ const commandsModule = ({
scrollActiveThumbnailIntoView: actions.scrollActiveThumbnailIntoView,
addDisplaySetAsLayer: actions.addDisplaySetAsLayer,
removeDisplaySetLayer: actions.removeDisplaySetLayer,
createStoreFunction: actions.createStoreFunction,
};
return {