OHIF / Viewers

OHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages
https://docs.ohif.org/
MIT License
3.3k stars 3.33k forks source link

Json creation in Standalone viewer #177

Closed marcheschi closed 5 years ago

marcheschi commented 6 years ago

Hi do you have a script in order to create such json files , for example from a dicom query to the pacs ? In order to create a package for a single real world study? I mean something createStudyJson.sh aet[@host[:port]] -Accnum="MystudyAccnumb" -o Mystudy.json

Thank you Paolo

MuriloSchaefer commented 6 years ago

Hi, So, we've had the same issue in my company, in order to resolve this we make a little node.js server which is responsible in get a vector of dcms files and return the formated JSON. We put it in a server because it is used by more than one application, so we keep everything separate. I'll put the code of the parser, feel free to copy and modify. We used the parseDicom from cornerstone to get the data from the dcm files. After this function, we sort the series and instances.

function parseAndInsertFile(content, url, finalJSON) {
    return new Promise(function(resolve, reject){
        var dataSet;
        //mount the JSON
        dataSet = ctrlParser.parseDicom(content);
        var fileJSON = {};
        fileJSON['transactionId'] = 'img';
        fileJSON['fileDate'] = dataSet.string('x00080012');
        fileJSON['fileTime'] = dataSet.string('x00080013');
        fileJSON['studies'] = [];
        var study = {};

        study['studyInstanceUid'] = dataSet.string('x0020000d');
        study['studyDescription'] = dataSet.string('x00081030');
        study['studyDate'] = dataSet.string('x00080020');
        study['studyTime'] = dataSet.string('x00080030');

        var patient = {}
        patient['patientName'] = dataSet.string('x00100010');
        var age = dataSet.string('x00101010');
        var units = ((age.slice(-1) == 'Y')? 'Anos': (age.slice(-1) == 'M')?'Meses':'Dias');
        patient['patientAge'] = age.slice(0,3) + ' ' + units;
        patient['patientAllergies'] = dataSet.string('x00102110')
        patient['patientBirthDate'] = dataSet.string('x00100030');
        patient['patientId'] = dataSet.string('x00100020');
        patient['patientSex'] = dataSet.string('x00100040');
        study['patient'] = patient;

        var equipament = {};
        equipament['manufacturer'] = dataSet.string('x00080070');
        equipament['model'] = dataSet.string('x00081090');
        equipament['stationName'] = dataSet.string('x00081010');
        equipament['AETitle'] = dataSet.string('x00020016');
        equipament['institutionName'] = dataSet.string('x00080080');
        equipament['softwareVersion'] = dataSet.string('x00181020');
        equipament['implementationVersionName'] = dataSet.string('x00020013');
        study['equipament'] = equipament;

        var physician = {}
        physician['name'] = dataSet.string('x00080090');
        physician['identification'] = dataSet.string('x00080092');

        study['physician'] = physician

        study['seriesList'] = [];
        var serie = {};
        serie['seriesDescription'] = dataSet.string('x0008103e');
        serie['seriesInstanceUid'] = dataSet.string('x0020000e');
        serie['seriesBodyPart'] = dataSet.string('x00180015');
        serie['seriesNumber'] = dataSet.string('x00200011');
        serie['seriesDate'] = dataSet.string('x00080021');
        serie['seriesTime'] = dataSet.string('x00080031');
        serie['seriesModality'] = dataSet.string('x00080060');
        serie['instances'] = [];

        var instance = {};
        instance['columns'] = dataSet.uint16('x00280011');
        instance['rows'] = dataSet.uint16('x00280010');
        instance['instanceNumber'] = (dataSet.string('x00200013'));
        instance['acquisitionNumber'] = dataSet.string('x00200012');
        instance['photometricInterpretation'] = dataSet.string('x00280004');
        instance['bitAllocated'] = dataSet.uint16('x00280100');
        instance['bitsStored'] = dataSet.uint16('x00280101');
        instance['pixelRepresentation'] = dataSet.uint16('x00280103');
        instance['samplesPerPixel'] = dataSet.uint16('x00280002');
        instance['pixelSpacing'] = dataSet.string('x00280030');
        instance['highBit'] = dataSet.uint16('x00280102');
        instance['rescaleSlope'] = dataSet.string('x00281053');
        instance['rescaleIntercept'] = dataSet.string('x00281052');
        instance['imageOrientationPatient'] = dataSet.string('x00200037');
        instance['imagePositionPatient'] = dataSet.string('x00200032');
        instance['imageType'] = dataSet.string('x00080008');
        instance['sopInstanceUid'] = dataSet.string('x00080018');
        url = url.replace('http', 'wadouri')
        instance['url'] = url;

        serie['instances'].push(instance);
        study['seriesList'].push(serie);
        fileJSON['studies'].push(study);
        //console.log(dataSetJSON);

        //Insert
        if(isEmpty(finalJSON)){
            //primeiro arquivo
            finalJSON = fileJSON;
            finalJSON['transactionId'] = finalJSON.studies[0].studyDescription;
        } else {
            //já foi inserido um estudo
            var isValid = validateFile(fileJSON, finalJSON);
            if (isValid){
                //Insere o DICOM na sua respectiva serie
                var fileStudy = fileJSON.studies[0];
                var fileSerie = fileJSON.studies[0].seriesList[0];
                var fileInstance = fileSerie.instances[0];

                var studyIndex = finalJSON.studies.findIndex(function(study, i){
                    return study.studyInstanceUid === fileStudy.studyInstanceUid;
                })
                if (studyIndex >=0){
                    //estudo já existe
                    var serieIndex = finalJSON.studies[studyIndex].seriesList.findIndex(function(serie, i){
                        return serie.seriesInstanceUid === fileSerie.seriesInstanceUid;
                    });
                    if (serieIndex >= 0 ){
                        //já há instancias desta serie inclusas, incluindo mais uma
                        finalJSON.studies[studyIndex].seriesList[serieIndex].instances.push(fileInstance);
                    }else{
                        //primeira instancia desta serie
                        finalJSON.studies[studyIndex].seriesList.push(fileSerie);
                    }
                } else {
                    finalJSON.studies.push(fileStudy);
                }
            }
        }
        resolve(finalJSON);
    });
};
marcheschi commented 6 years ago

Hi This is a very interesting approach. I'll try to implement something like this . Thank you for the code Paolo

swederik commented 6 years ago

Hi guys,

The JSON approach was initially an experiment when the standalone viewer was created. I honestly didn't expect so many people to start using it. I think we want to move towards something more standard for launching the viewer, like the DICOM JSON response to WADO-RS RetrieveMetadata for example.

For example, if you run this (sorry @pieper for using your server):

curl -H 'Accept: application/json' quantome.org:8080/dcm4chee-arc/aets/DCM4CHEE/rs/studies/1.3.12.2.14.19.81.4.1402019790780585883468456314383507/metadata | python -m json.tool

(remove the | python stuff if you don't want it pretty-printed)

you will get the DICOM JSON example.

We might also consider supporting the "Normalized DICOM JSON" approach as well, since it's a bit less verbose than typical DICOM JSON. @pieper - I can't seem to find the live example for that, so if you have it feel free to post it.

Some open questions (up for discussion):

The best option would be that we would look in the users localstorage/cookies for an existing token (e.g. from Keycloak) and use that when making all requests. For cases where no token is already present, it could be passed in the query parameters. The standalone viewer has no logic to do token refreshing, though. This might be fine temporarily, but it will be an issue if users need to save results.

pieper commented 6 years ago

@swederik no problem using the quantome.org server for demos (of course there's no uptime assurance for it!).

To get an idea of "NDD" format see here: https://github.com/pieper/dcmjs/wiki/NDD-Format

To try it on your data, you can drag and drop dicom files to this example page:

https://pieper.github.io/dcmjs/examples/display/index.html

(There's also a button to download sample data from a public server but if you click it be patient since it takes a minute or so to download).

By the way, it's been on my TODO list for a while to provide some node-based command line tools as part of the DCMJS npm package but I haven't had a chance to work on it. PRs welcome 😄

swederik commented 6 years ago

For what it's worth, here's another script to make the JSON files, courtesy of the SAKE Viewer team:

https://github.com/capstone-mgh/SAKE/blob/master/parsemetadata.py#L1

marcheschi commented 6 years ago

Very interesting Thank you Paolo

swederik commented 6 years ago

And here's another one, which just adds the file stuff on top of @MuriloSchaefer's script: https://gist.github.com/swederik/b7901bd933e6497720783e532616eec0

johannamussolini commented 5 years ago

Hi! I'm trying to run this script (https://gist.github.com/swederik/b7901bd933e6497720783e532616eec0) to generate the JSON file from my DICOM file but when trying to use it from the Standalone Viewer the following error is prompt in my browser:

Error: loadImageFromImageLoader: no image loader for imageId at loadImageFromImageLoader (ohif_cornerstone.js?hash=b2d4f3d201840adb2975e3fbc1f00e4786b64831:6133) at Module.loadAndCacheImage (ohif_cornerstone.js?hash=b2d4f3d201840adb2975e3fbc1f00e4786b64831:6199)

This is the JSON that I was able to generate from my DCM file using the above JS script { "transactionId": "study", "studies": [ { "studyInstanceUid": "1.2.826.0.1.3680043.2.1208.154494870514920191158835635", "studyDate": "20180817", "studyTime": "080835.000000", "patientName": "PERES PEPE", "patientBirthDate": "20050226", "patientId": "NO ID", "patientSex": "F", "seriesList": [ { "seriesInstanceUid": "1.2.826.0.1.3680043.2.1208.254494870514920191158835642", "seriesBodyPart": "SPINE LUMBAR", "seriesNumber": "0001", "seriesDate": "20180817", "seriesTime": "080835.000000", "seriesModality": "OT", "instances": [ { "columns": 3610, "rows": 4400, "instanceNumber": "0001", "photometricInterpretation": "MONOCHROME2", "bitAllocated": 16, "bitsStored": 12, "pixelRepresentation": 0, "samplesPerPixel": 1, "highBit": 11, "rescaleSlope": "1.00", "rescaleIntercept": "0.00", "imageType": "ORIGINAL\PRIMARY\OTHER", "sopInstanceUid": "1.2.826.0.1.3680043.2.1208.354494870514920191158835644", "url": "file://Users/johannamussolini/code/Viewers/StandaloneViewer/StandaloneViewer/private/testData/examples/1.2.826.0.1.3680043.2.1208.154494870514920191158835635/1.2.826.0.1.3680043.2.1208.254494870514920191158835642/1.2.826.0.1.3680043.2.1208.354494870514920191158835644.dcm" } ] } ] } ] }

Is there any problem with the DICOM image file? Is it not supported? How can I know the ImageId? Any help would be really appreciated! thanks!

MuriloSchaefer commented 5 years ago

the protocol that you used in your url is 'file', try to use the 'dicomweb' protocol. So the file url would be something like: 'dicomweb://Users/johannamussolini/code...'

johannamussolini commented 5 years ago

that's because my file is located locally and I'm running the Standalone viewer locally... do you think that's the issue? should I've to upload my images in the web so I can generate the JSON with an URL?

MuriloSchaefer commented 5 years ago

you can start a local server and then you can call the url like: "dicomweb:/localhost/folder1...".

I used the npm library called http-server. So to start a server is like: http-server . -p 5000 --cors this way you start a server in some folder using the port 5000

this issue is related to the protocol that you're using.

johannamussolini commented 5 years ago

oh ok, so I did that, started the server and updated the url parameter in my JSON file: "url": "dicomweb:/localhost:5000/1.2.826.0.1.3680043.2.1208.354494870514920191158835644.dcm" , but now the error is: "An error occurred when loading image: dicomweb:/localhost:5000/1.2.826.0.1.3680043.2.1208.354494870514920191158835644.dcm Details: [object: Object]"

So, I'm not sure, maybe the JSON was wrongly generated and for some reason the StandaloneViewer cannot display it?

Thanks again!

MuriloSchaefer commented 5 years ago

can you find out what these details are?

johannamussolini commented 5 years ago

I've just figured out...It was a cosmetic issue in the url you sent me above,... my bad sorry: the correct url has double / like this, and now the file was loaded:

dicomweb://localhost:5000/1.2.826.0.1.3680043.2.1208.354494870514920191158835644.dcm

worked great! thanks so much for your help!

johannamussolini commented 5 years ago

Just another concern... My DICOM files are generated under a folder which have this structure: 1.2.826.0.1.3680043.2.1208.1544948705149201921111140813 (root folder) -> 1.2.826.0.1.3680043.2.1208.25449487051492019211111426 --> 1.2.826.0.1.3680043.2.1208.35449487051492019211111427.dcm -> 1.2.826.0.1.3680043.2.1208.25449487051492019211111435 --> 1.2.826.0.1.3680043.2.1208.35449487051492019211111435.dcm and so on.... several others folders inside

When I was trying to use the script (https://gist.github.com/swederik/b7901bd933e6497720783e532616eec0) it seems that only takes DCM files under only one folder (maybe I did something wrong)... so I cannot get this script to generate the JSON correctly ...Anyway... what I tried to do is to put all DCM files under the root folder and ran the script to see how the JSON would be generated... however it generated only one serie per file and the frames are not displayed... so I'm not sure what I'm doing wrong.

Again, any guidance would be really appreciated. Thanks!

johannamussolini commented 5 years ago

@MuriloSchaefer: disregard,.. I was able to do it by using this:

const readdir = require('readdir-enhanced');

instead of fs.readdir and changing some code by:

readdir(testFolder, {filter: myFilter, deep: true, basePath: absPath}, function(err, files){ ... script code here with some changes } function myFilter(stats) { return stats.isFile() && (stats.path.replace(/^.*?.([a-zA-Z0-9]+)$/, "$1") === 'dcm'); }

by doing this I just get all .dcm files (which are under 'testFolder' and its subfolders recursively and I can just parse those files only).

Thanks for your help!

dannyrb commented 5 years ago

We're getting ready to formally announce version 2 of the OHIF Viewer. After the announcement, version 1 issues will receive lower priority support. As this issue is a bit stale, I'm going to close it for now.

miaoshenmao commented 2 years ago

Hi, So, we've had the same issue in my company, in order to resolve this we make a little node.js server which is responsible in get a vector of dcms files and return the formated JSON. We put it in a server because it is used by more than one application, so we keep everything separate. I'll put the code of the parser, feel free to copy and modify. We used the parseDicom from cornerstone to get the data from the dcm files. After this function, we sort the series and instances.

function parseAndInsertFile(content, url, finalJSON) {
  return new Promise(function(resolve, reject){
      var dataSet;
      //mount the JSON
      dataSet = ctrlParser.parseDicom(content);
      var fileJSON = {};
      fileJSON['transactionId'] = 'img';
      fileJSON['fileDate'] = dataSet.string('x00080012');
      fileJSON['fileTime'] = dataSet.string('x00080013');
      fileJSON['studies'] = [];
      var study = {};

      study['studyInstanceUid'] = dataSet.string('x0020000d');
      study['studyDescription'] = dataSet.string('x00081030');
      study['studyDate'] = dataSet.string('x00080020');
      study['studyTime'] = dataSet.string('x00080030');

      var patient = {}
      patient['patientName'] = dataSet.string('x00100010');
      var age = dataSet.string('x00101010');
      var units = ((age.slice(-1) == 'Y')? 'Anos': (age.slice(-1) == 'M')?'Meses':'Dias');
      patient['patientAge'] = age.slice(0,3) + ' ' + units;
      patient['patientAllergies'] = dataSet.string('x00102110')
      patient['patientBirthDate'] = dataSet.string('x00100030');
      patient['patientId'] = dataSet.string('x00100020');
      patient['patientSex'] = dataSet.string('x00100040');
      study['patient'] = patient;

      var equipament = {};
      equipament['manufacturer'] = dataSet.string('x00080070');
      equipament['model'] = dataSet.string('x00081090');
      equipament['stationName'] = dataSet.string('x00081010');
      equipament['AETitle'] = dataSet.string('x00020016');
      equipament['institutionName'] = dataSet.string('x00080080');
      equipament['softwareVersion'] = dataSet.string('x00181020');
      equipament['implementationVersionName'] = dataSet.string('x00020013');
      study['equipament'] = equipament;

      var physician = {}
      physician['name'] = dataSet.string('x00080090');
      physician['identification'] = dataSet.string('x00080092');

      study['physician'] = physician

      study['seriesList'] = [];
      var serie = {};
      serie['seriesDescription'] = dataSet.string('x0008103e');
      serie['seriesInstanceUid'] = dataSet.string('x0020000e');
      serie['seriesBodyPart'] = dataSet.string('x00180015');
      serie['seriesNumber'] = dataSet.string('x00200011');
      serie['seriesDate'] = dataSet.string('x00080021');
      serie['seriesTime'] = dataSet.string('x00080031');
      serie['seriesModality'] = dataSet.string('x00080060');
      serie['instances'] = [];

      var instance = {};
      instance['columns'] = dataSet.uint16('x00280011');
      instance['rows'] = dataSet.uint16('x00280010');
      instance['instanceNumber'] = (dataSet.string('x00200013'));
      instance['acquisitionNumber'] = dataSet.string('x00200012');
      instance['photometricInterpretation'] = dataSet.string('x00280004');
      instance['bitAllocated'] = dataSet.uint16('x00280100');
      instance['bitsStored'] = dataSet.uint16('x00280101');
      instance['pixelRepresentation'] = dataSet.uint16('x00280103');
      instance['samplesPerPixel'] = dataSet.uint16('x00280002');
      instance['pixelSpacing'] = dataSet.string('x00280030');
      instance['highBit'] = dataSet.uint16('x00280102');
      instance['rescaleSlope'] = dataSet.string('x00281053');
      instance['rescaleIntercept'] = dataSet.string('x00281052');
      instance['imageOrientationPatient'] = dataSet.string('x00200037');
      instance['imagePositionPatient'] = dataSet.string('x00200032');
      instance['imageType'] = dataSet.string('x00080008');
      instance['sopInstanceUid'] = dataSet.string('x00080018');
      url = url.replace('http', 'wadouri')
      instance['url'] = url;

      serie['instances'].push(instance);
      study['seriesList'].push(serie);
      fileJSON['studies'].push(study);
      //console.log(dataSetJSON);

      //Insert
      if(isEmpty(finalJSON)){
          //primeiro arquivo
          finalJSON = fileJSON;
          finalJSON['transactionId'] = finalJSON.studies[0].studyDescription;
      } else {
          //já foi inserido um estudo
          var isValid = validateFile(fileJSON, finalJSON);
          if (isValid){
              //Insere o DICOM na sua respectiva serie
              var fileStudy = fileJSON.studies[0];
              var fileSerie = fileJSON.studies[0].seriesList[0];
              var fileInstance = fileSerie.instances[0];

              var studyIndex = finalJSON.studies.findIndex(function(study, i){
                  return study.studyInstanceUid === fileStudy.studyInstanceUid;
              })
              if (studyIndex >=0){
                  //estudo já existe
                  var serieIndex = finalJSON.studies[studyIndex].seriesList.findIndex(function(serie, i){
                      return serie.seriesInstanceUid === fileSerie.seriesInstanceUid;
                  });
                  if (serieIndex >= 0 ){
                      //já há instancias desta serie inclusas, incluindo mais uma
                      finalJSON.studies[studyIndex].seriesList[serieIndex].instances.push(fileInstance);
                  }else{
                      //primeira instancia desta serie
                      finalJSON.studies[studyIndex].seriesList.push(fileSerie);
                  }
              } else {
                  finalJSON.studies.push(fileStudy);
              }
          }
      }
      resolve(finalJSON);
  });
};

cloud you share me your full html/js files, thank you very much! 2684717679@qq.com

LumiProj commented 1 year ago

Dear OHIF Viewer team,

I am currently using the OHIF Viewer in my web application and I am experiencing an issue with playing all frames of a DICOM file when loading studies through my ASP.NET Core API using JSON. While this functionality works fine in local mode, it seems to be malfunctioning when the studies are loaded via my API.

I have followed the official documentation of OHIF Viewer closely and cannot seem to identify the missing segment causing the issue.

I would greatly appreciate your guidance in resolving this matter. Could you please advise me on what I may be missing or what steps I can take to troubleshoot this issue?

Thank you for your assistance.

sedghi commented 3 months ago

We recently added several recipes for implementing authentication with Keycloak in OHIF. You can find them here:

https://docs.ohif.org/deployment/user-account-control

miaoshenmao commented 3 months ago

这是来自QQ邮箱的假期自动回复邮件。   您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。