diff OrthancServer/Sources/ServerContext.cpp @ 4514:5b929e6b3c36

removal of "dicom-as-json" attachments
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 16 Feb 2021 12:18:41 +0100
parents 1f455b86b054
children a3c6678aa7b1
line wrap: on
line diff
--- a/OrthancServer/Sources/ServerContext.cpp	Fri Feb 12 12:13:19 2021 +0100
+++ b/OrthancServer/Sources/ServerContext.cpp	Tue Feb 16 12:18:41 2021 +0100
@@ -583,12 +583,14 @@
       ServerIndex::Attachments attachments;
       attachments.push_back(dicomInfo);
 
-      FileInfo jsonInfo;
-      if (true /* TODO - !area_.HasReadRange() || !hasPixelDataOffset || compression != CompressionType_DicomAsJson */)
+      FileInfo dicomUntilPixelData;
+      if (hasPixelDataOffset &&
+          (!area_.HasReadRange() ||
+           compression != CompressionType_None))
       {
-        jsonInfo = accessor.Write(dicomAsJson.toStyledString(), 
-                                  FileContentType_DicomAsJson, compression, storeMD5_);
-        attachments.push_back(jsonInfo);
+        dicomUntilPixelData = accessor.Write(dicom.GetBufferData(), pixelDataOffset, 
+                                             FileContentType_DicomUntilPixelData, compression, storeMD5_);
+        attachments.push_back(dicomUntilPixelData);
       }
 
       typedef std::map<MetadataType, std::string>  InstanceMetadata;
@@ -610,9 +612,9 @@
       {
         accessor.Remove(dicomInfo);
 
-        if (jsonInfo.IsValid())
+        if (dicomUntilPixelData.IsValid())
         {
-          accessor.Remove(jsonInfo);
+          accessor.Remove(dicomUntilPixelData);
         }
       }
 
@@ -807,61 +809,178 @@
   }
 
 
+  static void InjectEmptyPixelData(Json::Value& dicomAsJson)
+  {
+    // This is for backward compatibility with Orthanc <= 1.9.0
+    Json::Value pixelData = Json::objectValue;
+    pixelData["Name"] = "PixelData";
+    pixelData["Type"] = "Null";
+    pixelData["Value"] = Json::nullValue;
+
+    dicomAsJson["7fe0,0010"] = pixelData;
+  }
+
+  
   void ServerContext::ReadDicomAsJson(Json::Value& result,
                                       const std::string& instancePublicId,
                                       const std::set<DicomTag>& ignoreTagLength)
   {
-    if (ignoreTagLength.empty())
+    /**
+     * CASE 1: The DICOM file, truncated at pixel data, is available
+     * as an attachment (it was created either because the storage
+     * area does not support range reads, or it "StorageCompression"
+     * is enabled). Simply return this attachment.
+     **/
+    
+    FileInfo attachment;
+
+    if (index_.LookupAttachment(attachment, instancePublicId, FileContentType_DicomUntilPixelData))
     {
-      std::string tmp;
+      std::string dicom;
 
       {
-        FileInfo attachment;
-        if (index_.LookupAttachment(attachment, instancePublicId, FileContentType_DicomAsJson))
+        StorageAccessor accessor(area_, GetMetricsRegistry());
+        accessor.Read(dicom, attachment);
+      }
+
+      ParsedDicomFile parsed(dicom);
+      OrthancConfiguration::DefaultDicomDatasetToJson(result, parsed, ignoreTagLength);
+      InjectEmptyPixelData(result);
+    }
+    else
+    {
+      /**
+       * The truncated DICOM file is not stored as a standalone
+       * attachment. Lookup whether the pixel data offset has already
+       * been computed for this instance.
+       **/
+    
+      bool hasPixelDataOffset;
+      uint64_t pixelDataOffset;
+
+      {
+        std::string s;
+        if (index_.LookupMetadata(s, instancePublicId, ResourceType_Instance,
+                                  MetadataType_Instance_PixelDataOffset))
         {
-          StorageAccessor accessor(area_, GetMetricsRegistry());
-          accessor.Read(tmp, attachment);
+          hasPixelDataOffset = false;
+
+          if (!s.empty())
+          {
+            try
+            {
+              pixelDataOffset = boost::lexical_cast<uint64_t>(s);
+              hasPixelDataOffset = true;
+            }
+            catch (boost::bad_lexical_cast&)
+            {
+            }
+          }
+
+          if (!hasPixelDataOffset)
+          {
+            LOG(ERROR) << "Metadata \"PixelDataOffset\" is corrupted for instance: " << instancePublicId;
+          }
         }
         else
         {
-          // The "DICOM as JSON" summary is not available from the Orthanc
-          // store (most probably deleted), reconstruct it from the DICOM file
-          std::string dicom;
-          ReadDicom(dicom, instancePublicId);
+          // This instance was created by a version of Orthanc <= 1.9.0
+          hasPixelDataOffset = false;
+        }
+      }
+
+
+      if (hasPixelDataOffset &&
+          area_.HasReadRange() &&
+          index_.LookupAttachment(attachment, instancePublicId, FileContentType_Dicom) &&
+          attachment.GetCompressionType() == CompressionType_None)
+      {
+        /**
+         * CASE 2: The pixel data offset is known, AND that a range read
+         * can be used to retrieve the truncated DICOM file. Note that
+         * this case cannot be used if "StorageCompression" option is
+         * "true".
+         **/
+      
+        StorageAccessor accessor(area_, GetMetricsRegistry());
+        std::unique_ptr<IMemoryBuffer> dicom(
+          area_.ReadRange(attachment.GetUuid(), FileContentType_Dicom, 0, pixelDataOffset));
 
-          LOG(INFO) << "Reconstructing the missing DICOM-as-JSON summary for instance: "
-                    << instancePublicId;
-    
-          ParsedDicomFile parsed(dicom);
+        if (dicom.get() == NULL)
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+        else
+        {
+          assert(dicom->GetSize() == pixelDataOffset);
+          ParsedDicomFile parsed(dicom->GetData(), dicom->GetSize());
+          OrthancConfiguration::DefaultDicomDatasetToJson(result, parsed, ignoreTagLength);
+          InjectEmptyPixelData(result);
+        }
+      }
+      else if (ignoreTagLength.empty() &&
+               index_.LookupAttachment(attachment, instancePublicId, FileContentType_DicomAsJson))
+      {
+        /**
+         * CASE 3: This instance was created using Orthanc <=
+         * 1.9.0. Reuse the old "DICOM-as-JSON" attachment if available.
+         * This is for backward compatibility: A call to
+         * "/tools/invalidate-tags" or to one flavors of
+         * "/.../.../reconstruct" will disable this case.
+         **/
+      
+        std::string dicomAsJson;
 
-          Json::Value summary;
-          OrthancConfiguration::DefaultDicomDatasetToJson(summary, parsed);
+        {
+          StorageAccessor accessor(area_, GetMetricsRegistry());
+          accessor.Read(dicomAsJson, attachment);
+        }
 
-          tmp = summary.toStyledString();
+        if (!Toolbox::ReadJson(result, dicomAsJson))
+        {
+          throw OrthancException(ErrorCode_CorruptedFile,
+                                 "Corrupted DICOM-as-JSON attachment of instance: " + instancePublicId);
+        }
+      }
+      else
+      {
+        /**
+         * CASE 4: Neither the truncated DICOM file is accessible, nor
+         * the DICOM-as-JSON summary. We have to retrieve the full DICOM
+         * file from the storage area.
+         **/
+
+        std::string dicom;
+        ReadDicom(dicom, instancePublicId);
 
-          if (!AddAttachment(instancePublicId, FileContentType_DicomAsJson,
-                             tmp.c_str(), tmp.size()))
+        ParsedDicomFile parsed(dicom);
+        OrthancConfiguration::DefaultDicomDatasetToJson(result, parsed, ignoreTagLength);
+
+        if (!hasPixelDataOffset)
+        {
+          /**
+           * The pixel data offset was never computed for this
+           * instance, which indicates that it was created using
+           * Orthanc <= 1.9.0, or that calls to
+           * "LookupPixelDataOffset()" from earlier versions of
+           * Orthanc have failed. Try again this precomputation now
+           * for future calls.
+           **/
+          if (DicomStreamReader::LookupPixelDataOffset(pixelDataOffset, dicom) &&
+              pixelDataOffset < dicom.size())
           {
-            throw OrthancException(ErrorCode_InternalError,
-                                   "Cannot associate the DICOM-as-JSON summary to instance: " + instancePublicId);
+            index_.SetMetadata(instancePublicId, MetadataType_Instance_PixelDataOffset,
+                               boost::lexical_cast<std::string>(pixelDataOffset));
+
+            if (!area_.HasReadRange() ||
+                compressionEnabled_ != CompressionType_None)
+            {
+              AddAttachment(instancePublicId, FileContentType_DicomUntilPixelData,
+                            dicom.empty() ? NULL: dicom.c_str(), pixelDataOffset);
+            }
           }
         }
       }
-
-      if (!Toolbox::ReadJson(result, tmp))
-      {
-        throw OrthancException(ErrorCode_CorruptedFile);
-      }
-    }
-    else
-    {
-      // The "DicomAsJson" attachment might have stored some tags as
-      // "too long". We are forced to re-parse the DICOM file.
-      std::string dicom;
-      ReadDicom(dicom, instancePublicId);
-
-      ParsedDicomFile parsed(dicom);
-      OrthancConfiguration::DefaultDicomDatasetToJson(result, parsed, ignoreTagLength);
     }
   }