changeset 80:6212bf978584

status of series
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 20 Sep 2012 15:18:12 +0200
parents 297bad4e1019
children 0ec5e2e327b1
files Core/DicomFormat/DicomIntegerPixelAccessor.cpp Core/DicomFormat/DicomMap.cpp Core/DicomFormat/DicomMap.h Core/DicomFormat/DicomTag.cpp Core/DicomFormat/DicomTag.h Core/SQLite/Statement.cpp Core/SQLite/Statement.h NEWS OrthancExplorer/explorer.js OrthancServer/DicomProtocol/DicomUserConnection.cpp OrthancServer/FromDcmtkBridge.cpp OrthancServer/OrthancRestApi.cpp OrthancServer/PrepareDatabase.sql OrthancServer/ServerIndex.cpp
diffstat 14 files changed, 240 insertions(+), 113 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Thu Sep 20 13:47:54 2012 +0200
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Thu Sep 20 15:18:12 2012 +0200
@@ -67,7 +67,7 @@
     frame_ = 0;
     try
     {
-      numberOfFrames_ = boost::lexical_cast<unsigned int>(values.GetValue(DicomTag::NUMBER_OF_FRAMES).AsString());
+      numberOfFrames_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_NUMBER_OF_FRAMES).AsString());
     }
     catch (OrthancException)
     {
--- a/Core/DicomFormat/DicomMap.cpp	Thu Sep 20 13:47:54 2012 +0200
+++ b/Core/DicomFormat/DicomMap.cpp	Thu Sep 20 15:18:12 2012 +0200
@@ -36,7 +36,7 @@
     DicomTag(0x0010, 0x0030),   // PatientBirthDate
     DicomTag(0x0010, 0x0040),   // PatientSex
     DicomTag(0x0010, 0x1000),   // OtherPatientIDs
-    DicomTag::PATIENT_ID
+    DICOM_TAG_PATIENT_ID
   };
 
   static DicomTag studyTags[] =
@@ -47,8 +47,8 @@
     DicomTag(0x0008, 0x0030),   // StudyTime
     DicomTag(0x0008, 0x1030),   // StudyDescription
     DicomTag(0x0020, 0x0010),   // StudyID
-    DicomTag::ACCESSION_NUMBER,
-    DicomTag::STUDY_INSTANCE_UID
+    DICOM_TAG_ACCESSION_NUMBER,
+    DICOM_TAG_STUDY_INSTANCE_UID
   };
 
   static DicomTag seriesTags[] =
@@ -64,9 +64,11 @@
     DicomTag(0x0018, 0x0024),   // SequenceName
     DicomTag(0x0018, 0x1030),   // ProtocolName
     DicomTag(0x0020, 0x0011),   // SeriesNumber
-    DicomTag::IMAGES_IN_ACQUISITION,
-    DicomTag::NUMBER_OF_SLICES,
-    DicomTag::SERIES_INSTANCE_UID
+    DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES,
+    DICOM_TAG_IMAGES_IN_ACQUISITION,
+    DICOM_TAG_NUMBER_OF_FRAMES,
+    DICOM_TAG_NUMBER_OF_SLICES,
+    DICOM_TAG_SERIES_INSTANCE_UID
   };
 
   static DicomTag instanceTags[] =
@@ -74,11 +76,9 @@
     DicomTag(0x0008, 0x0012),   // InstanceCreationDate
     DicomTag(0x0008, 0x0013),   // InstanceCreationTime
     DicomTag(0x0020, 0x0012),   // AcquisitionNumber
-    DicomTag::CARDIAC_NUMBER_OF_IMAGES,
-    DicomTag::IMAGE_INDEX,
-    DicomTag::INSTANCE_NUMBER,
-    DicomTag::NUMBER_OF_FRAMES,
-    DicomTag::SOP_INSTANCE_UID
+    DICOM_TAG_IMAGE_INDEX,
+    DICOM_TAG_INSTANCE_NUMBER,
+    DICOM_TAG_SOP_INSTANCE_UID
   };
 
 
@@ -160,6 +160,7 @@
   }
 
 
+
   DicomMap* DicomMap::Clone() const
   {
     std::auto_ptr<DicomMap> result(new DicomMap);
@@ -175,15 +176,30 @@
 
   const DicomValue& DicomMap::GetValue(const DicomTag& tag) const
   {
+    const DicomValue* value = TestAndGetValue(tag);
+
+    if (value)
+    {
+      return *value;
+    }
+    else
+    {
+      throw OrthancException("Inexistent tag");
+    }
+  }
+
+
+  const DicomValue* DicomMap::TestAndGetValue(const DicomTag& tag) const
+  {
     Map::const_iterator it = map_.find(tag);
 
     if (it == map_.end())
     {
-      throw OrthancException("Inexistent tag");
+      return NULL;
     }
     else
     {
-      return *it->second;
+      return it->second;
     }
   }
 
@@ -219,25 +235,25 @@
   void DicomMap::SetupFindStudyTemplate(DicomMap& result)
   {
     SetupFindTemplate(result, studyTags, sizeof(studyTags) / sizeof(DicomTag));
-    result.SetValue(DicomTag::ACCESSION_NUMBER, "");
-    result.SetValue(DicomTag::PATIENT_ID, "");
+    result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "");
+    result.SetValue(DICOM_TAG_PATIENT_ID, "");
   }
 
   void DicomMap::SetupFindSeriesTemplate(DicomMap& result)
   {
     SetupFindTemplate(result, seriesTags, sizeof(seriesTags) / sizeof(DicomTag));
-    result.SetValue(DicomTag::ACCESSION_NUMBER, "");
-    result.SetValue(DicomTag::PATIENT_ID, "");
-    result.SetValue(DicomTag::STUDY_INSTANCE_UID, "");
+    result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "");
+    result.SetValue(DICOM_TAG_PATIENT_ID, "");
+    result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "");
   }
 
   void DicomMap::SetupFindInstanceTemplate(DicomMap& result)
   {
     SetupFindTemplate(result, instanceTags, sizeof(instanceTags) / sizeof(DicomTag));
-    result.SetValue(DicomTag::ACCESSION_NUMBER, "");
-    result.SetValue(DicomTag::PATIENT_ID, "");
-    result.SetValue(DicomTag::STUDY_INSTANCE_UID, "");
-    result.SetValue(DicomTag::SERIES_INSTANCE_UID, "");
+    result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "");
+    result.SetValue(DICOM_TAG_PATIENT_ID, "");
+    result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "");
+    result.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "");
   }
 
 
--- a/Core/DicomFormat/DicomMap.h	Thu Sep 20 13:47:54 2012 +0200
+++ b/Core/DicomFormat/DicomMap.h	Thu Sep 20 15:18:12 2012 +0200
@@ -109,6 +109,13 @@
 
     const DicomValue& GetValue(const DicomTag& tag) const;
 
+    const DicomValue* TestAndGetValue(uint16_t group, uint16_t element) const
+    {
+      return TestAndGetValue(DicomTag(group, element));
+    }       
+
+    const DicomValue* TestAndGetValue(const DicomTag& tag) const;
+
     void Remove(const DicomTag& tag);
 
     void ExtractPatientInformation(DicomMap& result) const;
--- a/Core/DicomFormat/DicomTag.cpp	Thu Sep 20 13:47:54 2012 +0200
+++ b/Core/DicomFormat/DicomTag.cpp	Thu Sep 20 15:18:12 2012 +0200
@@ -58,21 +58,4 @@
     sprintf(b, "%04x,%04x", group_, element_);
     return std::string(b);
   }
-
-
-  const DicomTag DicomTag::ACCESSION_NUMBER = DicomTag(0x0008, 0x0050);
-  const DicomTag DicomTag::SOP_INSTANCE_UID = DicomTag(0x0008, 0x0018);
-  const DicomTag DicomTag::PATIENT_ID = DicomTag(0x0010, 0x0020);
-  const DicomTag DicomTag::SERIES_INSTANCE_UID = DicomTag(0x0020, 0x000e);
-  const DicomTag DicomTag::STUDY_INSTANCE_UID = DicomTag(0x0020, 0x000d);
-  const DicomTag DicomTag::PIXEL_DATA = DicomTag(0x7fe0, 0x0010);
-
-  const DicomTag DicomTag::INSTANCE_NUMBER = DicomTag(0x0020, 0x0013);
-  const DicomTag DicomTag::IMAGE_INDEX = DicomTag(0x0054, 0x1330);
-
-  const DicomTag DicomTag::NUMBER_OF_SLICES = DicomTag(0x0054, 0x0081);
-  const DicomTag DicomTag::NUMBER_OF_FRAMES = DicomTag(0x0028, 0x0008);
-  const DicomTag DicomTag::CARDIAC_NUMBER_OF_IMAGES = DicomTag(0x0018, 0x1090);
-  const DicomTag DicomTag::IMAGES_IN_ACQUISITION = DicomTag(0x0020, 0x1002);
-
 }
--- a/Core/DicomFormat/DicomTag.h	Thu Sep 20 13:47:54 2012 +0200
+++ b/Core/DicomFormat/DicomTag.h	Thu Sep 20 15:18:12 2012 +0200
@@ -57,21 +57,21 @@
     std::string Format() const;
 
     friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag);
-
-    // Aliases for the most useful tags
-    static const DicomTag ACCESSION_NUMBER;
-    static const DicomTag SOP_INSTANCE_UID;
-    static const DicomTag PATIENT_ID;
-    static const DicomTag SERIES_INSTANCE_UID;
-    static const DicomTag STUDY_INSTANCE_UID;
-    static const DicomTag PIXEL_DATA;
+  };
 
-    static const DicomTag INSTANCE_NUMBER;
-    static const DicomTag IMAGE_INDEX;
+  // Aliases for the most useful tags
+  static const DicomTag DICOM_TAG_ACCESSION_NUMBER(0x0008, 0x0050);
+  static const DicomTag DICOM_TAG_SOP_INSTANCE_UID(0x0008, 0x0018);
+  static const DicomTag DICOM_TAG_PATIENT_ID(0x0010, 0x0020);
+  static const DicomTag DICOM_TAG_SERIES_INSTANCE_UID(0x0020, 0x000e);
+  static const DicomTag DICOM_TAG_STUDY_INSTANCE_UID(0x0020, 0x000d);
+  static const DicomTag DICOM_TAG_PIXEL_DATA(0x7fe0, 0x0010);
 
-    static const DicomTag NUMBER_OF_SLICES;
-    static const DicomTag NUMBER_OF_FRAMES;
-    static const DicomTag CARDIAC_NUMBER_OF_IMAGES;
-    static const DicomTag IMAGES_IN_ACQUISITION;
-  };
+  static const DicomTag DICOM_TAG_IMAGE_INDEX(0x0054, 0x1330);
+  static const DicomTag DICOM_TAG_INSTANCE_NUMBER(0x0020, 0x0013);
+
+  static const DicomTag DICOM_TAG_NUMBER_OF_SLICES(0x0054, 0x0081);
+  static const DicomTag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008);
+  static const DicomTag DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES(0x0018, 0x1090);
+  static const DicomTag DICOM_TAG_IMAGES_IN_ACQUISITION(0x0020, 0x1002);
 }
--- a/Core/SQLite/Statement.cpp	Thu Sep 20 13:47:54 2012 +0200
+++ b/Core/SQLite/Statement.cpp	Thu Sep 20 15:18:12 2012 +0200
@@ -215,6 +215,11 @@
       return COLUMN_TYPE_NULL;
     }
 
+    bool Statement::ColumnIsNull(int col) const 
+    {
+      return sqlite3_column_type(GetStatement(), col) == SQLITE_NULL;
+    }
+
     bool Statement::ColumnBool(int col) const 
     {
       return !!ColumnInt(col);
--- a/Core/SQLite/Statement.h	Thu Sep 20 13:47:54 2012 +0200
+++ b/Core/SQLite/Statement.h	Thu Sep 20 15:18:12 2012 +0200
@@ -137,6 +137,7 @@
       ColumnType GetDeclaredColumnType(int col) const;
 
       // These all take a 0-based argument index.
+      bool ColumnIsNull(int col) const ;
       bool ColumnBool(int col) const;
       int ColumnInt(int col) const;
       int64_t ColumnInt64(int col) const;
--- a/NEWS	Thu Sep 20 13:47:54 2012 +0200
+++ b/NEWS	Thu Sep 20 15:18:12 2012 +0200
@@ -1,6 +1,7 @@
 Pending changes in the mainline
 ===============================
 
+* Status of series
 
 
 Version 0.2.0 (2012/09/16)
--- a/OrthancExplorer/explorer.js	Thu Sep 20 13:47:54 2012 +0200
+++ b/OrthancExplorer/explorer.js	Thu Sep 20 15:18:12 2012 +0200
@@ -85,8 +85,7 @@
 }
 
 
-
-function SortOnDicomTag(arr, tag, isInteger, reverse)
+function Sort(arr, fieldExtractor, isInteger, reverse)
 {
   var defaultValue;
   if (isInteger)
@@ -95,8 +94,8 @@
     defaultValue = '';
 
   arr.sort(function(a, b) {
-    var ta = a.MainDicomTags[tag];
-    var tb = b.MainDicomTags[tag];
+    var ta = fieldExtractor(a);
+    var tb = fieldExtractor(b);
     var order;
 
     if (ta == undefined)
@@ -129,6 +128,14 @@
 }
 
 
+function SortOnDicomTag(arr, tag, isInteger, reverse)
+{
+  return Sort(arr, function(a) { 
+    return a.MainDicomTags[tag];
+  }, isInteger, reverse);
+}
+
+
 
 function GetSingleResource(type, uuid, callback)
 {
@@ -266,9 +273,21 @@
 
 function FormatSeries(series, link, isReverse)
 {
-  var s = ('<h3>{0}</h3>{1}' +
-           '<span class="ui-li-count">{2}</span>').format
+  var c;
+  if (series.Instances.length == series.ExpectedNumberOfInstances)
+  {
+    c = series.ExpectedNumberOfInstances;
+  }
+  else
+  {
+    c = series.Instances.length + '/' + series.ExpectedNumberOfInstances;
+  }
+
+  var s = ('<h3>{0}</h3>' +
+           '<p><em>Status: <strong>{1}</strong></em></p>{2}' +
+           '<span class="ui-li-count">{3}</span>').format
   (series.MainDicomTags.SeriesDescription,
+   series.Status,
    FormatMainDicomTags(series.MainDicomTags, [
      "SeriesDescription", 
      "SeriesTime", 
@@ -276,7 +295,7 @@
      "ImagesInAcquisition",
      "SeriesDate"
    ]),
-   series.Instances.length
+   c
   );
 
   return CompleteFormatting(s, link, isReverse);
@@ -286,7 +305,7 @@
 function FormatInstance(instance, link, isReverse)
 {
   var s = ('<h3>Instance {0}</h3>{1}').format
-  (instance.MainDicomTags.InstanceNumber,
+  (instance.IndexInSeries,
    FormatMainDicomTags(instance.MainDicomTags, [
      "AcquisitionNumber", 
      "InstanceNumber", 
@@ -390,7 +409,7 @@
       GetSingleResource('studies', series.ParentStudy, function(study) {
         GetSingleResource('patients', study.ParentPatient, function(patient) {
           GetMultipleResources('instances', series.Instances, function(instances) {
-            SortOnDicomTag(instances, 'InstanceNumber', true, false);
+            Sort(instances, function(x) { return x.IndexInSeries; }, true, false);
 
             $('#series-info li').remove();
             $('#series-info')
@@ -622,7 +641,7 @@
   if ($.mobile.pageData) {
     GetSingleResource('series', $.mobile.pageData.uuid, function(series) {
       GetMultipleResources('instances', series.Instances, function(instances) {
-        SortOnDicomTag(instances, 'InstanceNumber', true, false);
+        Sort(instances, function(x) { return x.IndexInSeries; }, true, false);
 
         var images = [];
         for (var i = 0; i < instances.length; i++) {
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Thu Sep 20 13:47:54 2012 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Thu Sep 20 15:18:12 2012 +0200
@@ -355,8 +355,8 @@
     DicomMap s;
     fields.ExtractStudyInformation(s);
 
-    s.CopyTagIfExists(fields, DicomTag::PATIENT_ID);
-    s.CopyTagIfExists(fields, DicomTag::ACCESSION_NUMBER);
+    s.CopyTagIfExists(fields, DICOM_TAG_PATIENT_ID);
+    s.CopyTagIfExists(fields, DICOM_TAG_ACCESSION_NUMBER);
 
     Find(result, FindRootModel_Study, s);
   }
@@ -368,9 +368,9 @@
     DicomMap s;
     fields.ExtractSeriesInformation(s);
 
-    s.CopyTagIfExists(fields, DicomTag::PATIENT_ID);
-    s.CopyTagIfExists(fields, DicomTag::ACCESSION_NUMBER);
-    s.CopyTagIfExists(fields, DicomTag::STUDY_INSTANCE_UID);
+    s.CopyTagIfExists(fields, DICOM_TAG_PATIENT_ID);
+    s.CopyTagIfExists(fields, DICOM_TAG_ACCESSION_NUMBER);
+    s.CopyTagIfExists(fields, DICOM_TAG_STUDY_INSTANCE_UID);
 
     Find(result, FindRootModel_Series, s);
   }
@@ -382,10 +382,10 @@
     DicomMap s;
     fields.ExtractInstanceInformation(s);
 
-    s.CopyTagIfExists(fields, DicomTag::PATIENT_ID);
-    s.CopyTagIfExists(fields, DicomTag::ACCESSION_NUMBER);
-    s.CopyTagIfExists(fields, DicomTag::STUDY_INSTANCE_UID);
-    s.CopyTagIfExists(fields, DicomTag::SERIES_INSTANCE_UID);
+    s.CopyTagIfExists(fields, DICOM_TAG_PATIENT_ID);
+    s.CopyTagIfExists(fields, DICOM_TAG_ACCESSION_NUMBER);
+    s.CopyTagIfExists(fields, DICOM_TAG_STUDY_INSTANCE_UID);
+    s.CopyTagIfExists(fields, DICOM_TAG_SERIES_INSTANCE_UID);
 
     Find(result, FindRootModel_Instance, s);
   }
@@ -594,8 +594,8 @@
                                        const DicomMap& findResult)
   {
     DicomMap simplified;
-    simplified.SetValue(DicomTag::STUDY_INSTANCE_UID, findResult.GetValue(DicomTag::STUDY_INSTANCE_UID));
-    simplified.SetValue(DicomTag::SERIES_INSTANCE_UID, findResult.GetValue(DicomTag::SERIES_INSTANCE_UID));
+    simplified.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, findResult.GetValue(DICOM_TAG_STUDY_INSTANCE_UID));
+    simplified.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, findResult.GetValue(DICOM_TAG_SERIES_INSTANCE_UID));
     Move(targetAet, simplified);
   }
 
@@ -604,8 +604,8 @@
                                        const std::string& seriesUid)
   {
     DicomMap map;
-    map.SetValue(DicomTag::STUDY_INSTANCE_UID, studyUid);
-    map.SetValue(DicomTag::SERIES_INSTANCE_UID, seriesUid);
+    map.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid);
+    map.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid);
     Move(targetAet, map);
   }
 
@@ -613,9 +613,9 @@
                                          const DicomMap& findResult)
   {
     DicomMap simplified;
-    simplified.SetValue(DicomTag::STUDY_INSTANCE_UID, findResult.GetValue(DicomTag::STUDY_INSTANCE_UID));
-    simplified.SetValue(DicomTag::SERIES_INSTANCE_UID, findResult.GetValue(DicomTag::SERIES_INSTANCE_UID));
-    simplified.SetValue(DicomTag::SOP_INSTANCE_UID, findResult.GetValue(DicomTag::SOP_INSTANCE_UID));
+    simplified.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, findResult.GetValue(DICOM_TAG_STUDY_INSTANCE_UID));
+    simplified.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, findResult.GetValue(DICOM_TAG_SERIES_INSTANCE_UID));
+    simplified.SetValue(DICOM_TAG_SOP_INSTANCE_UID, findResult.GetValue(DICOM_TAG_SOP_INSTANCE_UID));
     Move(targetAet, simplified);
   }
 
@@ -625,9 +625,9 @@
                                          const std::string& instanceUid)
   {
     DicomMap map;
-    map.SetValue(DicomTag::STUDY_INSTANCE_UID, studyUid);
-    map.SetValue(DicomTag::SERIES_INSTANCE_UID, seriesUid);
-    map.SetValue(DicomTag::SOP_INSTANCE_UID, instanceUid);
+    map.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid);
+    map.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid);
+    map.SetValue(DICOM_TAG_SOP_INSTANCE_UID, instanceUid);
     Move(targetAet, map);
   }
 
--- a/OrthancServer/FromDcmtkBridge.cpp	Thu Sep 20 13:47:54 2012 +0200
+++ b/OrthancServer/FromDcmtkBridge.cpp	Thu Sep 20 15:18:12 2012 +0200
@@ -454,7 +454,7 @@
     FromDcmtkBridge::Convert(m, dataset);
 
     DcmElement* e;
-    if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DicomTag::PIXEL_DATA), e).good() &&
+    if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() &&
         e != NULL)
     {
       Uint8* pixData = NULL;
--- a/OrthancServer/OrthancRestApi.cpp	Thu Sep 20 13:47:54 2012 +0200
+++ b/OrthancServer/OrthancRestApi.cpp	Thu Sep 20 15:18:12 2012 +0200
@@ -219,8 +219,8 @@
       return false;
     }
 
-    if (m.GetValue(DicomTag::ACCESSION_NUMBER).AsString().size() <= 2 &&
-        m.GetValue(DicomTag::PATIENT_ID).AsString().size() <= 2)
+    if (m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 &&
+        m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2)
     {
       return false;
     }        
@@ -242,9 +242,9 @@
       return false;
     }
 
-    if ((m.GetValue(DicomTag::ACCESSION_NUMBER).AsString().size() <= 2 &&
-         m.GetValue(DicomTag::PATIENT_ID).AsString().size() <= 2) ||
-        m.GetValue(DicomTag::STUDY_INSTANCE_UID).AsString().size() <= 2)
+    if ((m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 &&
+         m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) ||
+        m.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2)
     {
       return false;
     }        
@@ -281,7 +281,7 @@
       {
         return false;
       }
-      m.CopyTagIfExists(patients.GetAnswer(i), DicomTag::PATIENT_ID);
+      m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID);
 
       DicomFindAnswers studies;
       c.FindStudy(studies, m);
@@ -299,8 +299,8 @@
         {
           return false;
         }
-        m.CopyTagIfExists(studies.GetAnswer(j), DicomTag::PATIENT_ID);
-        m.CopyTagIfExists(studies.GetAnswer(j), DicomTag::STUDY_INSTANCE_UID);
+        m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_PATIENT_ID);
+        m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID);
 
         DicomFindAnswers series;
         c.FindSeries(series, m);
--- a/OrthancServer/PrepareDatabase.sql	Thu Sep 20 13:47:54 2012 +0200
+++ b/OrthancServer/PrepareDatabase.sql	Thu Sep 20 15:18:12 2012 +0200
@@ -18,7 +18,7 @@
        uuid TEXT PRIMARY KEY,
        parentStudy TEXT REFERENCES Studies(uuid) ON DELETE CASCADE,
        dicomSeries TEXT,
-       numberOfInstances INTEGER
+       expectedNumberOfInstances INTEGER
        );
 
 CREATE TABLE Instances(
@@ -29,7 +29,7 @@
        fileSize INTEGER,
        jsonUuid TEXT,
        distantAet TEXT,
-       instanceIndex INTEGER
+       indexInSeries INTEGER
        );
 
 CREATE TABLE MainDicomTags(
--- a/OrthancServer/ServerIndex.cpp	Thu Sep 20 13:47:54 2012 +0200
+++ b/OrthancServer/ServerIndex.cpp	Thu Sep 20 15:18:12 2012 +0200
@@ -231,7 +231,7 @@
   {
     std::string instanceUuid = Toolbox::GenerateUuid();
 
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Instances VALUES(?, ?, ?, ?, ?, ?, ?)");
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Instances VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
     s.BindString(0, instanceUuid);
     s.BindString(1, parentSeriesUuid);
     s.BindString(2, dicomInstance);
@@ -239,6 +239,18 @@
     s.BindInt64(4, fileSize);
     s.BindString(5, jsonUuid);
     s.BindString(6, distantAet);
+
+    const DicomValue* indexInSeries;
+    if ((indexInSeries = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL ||
+        (indexInSeries = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL)
+    {
+      s.BindInt(7, boost::lexical_cast<unsigned int>(indexInSeries->AsString()));
+    }
+    else
+    {
+      s.BindNull(7);
+    }
+
     s.Run();
 
     RecordChange("instances", instanceUuid);
@@ -283,7 +295,20 @@
     s.BindString(0, seriesUuid);
     s.BindString(1, parentStudyUuid);
     s.BindString(2, dicomSeries);
-    s.BindNull(3);
+
+    const DicomValue* expectedNumberOfInstances;
+    if ((expectedNumberOfInstances = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_FRAMES)) != NULL ||
+        (expectedNumberOfInstances = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_SLICES)) != NULL ||
+        (expectedNumberOfInstances = dicomSummary.TestAndGetValue(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)) != NULL ||
+        (expectedNumberOfInstances = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGES_IN_ACQUISITION)) != NULL)
+    {
+      s.BindInt(3, boost::lexical_cast<unsigned int>(expectedNumberOfInstances->AsString()));
+    }
+    else
+    {
+      s.BindNull(3);
+    }
+
     s.Run();
 
     RecordChange("series", seriesUuid);
@@ -354,7 +379,7 @@
                                          const DicomMap& dicomSummary)
   {
     std::string patientUuid = Toolbox::GenerateUuid();
-    std::string dicomPatientId = dicomSummary.GetValue(DicomTag::PATIENT_ID).AsString();
+    std::string dicomPatientId = dicomSummary.GetValue(DICOM_TAG_PATIENT_ID).AsString();
 
     SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Patients VALUES(?, ?)");
     s.BindString(0, patientUuid);
@@ -474,10 +499,10 @@
   {
     boost::mutex::scoped_lock scoped_lock(mutex_);
 
-    std::string dicomPatientId = dicomSummary.GetValue(DicomTag::PATIENT_ID).AsString();
-    std::string dicomInstance = dicomSummary.GetValue(DicomTag::SOP_INSTANCE_UID).AsString();
-    std::string dicomSeries = dicomSummary.GetValue(DicomTag::SERIES_INSTANCE_UID).AsString();
-    std::string dicomStudy = dicomSummary.GetValue(DicomTag::STUDY_INSTANCE_UID).AsString();
+    std::string dicomPatientId = dicomSummary.GetValue(DICOM_TAG_PATIENT_ID).AsString();
+    std::string dicomInstance = dicomSummary.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString();
+    std::string dicomSeries = dicomSummary.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString();
+    std::string dicomStudy = dicomSummary.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString();
 
     try
     {
@@ -584,31 +609,60 @@
 
   SeriesStatus ServerIndex::GetSeriesStatus(const std::string& seriesUuid)
   {
-    int numberOfSlices;
-    if (!GetMainDicomIntTag(numberOfSlices, seriesUuid, DicomTag::NUMBER_OF_SLICES) ||
-        numberOfSlices < 0)
+    SQLite::Statement s1(db_, SQLITE_FROM_HERE, "SELECT expectedNumberOfInstances FROM Series WHERE uuid=?");
+    s1.BindString(0, seriesUuid);
+    if (!s1.Step())
+    {
+      return SeriesStatus_Unknown;
+    }
+
+    int numberOfInstances = s1.ColumnInt(0);
+    if (numberOfInstances < 0)
     {
       return SeriesStatus_Unknown;
     }
 
-    // Loop over the instances of the series
-    //std::set<
+    std::set<int> instances;
+    SQLite::Statement s2(db_, SQLITE_FROM_HERE, "SELECT indexInSeries FROM Instances WHERE parentSeries=?");
+    s2.BindString(0, seriesUuid);
+    while (s2.Step())
+    {
+      int index = s2.ColumnInt(0);
+      if (index <= 0 || index > numberOfInstances)
+      {
+        // Out-of-range instance index
+        return SeriesStatus_Inconsistent;
+      }
 
-    // TODO
-    return SeriesStatus_Unknown;
+      if (instances.find(index) != instances.end())
+      {
+        // Twice the same instance index
+        return SeriesStatus_Inconsistent;
+      }
+
+      instances.insert(index);
+    }
+
+    for (int i = 1; i <= numberOfInstances; i++)
+    {
+      if (instances.find(i) == instances.end())
+      {
+        return SeriesStatus_Missing;
+      }
+    }
+
+    return SeriesStatus_Complete;
   }
 
 
 
-
-
   bool ServerIndex::GetInstance(Json::Value& result,
                                 const std::string& instanceUuid)
   {
     assert(result.type() == Json::objectValue);
     boost::mutex::scoped_lock scoped_lock(mutex_);
 
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT parentSeries, dicomInstance, fileSize, fileUuid FROM Instances WHERE uuid=?");
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT parentSeries, dicomInstance, fileSize, fileUuid, indexInSeries FROM Instances WHERE uuid=?");
     s.BindString(0, instanceUuid);
     if (!s.Step())
     {
@@ -621,6 +675,16 @@
       result["FileSize"] = s.ColumnInt(2);   // TODO switch to 64bit with JsonCpp 0.6?
       result["FileUuid"] = s.ColumnString(3);
       MainDicomTagsToJson(result, instanceUuid);
+      
+      if (s.ColumnIsNull(4))
+      {
+        result["IndexInSeries"] = -1;
+      }
+      else
+      {
+        result["IndexInSeries"] = s.ColumnInt(4);
+      }
+
       return true;
     }
   }
@@ -632,7 +696,7 @@
     assert(result.type() == Json::objectValue);
     boost::mutex::scoped_lock scoped_lock(mutex_);
 
-    SQLite::Statement s1(db_, SQLITE_FROM_HERE, "SELECT parentStudy, dicomSeries FROM Series WHERE uuid=?");
+    SQLite::Statement s1(db_, SQLITE_FROM_HERE, "SELECT parentStudy, dicomSeries, expectedNumberOfInstances FROM Series WHERE uuid=?");
     s1.BindString(0, seriesUuid);
     if (!s1.Step())
     {
@@ -653,6 +717,37 @@
       
     result["Instances"] = instances;
 
+    if (s1.ColumnIsNull(2))
+    {
+      result["ExpectedNumberOfInstances"] = -1;
+    }
+    else
+    {
+      result["ExpectedNumberOfInstances"] = s1.ColumnInt(2);
+    }
+
+    SeriesStatus status = GetSeriesStatus(seriesUuid);
+
+    switch (status)
+    {
+    case SeriesStatus_Complete:
+      result["Status"] = "Complete";
+      break;
+
+    case SeriesStatus_Missing:
+      result["Status"] = "Missing";
+      break;
+
+    case SeriesStatus_Inconsistent:
+      result["Status"] = "Inconsistent";
+      break;
+
+    default:
+    case SeriesStatus_Unknown:
+      result["Status"] = "Unknown";
+      break;
+    }
+
     return true;
   }