Gzip as single archive before routing

Hi Friends,

I need some help. Before I ask my question, let me setup the scenario. I am using OrthanC as a mini-PACS to serve studies locally in a hospital. It also forwards these studies to a cloud based PACS server (which is also an OrthanC instance).

Since the internet connection is a little iffy at the hospital, I am trying to get the mini-PACS to achieve maximum compression before sending out the studies. I was thinking of combining the J2K compression lua script + sendmodality function to send these studies out (script below)

It would be great to also be able to accomplish these before sending them out:
1. Gzip as a single archive. (Can the cloud-PACS OrthanC accept gzipped studies?)
2. Lossy compression - I have found a value of "-q 85" (approx 1:8 compression) works well with no noticeable loss in quality. This was confirmed by a certified Radiologist.

This is the code I have until now.

function OnStoredInstance(instanceId, tags, metadata, origin)
   -- Do not compress twice the same file
   if origin['RequestOrigin'] ~= 'Lua' then

      -- Retrieve the incoming DICOM instance from Orthanc
      local dicom = RestApiGet('/instances/' .. instanceId .. '/file')

      -- Write the DICOM content to some temporary file
      local uncompressed = instanceId .. '-uncompressed.dcm'
      local target = assert(io.open(uncompressed, 'wb'))
      target:write(dicom)
      target:close()

      -- Compress to JPEG2000 using gdcm
      local compressed = instanceId .. '-compressed.dcm'
      os.execute('gdcmconv --j2k ' .. uncompressed .. ' ' .. compressed)

      -- Generate a new SOPInstanceUID for the JPEG2000 file, as
      -- gdcmconv does not do this by itself
      os.execute('dcmodify --no-backup -gin ' .. compressed)

      -- Read the JPEG2000 file
      local source = assert(io.open(compressed, 'rb'))
      local jpeg2k = source:read("*all")
      source:close()

      -- Upload the JPEG2000 file and remove the uncompressed file
      RestApiPost('/instances', jpeg2k)
      RestApiDelete('/instances/' .. instanceId)

      -- Remove the temporary DICOM files
      os.remove(uncompressed)
      os.remove(compressed)
   end

   SendToModality(instanceId, 'sample')

end

Hi Ayush,

Unfortunately, Orthanc will not accept gzip archives. So your best option is probably to just compress images as you suggest.

Hi Alain,

Thanks for your response. Does OrthanC accept J2K lossy transfer syntax? Because after compressing via os.execute('gdcmconv --j2k --lossy -q 85' .. uncompressed .. ' ' .. compressed), my cloud-PACS OrthanC was unable to accept the files.

Thanks,
Ayush

you should add -U to your gdcmconv command line (don’t ask me why :slight_smile: )

Lol. I was just going to ask why. Lemme try it out and post my results.

Btw, I love the Osimis viewer and can't wait for the Lify product to go live. Please do let me know if you need someone to help beta test it. I have already signed up for the updates, but haven't gotten any yet :frowning:

@Alain - thanks for the tip. It worked.

@All: I can confirm that the -U option is working. If anyone wants the final code, it is pasted below. Once again, the code compresses the incoming study to J2K lossy with a quality parameter of 85 (approx 1:8 compression) and then routes it to a Storage SCP (e.g. PACS Server). If anyone is planning to use this code, please be aware that lossy compression must be used extremely carefully for medical diagnosis. Proceed at your own risk. With that out of the way. Here's the code:

function OnStoredInstance(instanceId, tags, metadata, origin)
   
      -- Do not compress twice the same file
      if origin['RequestOrigin'] ~= 'Lua' then

         -- Retrieve the incoming DICOM instance from Orthanc
         local dicom = RestApiGet('/instances/' .. instanceId .. '/file')

         -- Write the DICOM content to some temporary file
         local uncompressed = instanceId .. '-uncompressed.dcm'
         local target = assert(io.open(uncompressed, 'wb'))
         target:write(dicom)
         target:close()

         -- Compress to JPEG2000 using gdcm
         local compressed = instanceId .. '-compressed.dcm'
         -- os.execute('gdcmconv --j2k ' .. uncompressed .. ' ' .. compressed)
         os.execute('gdcmconv -U --j2k --lossy -q 85 ' .. uncompressed .. ' ' .. compressed)

         -- Generate a new SOPInstanceUID for the JPEG2000 file, as
         -- gdcmconv does not do this by itself
         os.execute('dcmodify --no-backup -gin ' .. compressed)

         -- Read the JPEG2000 file
         local source = assert(io.open(compressed, 'rb'))
         local jpeg2k = source:read("*all")
         source:close()

         -- Upload the JPEG2000 file and remove the uncompressed file
         RestApiPost('/instances', jpeg2k)
         RestApiDelete('/instances/' .. instanceId)

         -- Remove the temporary DICOM files
         os.remove(uncompressed)
         os.remove(compressed)
      end

      if origin['RequestOrigin'] == 'Lua' then
         PrintRecursive(origin)
         SendToModality(instanceId, 'sample')
      end

end

Hello,

Ayush, thanks for sharing your tips about lossy compression!

It would be great to also be able to accomplish these before sending them out:

  1. Gzip as a single archive. (Can the cloud-PACS OrthanC accept gzipped studies?)

Coming back to the gzip-related part of question, besides the possibility of using an external script that decompresses the gzip file before sending it to Orthanc, this feature could also be implemented as a plugin to Orthanc:

https://orthanc.chu.ulg.ac.be/book/developers/creating-plugins.html

This plugin would re-implement the URI “/instances/” against HTTP POST using “OrthancPluginRegisterRestCallback()”. When a new file is received, it would check the magic headers of the incoming file to know whether this is a gzip file:

  • If this is a gzip file, the plugin would uncompress it (by calling “OrthancPluginBufferCompression()”) and upload the uncompressed buffer to Orthanc (with “OrthancPluginRestApiPost()” against built-in URI “/instances”).
  • If this is not a gzip file, the plugin would simply forward it to the default handler (with “OrthancPluginRestApiPost()” against built-in URI “/instances”).

The advantage of using a plugin is that you would not need an external script, making the process fully transparent to the end user within Orthanc Explorer.

Note that the same strategy could be used to uncompress ZIP files on-the-fly: The only difference would be that the decompression could lead to several DICOM files that would be uploaded one by one to the Orthanc store.

HTH,
Sébastien-