feat: add support to scoord3d (#5016)
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
1 parent
af40f06524
commit
735405a855
73 files changed
+1032
-993
No files matched your search
@@ -53,7 +53,7 @@
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.30.3",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.32.5",
|
||||
"@emotion/serialize": "^1.1.3",
|
||||
"@ohif/core": "3.11.0-beta.114",
|
||||
"@ohif/extension-cornerstone": "3.11.0-beta.114",
|
||||
@@ -79,7 +79,7 @@
|
||||
"classnames": "^2.3.2",
|
||||
"core-js": "*",
|
||||
"cornerstone-math": "^0.1.9",
|
||||
"dcmjs": "^0.42.0",
|
||||
"dcmjs": "0.43.1",
|
||||
"detect-gpu": "^4.0.16",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"dotenv-webpack": "^1.7.0",
|
||||
|
||||
@@ -26,7 +26,7 @@ window.config = {
|
||||
// filterQueryParam: false,
|
||||
// Uses the ohif datasource as the default - this requires that KHEOPS be
|
||||
// configured with an OHIF path to .../viewer/dicomwebproxy
|
||||
defaultDataSourceName: 'dicomweb',
|
||||
defaultDataSourceName: 'ohif',
|
||||
/* Dynamic config allows user to pass "configUrl" query string this allows to load config without recompiling application. The regex will ensure valid configuration source */
|
||||
// dangerouslyUseDynamicConfig: {
|
||||
// enabled: true,
|
||||
@@ -40,7 +40,7 @@ window.config = {
|
||||
dataSources: [
|
||||
{
|
||||
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
|
||||
sourceName: 'dicomweb',
|
||||
sourceName: 'ohif',
|
||||
configuration: {
|
||||
friendlyName: 'AWS S3 Static wado server',
|
||||
name: 'aws',
|
||||
@@ -54,47 +54,15 @@ window.config = {
|
||||
supportsFuzzyMatching: false,
|
||||
supportsWildcard: true,
|
||||
staticWado: true,
|
||||
singlepart: 'bulkdata,video',
|
||||
// whether the data source should use retrieveBulkData to grab metadata,
|
||||
// and in case of relative path, what would it be relative to, options
|
||||
// are in the series level or study level (some servers like series some study)
|
||||
singlepart: 'video,pdf',
|
||||
bulkDataURI: {
|
||||
enabled: true,
|
||||
relativeResolution: 'studies',
|
||||
transform: url => url.replace('/pixeldata.mp4', '/rendered'),
|
||||
},
|
||||
omitQuotationForMultipartRequest: true,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
|
||||
sourceName: 'ohif2',
|
||||
configuration: {
|
||||
friendlyName: 'AWS S3 Static wado secondary server',
|
||||
name: 'aws',
|
||||
wadoUriRoot: 'https://dd14fa38qiwhyfd.cloudfront.net/dicomweb',
|
||||
qidoRoot: 'https://dd14fa38qiwhyfd.cloudfront.net/dicomweb',
|
||||
wadoRoot: 'https://dd14fa38qiwhyfd.cloudfront.net/dicomweb',
|
||||
qidoSupportsIncludeField: false,
|
||||
supportsReject: false,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
enableStudyLazyLoad: true,
|
||||
supportsFuzzyMatching: false,
|
||||
supportsWildcard: true,
|
||||
staticWado: true,
|
||||
singlepart: 'bulkdata,video',
|
||||
// whether the data source should use retrieveBulkData to grab metadata,
|
||||
// and in case of relative path, what would it be relative to, options
|
||||
// are in the series level or study level (some servers like series some study)
|
||||
bulkDataURI: {
|
||||
enabled: true,
|
||||
relativeResolution: 'studies',
|
||||
},
|
||||
omitQuotationForMultipartRequest: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
|
||||
sourceName: 'ohif3',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useCallback, useRef } from 'react';
|
||||
import { Types, MeasurementService } from '@ohif/core';
|
||||
import { Types } from '@ohif/core';
|
||||
import { ViewportGrid, ViewportPane } from '@ohif/ui-next';
|
||||
import { useViewportGrid } from '@ohif/ui-next';
|
||||
import EmptyViewport from './EmptyViewport';
|
||||
@@ -14,13 +14,8 @@ function ViewerViewportGrid(props: withAppTypes) {
|
||||
const { numCols, numRows } = layout;
|
||||
const layoutHash = useRef(null);
|
||||
|
||||
const {
|
||||
displaySetService,
|
||||
measurementService,
|
||||
hangingProtocolService,
|
||||
uiNotificationService,
|
||||
customizationService,
|
||||
} = servicesManager.services;
|
||||
const { displaySetService, hangingProtocolService, uiNotificationService, customizationService } =
|
||||
servicesManager.services;
|
||||
|
||||
const generateLayoutHash = () => `${numCols}-${numRows}`;
|
||||
|
||||
@@ -32,9 +27,9 @@ function ViewerViewportGrid(props: withAppTypes) {
|
||||
|
||||
*/
|
||||
const updateDisplaySetsFromProtocol = (
|
||||
protocol: Types.HangingProtocol.Protocol,
|
||||
_protocol: Types.HangingProtocol.Protocol,
|
||||
stage,
|
||||
activeStudyUID,
|
||||
_activeStudyUID,
|
||||
viewportMatchDetails
|
||||
) => {
|
||||
const availableDisplaySets = displaySetService.getActiveDisplaySets();
|
||||
@@ -154,100 +149,6 @@ function ViewerViewportGrid(props: withAppTypes) {
|
||||
}
|
||||
}, [viewportGridService, generateLayoutHash]);
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = measurementService.subscribe(
|
||||
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_LAYOUT,
|
||||
event => {
|
||||
const { viewportId, measurement, isConsumed } = event;
|
||||
if (isConsumed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { displaySetInstanceUID: referencedDisplaySetInstanceUID } = measurement;
|
||||
const { viewports } = viewportGridService.getState();
|
||||
|
||||
// Check if any viewport can display this measurement
|
||||
let canAnyViewportDisplayMeasurement = false;
|
||||
|
||||
viewports.forEach((viewport, id) => {
|
||||
const displaySetInstanceUIDs = viewport.displaySetInstanceUIDs || [];
|
||||
const viewportHasDisplaySet = displaySetInstanceUIDs.includes(
|
||||
referencedDisplaySetInstanceUID
|
||||
);
|
||||
|
||||
// Extract metadata and prepare reference
|
||||
const { FrameOfReferenceUID, ...metadataRest } = measurement.metadata;
|
||||
const reference = {
|
||||
...(viewportHasDisplaySet ? measurement.metadata : metadataRest),
|
||||
displaySetInstanceUID: referencedDisplaySetInstanceUID,
|
||||
};
|
||||
|
||||
// Check if viewport can display the reference
|
||||
if (
|
||||
viewport.isReferenceViewable?.({
|
||||
viewportId: id,
|
||||
reference,
|
||||
})
|
||||
) {
|
||||
canAnyViewportDisplayMeasurement = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (canAnyViewportDisplayMeasurement) {
|
||||
// Let the viewports handle the jump
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to change layouts since no viewport consumed the event
|
||||
const updatedViewports = _getUpdatedViewports(viewportId, referencedDisplaySetInstanceUID);
|
||||
|
||||
if (!updatedViewports?.[0]) {
|
||||
console.warn(
|
||||
'ViewportGrid::Unable to navigate to viewport containing',
|
||||
referencedDisplaySetInstanceUID
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the viewport that can display the measurement
|
||||
const viewport = updatedViewports.find(viewport => {
|
||||
const gridViewport = viewportGridService.getViewportState(viewport.viewportId);
|
||||
return gridViewport.isReferenceViewable?.({
|
||||
viewportId: viewport.viewportId,
|
||||
reference: {
|
||||
...measurement.metadata,
|
||||
displaySetInstanceUID: referencedDisplaySetInstanceUID,
|
||||
},
|
||||
viewportOptions: gridViewport.viewportOptions || {},
|
||||
});
|
||||
});
|
||||
|
||||
if (!viewport) {
|
||||
console.warn('No suitable viewport found for displaying measurement');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update stored position presentation
|
||||
commandsManager.run('updateStoredPositionPresentation', {
|
||||
viewportId: viewport.viewportId,
|
||||
displaySetInstanceUIDs: [referencedDisplaySetInstanceUID],
|
||||
referencedImageId: measurement.referencedImageId,
|
||||
options: {
|
||||
...measurement.metadata,
|
||||
},
|
||||
});
|
||||
|
||||
event.consume();
|
||||
|
||||
commandsManager.run('setDisplaySetsForViewports', { viewportsToUpdate: updatedViewports });
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [viewports, _getUpdatedViewports]);
|
||||
|
||||
const onDropHandler = (viewportId, { displaySetInstanceUID }) => {
|
||||
const { viewportGridService } = servicesManager.services;
|
||||
const customOnDropHandler = customizationService.getCustomization('customOnDropHandler');
|
||||
@@ -267,23 +168,6 @@ function ViewerViewportGrid(props: withAppTypes) {
|
||||
viewportGridService.publishViewportOnDropHandled({ displaySetInstanceUID });
|
||||
};
|
||||
|
||||
// Store previous isReferenceViewable values to avoid infinite loops
|
||||
const prevReferenceViewableMap = useRef(new Map());
|
||||
// Track viewports that need isReferenceViewable updates
|
||||
const viewportsToUpdate = useRef(new Map());
|
||||
|
||||
// Apply isReferenceViewable updates in an effect, not during render
|
||||
useEffect(() => {
|
||||
const updates = viewportsToUpdate.current;
|
||||
if (updates.size > 0) {
|
||||
updates.forEach((isReferenceViewable, viewportId) => {
|
||||
viewportGridService.setIsReferenceViewable(viewportId, isReferenceViewable);
|
||||
prevReferenceViewableMap.current.set(viewportId, isReferenceViewable);
|
||||
});
|
||||
viewportsToUpdate.current.clear();
|
||||
}
|
||||
});
|
||||
|
||||
const getViewportPanes = useCallback(() => {
|
||||
const viewportPanes = [];
|
||||
|
||||
@@ -315,27 +199,12 @@ function ViewerViewportGrid(props: withAppTypes) {
|
||||
return !displaySet?.unsupported;
|
||||
});
|
||||
|
||||
const { component: ViewportComponent, isReferenceViewable } = _getViewportComponent(
|
||||
const { component: ViewportComponent } = _getViewportComponent(
|
||||
displaySets,
|
||||
viewportComponents,
|
||||
uiNotificationService
|
||||
);
|
||||
|
||||
// Only queue isReferenceViewable updates if it's changed to avoid render loops
|
||||
// We need to handle both function and non-function values
|
||||
if (viewportId) {
|
||||
const prevValue = prevReferenceViewableMap.current.get(viewportId);
|
||||
const isFunction = typeof isReferenceViewable === 'function';
|
||||
const isSameFunction = isFunction && typeof prevValue === 'function';
|
||||
|
||||
// For non-functions, compare directly. For functions, we treat them as always different
|
||||
// (this is conservative but safe)
|
||||
if (!isSameFunction && prevValue !== isReferenceViewable) {
|
||||
// Queue the update instead of doing it during render
|
||||
viewportsToUpdate.current.set(viewportId, isReferenceViewable);
|
||||
}
|
||||
}
|
||||
|
||||
// look inside displaySets to see if they need reRendering
|
||||
const displaySetsNeedsRerendering = displaySets.some(displaySet => {
|
||||
return displaySet.needsRerendering;
|
||||
@@ -462,8 +331,8 @@ function _getViewportComponent(displaySets, viewportComponents, uiNotificationSe
|
||||
throw new Error('displaySetsToDisplay is null');
|
||||
}
|
||||
if (viewportComponents[i].displaySetsToDisplay.includes(SOPClassHandlerId)) {
|
||||
const { component, isReferenceViewable } = viewportComponents[i];
|
||||
return { component, isReferenceViewable };
|
||||
const { component } = viewportComponents[i];
|
||||
return { component };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,7 +343,7 @@ function _getViewportComponent(displaySets, viewportComponents, uiNotificationSe
|
||||
type: 'error',
|
||||
});
|
||||
|
||||
return { component: EmptyViewport, isReferenceViewable: () => false };
|
||||
return { component: EmptyViewport };
|
||||
}
|
||||
|
||||
export default ViewerViewportGrid;
|
||||
Reference in new issue
Block a user