Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/default/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/calculate-suv": "1.1.0",
"@cornerstonejs/nifti-volume-loader": "4.21.7",
"lodash.get": "4.4.2",
"lodash.uniqby": "4.7.0",
"react-color": "2.19.3"
Expand Down
63 changes: 63 additions & 0 deletions extensions/default/src/getSopClassHandlerModule.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { utils, classes } from '@ohif/core';
import { createNiftiImageIdsAndCacheMetadata } from '@cornerstonejs/nifti-volume-loader';
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
import i18n from '@ohif/i18n';
import { id } from './id';
import getDisplaySetMessages from './getDisplaySetMessages';
Expand All @@ -14,6 +15,23 @@ const DYNAMIC_VOLUME_LOADER_SCHEME = 'cornerstoneStreamingDynamicImageVolume';
const sopClassHandlerName = 'stack';
let appContext = {};

const isAbsolutePathOrUrl = value =>
typeof value === 'string' &&
(/^[a-z]+:\/\//i.test(value) || value.startsWith('//') || value.startsWith('/'));

const joinUrl = (baseUrl, path) => {
if (!baseUrl) {
return path;
}

try {
return new URL(path, baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`).toString();
} catch (error) {
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
return `${normalizedBaseUrl}${path.replace(/^\/+/, '')}`;
}
};

const getDynamicVolumeInfo = instances => {
const { extensionManager } = appContext;

Expand Down Expand Up @@ -96,6 +114,14 @@ const makeDisplaySet = (instances, index) => {
// set appropriate attributes to image set...
const messages = getDisplaySetMessages(instances, isReconstructable, isDynamicVolume);

const { niftiPrivateTagName, niftiBaseUrl } = dataSource.getConfig?.() || {};
const niftiPath = niftiPrivateTagName ? instance[niftiPrivateTagName] : undefined;
let niftiURL;

if (typeof niftiPath === 'string' && niftiPath.trim().length > 0) {
niftiURL = isAbsolutePathOrUrl(niftiPath) ? niftiPath : joinUrl(niftiBaseUrl, niftiPath);
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.

imageSet.setAttributes({
volumeLoaderSchema,
displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID
Expand Down Expand Up @@ -124,6 +150,43 @@ const makeDisplaySet = (instances, index) => {
FrameOfReferenceUID: instance.FrameOfReferenceUID,
});

if (niftiURL) {
// If a downstream integration already exposes a NIfTI URL on the series,
// pass through the DICOM-derived metadata that a paired loader-side
// implementation can consume for VOI and series registration.
const dicomMetadata = {
Modality: instance.Modality,
SeriesInstanceUID: instance.SeriesInstanceUID,
SeriesNumber: instance.SeriesNumber,
SeriesDescription: instance.SeriesDescription,
WindowCenter: instance.WindowCenter,
WindowWidth: instance.WindowWidth,
VOILUTFunction: instance.VOILUTFunction,
RescaleSlope: instance.RescaleSlope,
RescaleIntercept: instance.RescaleIntercept,
};

imageSet.setAttributes({
niftiURL,
loadImageIds: async () => {
try {
const loadedImageIds = await createNiftiImageIdsAndCacheMetadata({
url: niftiURL,
dicomMetadata,
});

imageSet.setAttributes({
imageIds: loadedImageIds,
numImageFrames: Array.isArray(loadedImageIds) ? loadedImageIds.length : 0,
isReconstructable: true,
});
Comment on lines +180 to +184

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don't force isReconstructable: true when the loader returns no image IDs.

If createNiftiImageIdsAndCacheMetadata resolves to an empty (or non-array) result, the display set ends up with 0 frames yet still advertises reconstructability, which can mislead downstream MPR/volume paths. Derive the flag from the resolved IDs instead.

🐛 Proposed fix
+          const hasImageIds = Array.isArray(loadedImageIds) && loadedImageIds.length > 0;
+
           imageSet.setAttributes({
             imageIds: loadedImageIds,
-            numImageFrames: Array.isArray(loadedImageIds) ? loadedImageIds.length : 0,
-            isReconstructable: true,
+            numImageFrames: hasImageIds ? loadedImageIds.length : 0,
+            isReconstructable: hasImageIds,
           });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
imageSet.setAttributes({
imageIds: loadedImageIds,
numImageFrames: Array.isArray(loadedImageIds) ? loadedImageIds.length : 0,
isReconstructable: true,
});
const hasImageIds = Array.isArray(loadedImageIds) && loadedImageIds.length > 0;
imageSet.setAttributes({
imageIds: loadedImageIds,
numImageFrames: hasImageIds ? loadedImageIds.length : 0,
isReconstructable: hasImageIds,
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/default/src/getSopClassHandlerModule.js` around lines 180 - 184,
The display set currently forces isReconstructable: true regardless of the
loader result; instead derive it from createNiftiImageIdsAndCacheMetadata's
resolved value (loadedImageIds) by setting isReconstructable to true only when
Array.isArray(loadedImageIds) && loadedImageIds.length > 0; update the call to
imageSet.setAttributes (and ensure numImageFrames uses the same safe length
logic) so downstream MPR/volume code sees reconstructability only when there are
actual image IDs.

} catch (error) {
console.error('Error loading niftiURL:', error);
}
},
Comment thread
greptile-apps[bot] marked this conversation as resolved.
});
}

const imageIds = dataSource.getImageIdsForDisplaySet(imageSet);
let imageId = imageIds[Math.floor(imageIds.length / 2)];
let thumbnailInstance = instances[Math.floor(instances.length / 2)];
Expand Down