diff PalanthirServer/PalantirRestApi.cpp @ 45:33d67e1ab173

r
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 05 Sep 2012 13:24:59 +0200
parents PalantirServer/PalantirRestApi.cpp@9be852ad33d2
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PalanthirServer/PalantirRestApi.cpp	Wed Sep 05 13:24:59 2012 +0200
@@ -0,0 +1,750 @@
+/**
+ * Palantir - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PalantirRestApi.h"
+
+#include "PalantirInitialization.h"
+#include "FromDcmtkBridge.h"
+#include "../Core/Uuid.h"
+
+#include <dcmtk/dcmdata/dcistrmb.h>
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <boost/lexical_cast.hpp>
+
+namespace Palantir
+{
+  static void SendJson(HttpOutput& output,
+                       const Json::Value& value)
+  {
+    Json::StyledWriter writer;
+    std::string s = writer.write(value);
+    output.AnswerBufferWithContentType(s, "application/json");
+  }
+
+
+  static void SimplifyTagsRecursion(Json::Value& target,
+                                    const Json::Value& source)
+  {
+    assert(source.isObject());
+
+    target = Json::objectValue;
+    Json::Value::Members members = source.getMemberNames();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const Json::Value& v = source[members[i]];
+      const std::string& name = v["Name"].asString();
+      const std::string& type = v["Type"].asString();
+
+      if (type == "String")
+      {
+        target[name] = v["Value"].asString();
+      }
+      else if (type == "TooLong" ||
+               type == "Null")
+      {
+        target[name] = Json::nullValue;
+      }
+      else if (type == "Sequence")
+      {
+        const Json::Value& array = v["Value"];
+        assert(array.isArray());
+
+        Json::Value children = Json::arrayValue;
+        for (size_t i = 0; i < array.size(); i++)
+        {
+          Json::Value c;
+          SimplifyTagsRecursion(c, array[i]);
+          children.append(c);
+        }
+
+        target[name] = children;
+      }
+      else
+      {
+        assert(0);
+      }
+    }
+  }
+
+
+  static void SimplifyTags(Json::Value& target,
+                           const FileStorage& storage,
+                           const std::string& fileUuid)
+  {
+    std::string s;
+    storage.ReadFile(s, fileUuid);
+
+    Json::Value source;
+    Json::Reader reader;
+    if (!reader.parse(s, source))
+    {
+      throw PalantirException("Corrupted JSON file");
+    }
+
+    SimplifyTagsRecursion(target, source);
+  }
+
+
+  bool PalantirRestApi::Store(Json::Value& result,
+                              const std::string& postData)
+  {
+    // Prepare an input stream for the memory buffer
+    DcmInputBufferStream is;
+    if (postData.size() > 0)
+    {
+      is.setBuffer(&postData[0], postData.size());
+    }
+    is.setEos();
+
+    //printf("[%d]\n", postData.size());
+
+    DcmFileFormat dicomFile;
+    if (dicomFile.read(is).good())
+    {
+      DicomMap dicomSummary;
+      FromDcmtkBridge::Convert(dicomSummary, *dicomFile.getDataset());
+          
+      Json::Value dicomJson;
+      FromDcmtkBridge::ToJson(dicomJson, *dicomFile.getDataset());
+      
+      std::string instanceUuid;
+      StoreStatus status = StoreStatus_Failure;
+      if (postData.size() > 0)
+      {
+        status = index_.Store
+          (instanceUuid, storage_, reinterpret_cast<const char*>(&postData[0]),
+           postData.size(), dicomSummary, dicomJson, "");
+      }
+
+      switch (status)
+      {
+      case StoreStatus_Success:
+        result["ID"] = instanceUuid;
+        result["Path"] = "/instances/" + instanceUuid;
+        result["Status"] = "Success";
+        return true;
+      
+      case StoreStatus_AlreadyStored:
+        result["ID"] = instanceUuid;
+        result["Path"] = "/instances/" + instanceUuid;
+        result["Status"] = "AlreadyStored";
+        return true;
+
+      default:
+        return false;
+      }
+    }
+
+    return false;
+  }
+
+  void PalantirRestApi::ConnectToModality(DicomUserConnection& c,
+                                          const std::string& name)
+  {
+    std::string aet, address;
+    int port;
+    GetDicomModality(name, aet, address, port);
+    c.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "PALANTIR"));
+    c.SetDistantApplicationEntityTitle(aet);
+    c.SetDistantHost(address);
+    c.SetDistantPort(port);
+    c.Open();
+  }
+
+  bool PalantirRestApi::MergeQueryAndTemplate(DicomMap& result,
+                                              const std::string& postData)
+  {
+    Json::Value query;
+    Json::Reader reader;
+
+    if (!reader.parse(postData, query) ||
+        query.type() != Json::objectValue)
+    {
+      return false;
+    }
+
+    Json::Value::Members members = query.getMemberNames();
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      DicomTag t = FromDcmtkBridge::FindTag(members[i]);
+      result.SetValue(t, query[members[i]].asString());
+    }
+
+    return true;
+  }
+
+  bool PalantirRestApi::DicomFindPatient(Json::Value& result,
+                                         DicomUserConnection& c,
+                                         const std::string& postData)
+  {
+    DicomMap m;
+    DicomMap::SetupFindPatientTemplate(m);
+    if (!MergeQueryAndTemplate(m, postData))
+    {
+      return false;
+    }
+
+    DicomFindAnswers answers;
+    c.FindPatient(answers, m);
+    answers.ToJson(result);
+    return true;
+  }
+
+  bool PalantirRestApi::DicomFindStudy(Json::Value& result,
+                                       DicomUserConnection& c,
+                                       const std::string& postData)
+  {
+    DicomMap m;
+    DicomMap::SetupFindStudyTemplate(m);
+    if (!MergeQueryAndTemplate(m, postData))
+    {
+      return false;
+    }
+
+    if (m.GetValue(DicomTag::ACCESSION_NUMBER).AsString().size() <= 2 &&
+        m.GetValue(DicomTag::PATIENT_ID).AsString().size() <= 2)
+    {
+      return false;
+    }        
+        
+    DicomFindAnswers answers;
+    c.FindStudy(answers, m);
+    answers.ToJson(result);
+    return true;
+  }
+
+  bool PalantirRestApi::DicomFindSeries(Json::Value& result,
+                                        DicomUserConnection& c,
+                                        const std::string& postData)
+  {
+    DicomMap m;
+    DicomMap::SetupFindSeriesTemplate(m);
+    if (!MergeQueryAndTemplate(m, postData))
+    {
+      return false;
+    }
+
+    if ((m.GetValue(DicomTag::ACCESSION_NUMBER).AsString().size() <= 2 &&
+         m.GetValue(DicomTag::PATIENT_ID).AsString().size() <= 2) ||
+        m.GetValue(DicomTag::STUDY_UID).AsString().size() <= 2)
+    {
+      return false;
+    }        
+        
+    DicomFindAnswers answers;
+    c.FindSeries(answers, m);
+    answers.ToJson(result);
+    return true;
+  }
+
+  bool PalantirRestApi::DicomFind(Json::Value& result,
+                                  DicomUserConnection& c,
+                                  const std::string& postData)
+  {
+    DicomMap m;
+    DicomMap::SetupFindPatientTemplate(m);
+    if (!MergeQueryAndTemplate(m, postData))
+    {
+      return false;
+    }
+
+    DicomFindAnswers patients;
+    c.FindPatient(patients, m);
+
+    // Loop over the found patients
+    result = Json::arrayValue;
+    for (size_t i = 0; i < patients.GetSize(); i++)
+    {
+      Json::Value patient(Json::objectValue);
+      FromDcmtkBridge::ToJson(patient, patients.GetAnswer(i));
+
+      DicomMap::SetupFindStudyTemplate(m);
+      if (!MergeQueryAndTemplate(m, postData))
+      {
+        return false;
+      }
+      m.CopyTagIfExists(patients.GetAnswer(i), DicomTag::PATIENT_ID);
+
+      DicomFindAnswers studies;
+      c.FindStudy(studies, m);
+
+      patient["Studies"] = Json::arrayValue;
+      
+      // Loop over the found studies
+      for (size_t j = 0; j < studies.GetSize(); j++)
+      {
+        Json::Value study(Json::objectValue);
+        FromDcmtkBridge::ToJson(study, studies.GetAnswer(j));
+
+        DicomMap::SetupFindSeriesTemplate(m);
+        if (!MergeQueryAndTemplate(m, postData))
+        {
+          return false;
+        }
+        m.CopyTagIfExists(studies.GetAnswer(j), DicomTag::PATIENT_ID);
+        m.CopyTagIfExists(studies.GetAnswer(j), DicomTag::STUDY_UID);
+
+        DicomFindAnswers series;
+        c.FindSeries(series, m);
+
+        // Loop over the found series
+        study["Series"] = Json::arrayValue;
+        for (size_t k = 0; k < series.GetSize(); k++)
+        {
+          Json::Value series2(Json::objectValue);
+          FromDcmtkBridge::ToJson(series2, series.GetAnswer(k));
+          study["Series"].append(series2);
+        }
+
+        patient["Studies"].append(study);
+      }
+
+      result.append(patient);
+    }
+    
+    return true;
+  }
+
+
+
+  bool PalantirRestApi::DicomStore(Json::Value& result,
+                                   DicomUserConnection& c,
+                                   const std::string& postData)
+  {
+    Json::Value found(Json::objectValue);
+
+    if (!Toolbox::IsUuid(postData))
+    {
+      // This is not a UUID, assume this is a DICOM instance
+      c.Store(postData);
+    }
+    else if (index_.GetSeries(found, postData))
+    {
+      // The UUID corresponds to a series
+      for (size_t i = 0; i < found["Instances"].size(); i++)
+      {
+        std::string uuid = found["Instances"][i].asString();
+        Json::Value instance(Json::objectValue);
+        if (index_.GetInstance(instance, uuid))
+        {
+          std::string content;
+          storage_.ReadFile(content, instance["FileUuid"].asString());
+          c.Store(content);
+        }
+        else
+        {
+          return false;
+        }
+      }
+    }
+    else if (index_.GetInstance(found, postData))
+    {
+      // The UUID corresponds to an instance
+      std::string content;
+      storage_.ReadFile(content, found["FileUuid"].asString());
+      c.Store(content);
+    }
+    else
+    {
+      return false;
+    }
+
+    return true;
+  }
+
+
+  PalantirRestApi::PalantirRestApi(ServerIndex& index,
+                                   const std::string& path) :
+    index_(index),
+    storage_(path)
+  {
+    GetListOfDicomModalities(modalities_);
+  }
+
+
+  void PalantirRestApi::Handle(
+    HttpOutput& output,
+    const std::string& method,
+    const UriComponents& uri,
+    const Arguments& headers,
+    const Arguments& arguments,
+    const std::string& postData)
+  {
+    if (uri.size() == 0)
+    {
+      if (method == "GET")
+      {
+        output.Redirect("/app/explorer.html");
+      }
+      else
+      {
+        output.SendMethodNotAllowedError("GET");
+      }
+
+      return;
+    }
+
+    bool existingResource = false;
+    Json::Value result(Json::objectValue);
+
+
+    // List all the instances ---------------------------------------------------
+ 
+    if (uri.size() == 1 && uri[0] == "instances")
+    {
+      if (method == "GET")
+      {
+        result = Json::Value(Json::arrayValue);
+        index_.GetAllUuids(result, "Instances");
+        existingResource = true;
+      }
+      else if (method == "POST")
+      {
+        // Add a new instance to the storage
+        if (Store(result, postData))
+        {
+          SendJson(output, result);
+          return;
+        }
+        else
+        {
+          output.SendHeader(Palantir_HttpStatus_415_UnsupportedMediaType);
+          return;
+        }
+      }
+      else
+      {
+        output.SendMethodNotAllowedError("GET,POST");
+        return;
+      }
+    }
+
+
+    // List all the patients, studies or series ---------------------------------
+ 
+    if (uri.size() == 1 && 
+        (uri[0] == "series" ||
+         uri[0] == "studies" ||
+         uri[0] == "patients"))
+    {
+      if (method == "GET")
+      {
+        result = Json::Value(Json::arrayValue);
+
+        if (uri[0] == "instances")
+          index_.GetAllUuids(result, "Instances");
+        else if (uri[0] == "series")
+          index_.GetAllUuids(result, "Series");
+        else if (uri[0] == "studies")
+          index_.GetAllUuids(result, "Studies");
+        else if (uri[0] == "patients")
+          index_.GetAllUuids(result, "Patients");
+
+        existingResource = true;
+      }
+      else
+      {
+        output.SendMethodNotAllowedError("GET");
+        return;
+      }
+    }
+
+
+    // Information about a single object ----------------------------------------
+ 
+    else if (uri.size() == 2 && 
+             (uri[0] == "instances" ||
+              uri[0] == "series" ||
+              uri[0] == "studies" ||
+              uri[0] == "patients"))
+    {
+      if (method == "GET")
+      {
+        if (uri[0] == "patients")
+        {
+          existingResource = index_.GetPatient(result, uri[1]);
+        }
+        else if (uri[0] == "studies")
+        {
+          existingResource = index_.GetStudy(result, uri[1]);
+        }
+        else if (uri[0] == "series")
+        {
+          existingResource = index_.GetSeries(result, uri[1]);
+        }
+        else if (uri[0] == "instances")
+        {
+          existingResource = index_.GetInstance(result, uri[1]);
+        }
+      }
+      else if (method == "DELETE")
+      {
+        if (uri[0] == "patients")
+        {
+          existingResource = index_.DeletePatient(result, uri[1]);
+        }
+        else if (uri[0] == "studies")
+        {
+          existingResource = index_.DeleteStudy(result, uri[1]);
+        }
+        else if (uri[0] == "series")
+        {
+          existingResource = index_.DeleteSeries(result, uri[1]);
+        }
+        else if (uri[0] == "instances")
+        {
+          existingResource = index_.DeleteInstance(result, uri[1]);
+        }
+
+        if (existingResource)
+        {
+          result["Status"] = "Success";
+        }
+      }
+      else
+      {
+        output.SendMethodNotAllowedError("GET,DELETE");
+        return;
+      }
+    }
+
+
+    // Get the DICOM or the JSON file of one instance ---------------------------
+ 
+    else if (uri.size() == 3 &&
+             uri[0] == "instances" &&
+             (uri[2] == "file" || 
+              uri[2] == "tags" || 
+              uri[2] == "simplified-tags"))
+    {
+      std::string fileUuid, contentType;
+      if (uri[2] == "file")
+      {
+        existingResource = index_.GetDicomFile(fileUuid, uri[1]);
+        contentType = "application/dicom";
+      }
+      else if (uri[2] == "tags" ||
+               uri[2] == "simplified-tags")
+      {
+        existingResource = index_.GetJsonFile(fileUuid, uri[1]);
+        contentType = "application/json";
+      }
+
+      if (existingResource)
+      {
+        if (uri[2] == "simplified-tags")
+        {
+          Json::Value v;
+          SimplifyTags(v, storage_, fileUuid);
+          SendJson(output, v);
+          return;
+        }
+        else
+        {
+          output.AnswerFile(storage_, fileUuid, contentType);
+          return;
+        }
+      }
+    }
+
+
+    else if (uri.size() == 3 &&
+             uri[0] == "instances" &&
+             (uri[2] == "preview" ||
+              uri[2] == "image-uint8" ||
+              uri[2] == "image-uint16"))
+    {
+      std::string uuid;
+      existingResource = index_.GetDicomFile(uuid, uri[1]);
+
+      if (existingResource)
+      {
+        std::string dicomContent, png;
+        storage_.ReadFile(dicomContent, uuid);
+        try
+        {
+          if (uri[2] == "preview")
+          {
+            FromDcmtkBridge::ExtractPngImage(png, dicomContent, ImageExtractionMode_Preview);
+          }
+          else if (uri[2] == "image-uint8")
+          {
+            FromDcmtkBridge::ExtractPngImage(png, dicomContent, ImageExtractionMode_UInt8);
+          }
+          else if (uri[2] == "image-uint16")
+          {
+            FromDcmtkBridge::ExtractPngImage(png, dicomContent, ImageExtractionMode_UInt16);
+          }
+          else
+          {
+            throw PalantirException(ErrorCode_InternalError);
+          }
+
+          output.AnswerBufferWithContentType(png, "image/png");
+          return;
+        }
+        catch (PalantirException&)
+        {
+          output.Redirect("/app/images/Unsupported.png");
+          return;
+        }
+      }
+    }
+
+
+
+    // Changes API --------------------------------------------------------------
+ 
+    if (uri.size() == 1 && uri[0] == "changes")
+    {
+      if (method == "GET")
+      {
+        const static unsigned int MAX_RESULTS = 100;
+
+        std::string filter = GetArgument(arguments, "filter", "");
+        int64_t since;
+        unsigned int limit;
+        try
+        {
+          since = boost::lexical_cast<int64_t>(GetArgument(arguments, "since", "0"));
+          limit = boost::lexical_cast<unsigned int>(GetArgument(arguments, "limit", "0"));
+        }
+        catch (boost::bad_lexical_cast)
+        {
+          output.SendHeader(Palantir_HttpStatus_400_BadRequest);
+          return;
+        }
+
+        if (limit == 0 || limit > MAX_RESULTS)
+        {
+          limit = MAX_RESULTS;
+        }
+
+        if (!index_.GetChanges(result, since, filter, limit))
+        {
+          output.SendHeader(Palantir_HttpStatus_400_BadRequest);
+          return;
+        }
+
+        existingResource = true;
+      }
+      else
+      {
+        output.SendMethodNotAllowedError("GET");
+        return;
+      }
+    }
+
+
+    // DICOM bridge -------------------------------------------------------------
+
+    if (uri.size() == 1 &&
+        uri[0] == "modalities")
+    {
+      if (method == "GET")
+      {
+        result = Json::Value(Json::arrayValue);
+        existingResource = true;
+
+        for (Modalities::const_iterator it = modalities_.begin(); 
+             it != modalities_.end(); it++)
+        {
+          result.append(*it);
+        }
+      }
+      else
+      {
+        output.SendMethodNotAllowedError("GET");
+        return;
+      }
+    }
+
+    if ((uri.size() == 2 ||
+         uri.size() == 3) && 
+        uri[0] == "modalities")
+    {
+      if (modalities_.find(uri[1]) == modalities_.end())
+      {
+        // Unknown modality
+      }
+      else if (uri.size() == 2)
+      {
+        if (method != "GET")
+        {
+          output.SendMethodNotAllowedError("POST");
+          return;
+        }
+        else
+        {
+          existingResource = true;
+          result = Json::arrayValue;
+          result.append("find-patient");
+          result.append("find-study");
+          result.append("find-series");
+          result.append("find");
+          result.append("store");
+        }
+      }
+      else if (uri.size() == 3)
+      {
+        if (uri[2] != "find-patient" &&
+            uri[2] != "find-study" &&
+            uri[2] != "find-series" &&
+            uri[2] != "find" &&
+            uri[2] != "store")
+        {
+          // Unknown request
+        }
+        else if (method != "POST")
+        {
+          output.SendMethodNotAllowedError("POST");
+          return;
+        }
+        else
+        {
+          DicomUserConnection connection;
+          ConnectToModality(connection, uri[1]);
+          existingResource = true;
+          
+          if ((uri[2] == "find-patient" && !DicomFindPatient(result, connection, postData)) ||
+              (uri[2] == "find-study" && !DicomFindStudy(result, connection, postData)) ||
+              (uri[2] == "find-series" && !DicomFindSeries(result, connection, postData)) ||
+              (uri[2] == "find" && !DicomFind(result, connection, postData)) ||
+              (uri[2] == "store" && !DicomStore(result, connection, postData)))
+          {
+            output.SendHeader(Palantir_HttpStatus_400_BadRequest);
+            return;
+          }
+        }
+      }
+    }
+
+ 
+    if (existingResource)
+    {
+      SendJson(output, result);
+    }
+    else
+    {
+      output.SendHeader(Palantir_HttpStatus_404_NotFound);
+    }
+  }
+}