Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(dicom): Update multiframe DICOM JSON parsing for correct image ID generation #4307

Merged
merged 2 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
28 changes: 18 additions & 10 deletions .scripts/dicom-json-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,42 @@
* The JSON file can be used to load the study into the OHIF Viewer. You can get more detail
* in the DICOM JSON Data source on docs.ohif.org
*
* Usage: node dicomStudyToJSONLaunch.js <studyFolder> <urlPrefix> <outputJSONPath>
* Usage: node dicomStudyToJSONLaunch.js <studyFolder> <urlPrefix> <outputJSONPath> <optional scheme>
*
* params:
* - studyFolder: path to the study folder
* - studyFolder: path to the study folder which contains the DICOM files
* - urlPrefix: prefix to the url that will be used to load the study into the viewer. For instance
* we use https://ohif-assets.s3.us-east-2.amazonaws.com/dicom-json/data as the urlPrefix for the
* example since the data is hosted on S3 and each study is in a folder. So the url in the generated
* json file for the first instance of the first series of the first study will be
* dicomweb:https://ohif-assets.s3.us-east-2.amazonaws.com/dicom-json/data/Series1/Instance1
*
* as you see the dicomweb is a prefix that is used to load the data into the viewer, which is suited when
* the .dcm file is hosted statically and can be accessed via a URL (like our example above)
* However, you can specify a new scheme bellow.
*
* - outputJSONPath: path to the output JSON file
* - scheme: default dicomweb if not provided
*/
const dcmjs = require('dcmjs');
const path = require('path');
const fs = require('fs').promises;

const args = process.argv.slice(2);
const [studyDirectory, urlPrefix, outputPath] = args;
const [studyDirectory, urlPrefix, outputPath, scheme = 'dicomweb'] = args;

if (args.length !== 3) {
console.error('Usage: node dicomStudyToJSONLaunch.js <studyFolder> <urlPrefix> <outputJSONPath>');
if (args.length < 3 || args.length > 4) {
console.error(
'Usage: node dicomStudyToJSONLaunch.js <studyFolder> <urlPrefix> <outputJSONPath> [scheme]'
);
process.exit(1);
}

const model = {
studies: [],
};

async function convertDICOMToJSON(studyDirectory, urlPrefix, outputPath) {
async function convertDICOMToJSON(studyDirectory, urlPrefix, outputPath, scheme) {
try {
const files = await recursiveReadDir(studyDirectory);
console.debug('Processing...');
Expand All @@ -42,7 +50,7 @@ async function convertDICOMToJSON(studyDirectory, urlPrefix, outputPath) {
const dicomDict = dcmjs.data.DicomMessage.readFile(arrayBuffer.buffer);
const instance = dcmjs.data.DicomMetaDictionary.naturalizeDataset(dicomDict.dict);

instance.fileLocation = createImageId(file, urlPrefix, studyDirectory);
instance.fileLocation = createImageId(file, urlPrefix, studyDirectory, scheme);
processInstance(instance);
}
}
Expand Down Expand Up @@ -77,10 +85,10 @@ async function recursiveReadDir(dir) {
return results;
}

function createImageId(fileLocation, urlPrefix, studyDirectory) {
function createImageId(fileLocation, urlPrefix, studyDirectory, scheme) {
const relativePath = path.relative(studyDirectory, fileLocation);
const normalizedPath = path.normalize(relativePath).replace(/\\/g, '/');
return `dicomweb:${urlPrefix}${normalizedPath}`;
return `${scheme}:${urlPrefix}${normalizedPath}`;
}

function processInstance(instance) {
Expand Down Expand Up @@ -262,4 +270,4 @@ function createInstanceMetaDataMultiFrame(instance) {
return instances;
}

convertDICOMToJSON(studyDirectory, urlPrefix, outputPath);
convertDICOMToJSON(studyDirectory, urlPrefix, outputPath, scheme);
17 changes: 12 additions & 5 deletions extensions/default/src/DicomJSONDataSource/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ const findStudies = (key, value) => {
};

function createDicomJSONApi(dicomJsonConfig) {
const { wadoRoot } = dicomJsonConfig;

const implementation = {
initialize: async ({ query, url }) => {
if (!url) {
Expand Down Expand Up @@ -89,7 +87,8 @@ function createDicomJSONApi(dicomJsonConfig) {
SeriesInstanceUID = series.SeriesInstanceUID;

series.instances.forEach(instance => {
const { url: imageId, metadata: naturalizedDicom } = instance;
const { metadata: naturalizedDicom } = instance;
const imageId = getImageId({ instance, config: dicomJsonConfig });

// Add imageId specific mapping to this data as the URL isn't necessarliy WADO-URI.
metadataProvider.addImageIdToUIDs(imageId, {
Expand Down Expand Up @@ -228,7 +227,7 @@ function createDicomJSONApi(dicomJsonConfig) {
const obj = {
...modifiedMetadata,
url: instance.url,
imageId: instance.url,
imageId: getImageId({ instance, config: dicomJsonConfig }),
...series,
...study,
};
Expand Down Expand Up @@ -257,7 +256,15 @@ function createDicomJSONApi(dicomJsonConfig) {
return imageIds;
}

displaySet.images.forEach(instance => {
const { StudyInstanceUID, SeriesInstanceUID } = displaySet;
const study = findStudies('StudyInstanceUID', StudyInstanceUID)[0];
const series = study.series.find(s => s.SeriesInstanceUID === SeriesInstanceUID);
let instances = displaySet.images;
if (series.instances.length > displaySet.images.length) {
instances = series.instances;
}

instances.forEach(instance => {
const NumberOfFrames = instance.NumberOfFrames;

if (NumberOfFrames > 1) {
Expand Down