Orthanc convert YBR to RGB but does not change metadata

Using Orthanc 1.12 I see that my test pattern data (two samples in the link below) which are natively JPEG Baseline (Process 1) (1.2.840.10008.1.2.4.50) and YBR422 and YBR_FULL, gets returned by Orthanc in 1.2.840.10008.1.2.1 with RGB pixelData (so apparently Orthanc converts it), while the metadata photometricInterpretation is still YBR.

Is there any explanation for this? My code has a step for color conversion, but I can’t find any way to identify the pixelData colorspace as the metadata is wrong.

Thanks!

Test Pattern Colors: Dropbox - color-test - Simplify your life

Based on my understanding it should return in 1.2.1 but not touch the pixelData, but if it does it should at least edit the header too

Hi Alireza,

I have just tested it here first without any IngestTranscoding configured on Orthanc side: they both remain in JPEG Baseline and the Photometric stays unchanged (which is expected).

Then, I have configured IngestTranscoding to 1.2.840.10008.1.2.1 and, once ingested into Orthanc, their PhotometricInterpretation is updated to RGB and they still display fine. I have also played with the GDCM plugin configuration but always got the same result.

Could you define your setup and test more in details ?

[EDIT]: note that I realized that I tested with the mainline version which includes this patch related to YBR to RGB conversion.

Best regards,

Alain.

hmm interesting, here are my configs

docker-compose.yml

version: "3.5"

services:
  orthanc:
    image: jodogne/orthanc-plugins:1.12.0
    hostname: orthanc
    volumes:
      # Config
      - ./config/orthanc.json:/etc/orthanc/orthanc.json:ro
      # Persist data
      - ./volumes/orthanc-db/:/var/lib/orthanc/db/
    ports:
      - "4242:4242" # DICOM
      - "8042:8042" # Web
    restart: unless-stopped
  nginx:
    image: nginx:latest
    volumes:
      - ./config/nginx.conf:/etc/nginx/nginx.conf
    ports:
      - "80:80" #ngnix proxy
    depends_on:
      - orthanc

and orthanc.json

{
  "Name": "Orthanc inside Docker",
  "StorageDirectory": "/var/lib/orthanc/db",
  "IndexDirectory": "/var/lib/orthanc/db",
  "StorageCompression": false,
  "MaximumStorageSize": 0,
  "MaximumPatientCount": 0,
  "LuaScripts": [],
  "Plugins": ["/usr/share/orthanc/plugins", "/usr/local/share/orthanc/plugins"],
  "ConcurrentJobs": 2,
  "HttpServerEnabled": true,
  "HttpPort": 8042,
  "HttpDescribeErrors": true,
  "HttpCompressionEnabled": true,
  "DicomServerEnabled": true,
  "DicomAet": "ORTHANC",
  "DicomCheckCalledAet": false,
  "DicomPort": 4242,
  "DefaultEncoding": "Latin1",
  "DeflatedTransferSyntaxAccepted": true,
  "JpegTransferSyntaxAccepted": true,
  "Jpeg2000TransferSyntaxAccepted": true,
  "JpegLosslessTransferSyntaxAccepted": true,
  "JpipTransferSyntaxAccepted": true,
  "Mpeg2TransferSyntaxAccepted": true,
  "RleTransferSyntaxAccepted": true,
  "UnknownSopClassAccepted": false,
  "DicomScpTimeout": 30,

  "RemoteAccessAllowed": true,
  "SslEnabled": false,
  "SslCertificate": "certificate.pem",
  "AuthenticationEnabled": false,
  "RegisteredUsers": {
    "test": "test"
  },
  "DicomModalities": {},
  "DicomModalitiesInDatabase": false,
  "DicomAlwaysAllowEcho": true,
  "DicomAlwaysAllowStore": true,
  "DicomCheckModalityHost": false,
  "DicomScuTimeout": 10,
  "OrthancPeers": {},
  "OrthancPeersInDatabase": false,
  "HttpProxy": "",

  "HttpVerbose": true,

  "HttpTimeout": 10,
  "HttpsVerifyPeers": true,
  "HttpsCACertificates": "",
  "UserMetadata": {},
  "UserContentType": {},
  "StableAge": 60,
  "StrictAetComparison": false,
  "StoreMD5ForAttachments": true,
  "LimitFindResults": 0,
  "LimitFindInstances": 0,
  "LimitJobs": 10,
  "LogExportedResources": false,
  "KeepAlive": true,
  "TcpNoDelay": true,
  "HttpThreadsCount": 50,
  "StoreDicom": true,
  "DicomAssociationCloseDelay": 5,
  "QueryRetrieveSize": 10,
  "CaseSensitivePN": false,
  "LoadPrivateDictionary": true,
  "Dictionary": {},
  "SynchronousCMove": true,
  "JobsHistorySize": 10,
  "SaveJobs": true,
  "OverwriteInstances": false,
  "MediaArchiveSize": 1,
  "StorageAccessOnFind": "Always",
  "MetricsEnabled": true,

  "DicomWeb": {
    "Enable": true,
    "Root": "/dicom-web/",
    "EnableWado": true,
    "WadoRoot": "/wado",
    "Host": "127.0.0.1",
    "Ssl": false,
    "StowMaxInstances": 10,
    "StowMaxSize": 10,
    "QidoCaseSensitive": false
  }
}

and I have a nginx.conf too which I don’t think is important

You do not have IngestTranscoding configured so I assume that you are observing the issue when retrieving data right ?

Could you give a sample request that you use ?

No I don’t have that but based no the docs here the default behavior is to return the original transcoding so I expected not have to add anything new. Am i missing something? what configuration makes Orthanc to not re-transcode again?

Here is the request header

GET /dicom-web/studies/1.2.826.0.1.3680043.2.1125.7FCE05E9-ECED-4694-AB61-9F1411AC07B2/series/1.2.826.0.1.3680043.2.1125.B1AA12E9-4082-4E09-A4DF-638DFAC2601F/instances/1.2.276.0.7230010.3.1.4.253549293.26360.1555332281.164/frames/1 HTTP/1.1
Accept: multipart/related; type="application/octet-stream"
Accept-Encoding: gzip, deflate, br
Accept-Language: en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7
Cache-Control: no-cache
Connection: keep-alive
Host: localhost
Origin: http://localhost:3000
Pragma: no-cache
Referer: http://localhost:3000/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
sec-ch-ua: "Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"

and here is the response header

HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Wed, 24 May 2023 15:36:06 GMT
Content-Type: multipart/related; type="application/octet-stream; transfer-syntax=1.2.840.10008.1.2.1"; boundary=8cb27f48-2a60-4886-a802-5dbb15679580-b6c7ed73-49f4-403e-8ddb-f205c1a13
Content-Length: 100231
Connection: keep-alive
Content-Encoding: gzip
Access-Control-Allow-Origin: *
Access-Control-Allow_Credentials: true
Access-Control-Allow-Headers: Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range
Access-Control-Allow-Methods: GET,POST,OPTIONS,PUT,DELETE,PATCH

and here is the preview of the retrieved
image

Hi Alireza,

While debugging, I found this comment which explains why Orthanc returns an Explicit content:

  /**
   * Up to release 1.4 of the DICOMweb plugin, WADO-RS
   * RetrieveInstance, RetrieveSeries and RetrieveStudy did *NOT*
   * transcode if no transer syntax was explicitly provided. This was
   * because the DICOM standard didn't specify a behavior in this case
   * up to DICOM 2016b:
   * http://dicom.nema.org/medical/dicom/2016b/output/chtml/part18/sect_6.5.3.html
   *
   * However, starting with DICOM 2016c, it is explicitly stated that
   * "If transfer-syntax is not specified in the dcm-parameters the
   * origin server shall use the Explicit VR Little Endian Transfer
   * Syntax "1.2.840.10008.1.2.1" for each Instance":
   * http://dicom.nema.org/medical/dicom/2016c/output/chtml/part18/sect_6.5.3.html
   * 
   * As a consequence, starting with release 1.5 of the DICOMweb
   * plugin, transcoding to "Little Endian Explicit" takes place by
   * default. If this transcoding is not desirable, the "Accept" HTTP
   * header can be set to
   * "multipart/related;type=application/dicom;transfer-syntax=*" (note
   * the asterisk "*") in order to prevent transcoding. The same
   * convention is used by the Google Cloud Platform:
   * https://cloud.google.com/healthcare/docs/dicom
   **/

So, as soon as I configure the Accept headers to multipart/related;type=application/dicom;transfer-syntax=1.2.840.10008.1.2.4.50 I get the non-transcoded file in JPEG.

If I set it to multipart/related;type=application/dicom;transfer-syntax=*, I also get the non-transcoded file in JPEG.

Hope this helps,

Alain.

Interesting, I’m fine with the server returning the 1.2.1 too but as I said the problem is that it converts the pixelData but does not modify the metadata for the photometric. I think the issue is orthanc understands it needs to return in 1.2.1 (based on the big comment above), uncompress the pixelData, but does not touch the photometric, which as a result make it incorrect since pixelData has become RGB

Hi Alireza,

I digged into the code to finally find out that Orthanc implements the same behavior as dcmdjpeg (and the same as GDCM) that is converting YBR to RGB colorspace:

Sorry, but I think I’m only starting to understand your concern:

  • when you call /studies/../instances/../frames/1, you only get the pixel data from frame 1 and you assume that it is in YBR because YBR is what you got from a previous call to /studies/../instances/metadata
  • while if you call /studies/../instances/{id}, you get a DICOM file with the PhotometricInterpretation correctly set to RGB

From the standard, I understand that we shall return the Photometric Interpretation of the stored data since you are not providing a target transfer-syntax when calling /metadata.

So, indeed, Orthanc shall try not to change the colorspace when you call the ../frames/.. URI since you have no way to know what the colorspace actually is.

I’ll look into it but that might not be an easy change since it’s not the default DCMTK behavior.

Best regards,

Alain.

1 Like

Great debugging!
Yeah you got it right, basically the problem is consistency of pixelData and PhotometricInterpretation of decompressed pixelData., for the viewers, it is totally fine if Orthanc doesn’t decompress (hence no conversion of YBR) AND it is totally fine if Orthanc returns decompressed (I understand that the default is to convert to RGB) AND RGB in metadata (since that is the only way to find out what is the nature of pixelData)

Hi Alireza,

I have finally found an easy way to disable this automatic conversion. It has been implemented in this commit.

Best regards,

Alain.

2 Likes

Sounds great. Sorry I’m not familiar with Orthanc versioning, which version will this be included?

That will be part of 1.12.1 but we don’t know the release date yet.

If the build goes fine, you should have it in osimis/orthanc:master-unstable by tomorrow morning.

1 Like

Hi everyone,

Unfortunately, I had to revert this change since it was breaking the behavior of "IngestTranscoding".
@alireza: Sorry for that ! Any possibility for you to call ../frames/0/rendered instead of ../frames/0 ?

I have added the issue in our “mid-term” TODO :frowning:

Best regards,

Alain.

Notes mainly for myself for further investigation:

In case we don’t want to modify the transcoding behavior and return the new PhotometricInterpretation in /metadata:

DCMTK provides a method to get the PhotometricInterpreation of the decompressed image:

OFCondition DcmPixelData::getDecompressedColorModel(
    DcmItem *dataset,
    OFString &decompressedColorModel)

We could call it when providing the /metadata (but that’s really not easy to call that code from the DicomWeb plugin). Here’s a possible solution:

  • when the DicomWeb plugin requests the /tags for the /metadata route, it could specify the accepted transfer-syntax header
  • Orthanc core would detect if decompression would happen based on the initial transfer syntax and the accept transfer-syntax
  • Orthanc core would then call getDecompressedColorModel and modify the PhotometricInterpretation of the returned /tags
  • Clients would have to specify the expected transfer-syntax when requesting the /metadata → not sure this is standard compliant (of course, if not specified, Explicit TS would be choosen)
  • this method decodes the first frame to check if its PhotometricInterpretation has been modified → not optimal at all in terms of performance !

Tried to avoid the color space conversion during transcoding

No luck so far since it seems in DCMTK we can only register one global JPEG decoder and therefore we have to choose if it performs color space conversion or not at Orthanc level ! (reference to the question asked in the DCMTK forum)

Note: The DicomWeb standard says :

Some Attributes of the DICOM Dataset may depend on the Representation, not the Resource, especially those that describe the Pixel Data, so may differ from those encoded in a particular retrieved Representation. E.g., Photometric Interpretation (0028,0004) may differ depending on the Transfer Syntax.

The metadata is not supposed to change. The required behaviour on display is:

  • If the PMI is YBR, but the decoder is producing RGB, then the client must display as RGB and NOT transcode YBR
  • If the original data is encoded as JPEG, then the correct PMI is YBR of some flavour - probably YBR_FULL_422, but clients have to do the previous step and display it as RGB. The PMI ONLY matters for how the data in LEI gets sent, and doesn’t actually affect the encoding here.
  • If the PMI is YBR, and the server side decoder produces RGB, then the server MUST transcode to YBR before returning the raw (uncompressed) data.

Given these rules, it is possible to display coloured images whether retrieved as JPEG or as unencoded, with just the single encoder/decoder setting - although it may required applying a colour space transform after decoding or before encoding.

Hello everyone,

I’m having issues with US multiframe DICOM files while using OHIF plugin with latest OHIF source - all the series get displayed in wrong colorspace.

While using plugin with OHIF 3.6.0 and dicom-web data source, most of the instances get displayed correctly, except for one (which I believe is an SR generated by a Philips i33 US machine)

it seems like changes to OHIF source code since 3.6.0 are causing all the images to display wrong.

Additionally, when using dicom-web as data source, all the instances get displayed as single frames within a series. Is it possible that not all required tags (e.g. PlanarConfiguration) are passed to OHIF?

I believe the list of tags is after line 110 in Plugin source code:
https://orthanc.uclouvain.be/hg/orthanc-ohif/file/tip/Sources/Plugin.cpp


I understand that OHIF and Orthanc are separate projects, but since all of you are in this thread and this is an integration issue I thought I’d post here.

Kind Regards,

Yomarbuzz

@alainmazy
I would love to take another look at this if it’s possible, as we have received several reported issues. I’m eager to understand what you believe would be the best approach

Sample Data (two different studies):

If I drag and drop it into our local OHIF, it works fine, indicating it is not the dcm problem

If I upload it to Orthanc and use the OHIF viewer as plugin

It renders fine also, since it is the json metadata

However, connecting to the DICOM Web Orthanc we see the initial reported issue in the first comment above.

How do you suggest we address this problem? Could you ensure that the dicomweb metadata is consistent with the dicomjson you send? The plugin has no trouble rendering it as you can see, but dicom web metadata seems wrong.

References:

Hi everyone,

I have just tested this sample setup, both with the OHIF plugin in dicom-web mode (version 1.1 that includes OHIF v 3.7.0) and with a custom built version of OHIF running in a container (tested with both 3.7.0 and 3.8.0-beta36).

Everything displays fine.

Note that this part of the configuration file is the key:

  dataSources: [
    {
      ...
        acceptHeader: [ 'multipart/related; type=application/octet-stream; transfer-syntax=*']
      },
    }],

This is included in the default config in the OHIF plugin v1.1.

If you are still encountering issues, please modify the sample setup and share it with me.

HTH,

Alain.

Thanks @alainmazy It worked when I tried it.
I’m wondering if it’s beneficial to make that the default setting for all our data sources. I’d love to hear your thoughts, especially since you have more knowledge about the dicom servers than I do.