# HG changeset patch # User Sebastien Jodogne # Date 1499843532 -7200 # Node ID 1bdc4cc68171ffa97a54cc9fb7442eaf16c9f0af # Parent c6defdc4c611fca2ab528ba2c6937a742e0329a8# Parent 06ff7e86638a466572639a3a76464ddc3a27036c integration mainline->issue-46-anonymization diff -r c6defdc4c611 -r 1bdc4cc68171 Core/DicomFormat/DicomImageInformation.cpp --- a/Core/DicomFormat/DicomImageInformation.cpp Wed May 10 22:40:59 2017 +0200 +++ b/Core/DicomFormat/DicomImageInformation.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -200,13 +200,6 @@ throw OrthancException(ErrorCode_NotImplemented); } - if (bitsAllocated_ > 32 || - bitsStored_ >= 32) - { - // Not available, as the accessor internally uses int32_t values - throw OrthancException(ErrorCode_NotImplemented); - } - if (samplesPerPixel_ == 0) { throw OrthancException(ErrorCode_NotImplemented); diff -r c6defdc4c611 -r 1bdc4cc68171 Core/DicomFormat/DicomIntegerPixelAccessor.cpp --- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Wed May 10 22:40:59 2017 +0200 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -54,6 +54,13 @@ pixelData_(pixelData), size_(size) { + if (information_.GetBitsAllocated() > 32 || + information_.GetBitsStored() >= 32) + { + // Not available, as the accessor internally uses int32_t values + throw OrthancException(ErrorCode_NotImplemented); + } + frame_ = 0; frameOffset_ = information_.GetFrameSize(); diff -r c6defdc4c611 -r 1bdc4cc68171 Core/Enumerations.cpp --- a/Core/Enumerations.cpp Wed May 10 22:40:59 2017 +0200 +++ b/Core/Enumerations.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -636,10 +636,10 @@ return "RGB"; case PhotometricInterpretation_Monochrome1: - return "Monochrome1"; + return "MONOCHROME1"; case PhotometricInterpretation_Monochrome2: - return "Monochrome2"; + return "MONOCHROME2"; case PhotometricInterpretation_ARGB: return "ARGB"; @@ -651,25 +651,25 @@ return "HSV"; case PhotometricInterpretation_Palette: - return "Palette color"; + return "PALETTE COLOR"; case PhotometricInterpretation_YBRFull: - return "YBR full"; + return "YBR_FULL"; case PhotometricInterpretation_YBRFull422: - return "YBR full 422"; + return "YBR_FULL_422"; case PhotometricInterpretation_YBRPartial420: - return "YBR partial 420"; + return "YBR_PARTIAL_420"; case PhotometricInterpretation_YBRPartial422: - return "YBR partial 422"; + return "YBR_PARTIAL_422"; case PhotometricInterpretation_YBR_ICT: - return "YBR ICT"; + return "YBR_ICT"; case PhotometricInterpretation_YBR_RCT: - return "YBR RCT"; + return "YBR_RCT"; case PhotometricInterpretation_Unknown: return "Unknown"; @@ -1053,6 +1053,80 @@ } + PhotometricInterpretation StringToPhotometricInterpretation(const char* value) + { + // http://dicom.nema.org/medical/dicom/2017a/output/chtml/part03/sect_C.7.6.3.html#sect_C.7.6.3.1.2 + std::string s(value); + + if (s == "MONOCHROME1") + { + return PhotometricInterpretation_Monochrome1; + } + + if (s == "MONOCHROME2") + { + return PhotometricInterpretation_Monochrome2; + } + + if (s == "PALETTE COLOR") + { + return PhotometricInterpretation_Palette; + } + + if (s == "RGB") + { + return PhotometricInterpretation_RGB; + } + + if (s == "HSV") + { + return PhotometricInterpretation_HSV; + } + + if (s == "ARGB") + { + return PhotometricInterpretation_ARGB; + } + + if (s == "CMYK") + { + return PhotometricInterpretation_CMYK; + } + + if (s == "YBR_FULL") + { + return PhotometricInterpretation_YBRFull; + } + + if (s == "YBR_FULL_422") + { + return PhotometricInterpretation_YBRFull422; + } + + if (s == "YBR_PARTIAL_422") + { + return PhotometricInterpretation_YBRPartial422; + } + + if (s == "YBR_PARTIAL_420") + { + return PhotometricInterpretation_YBRPartial420; + } + + if (s == "YBR_ICT") + { + return PhotometricInterpretation_YBR_ICT; + } + + if (s == "YBR_RCT") + { + return PhotometricInterpretation_YBR_RCT; + } + + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + unsigned int GetBytesPerPixel(PixelFormat format) { switch (format) diff -r c6defdc4c611 -r 1bdc4cc68171 Core/Enumerations.h --- a/Core/Enumerations.h Wed May 10 22:40:59 2017 +0200 +++ b/Core/Enumerations.h Wed Jul 12 09:12:12 2017 +0200 @@ -524,6 +524,8 @@ ValueRepresentation StringToValueRepresentation(const std::string& vr, bool throwIfUnsupported); + PhotometricInterpretation StringToPhotometricInterpretation(const char* value); + unsigned int GetBytesPerPixel(PixelFormat format); bool GetDicomEncoding(Encoding& encoding, diff -r c6defdc4c611 -r 1bdc4cc68171 Core/Images/ImageProcessing.cpp --- a/Core/Images/ImageProcessing.cpp Wed May 10 22:40:59 2017 +0200 +++ b/Core/Images/ImageProcessing.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -772,4 +772,29 @@ throw OrthancException(ErrorCode_NotImplemented); } } + + + void ImageProcessing::Invert(ImageAccessor& image) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + { + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + uint8_t* p = reinterpret_cast(image.GetRow(y)); + + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + *p = 255 - (*p); + } + } + + return; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } } diff -r c6defdc4c611 -r 1bdc4cc68171 Core/Images/ImageProcessing.h --- a/Core/Images/ImageProcessing.h Wed May 10 22:40:59 2017 +0200 +++ b/Core/Images/ImageProcessing.h Wed Jul 12 09:12:12 2017 +0200 @@ -73,5 +73,7 @@ static void ShiftScale(ImageAccessor& image, float offset, float scaling); + + static void Invert(ImageAccessor& image); }; } diff -r c6defdc4c611 -r 1bdc4cc68171 Core/MultiThreading/RunnableWorkersPool.cpp --- a/Core/MultiThreading/RunnableWorkersPool.cpp Wed May 10 22:40:59 2017 +0200 +++ b/Core/MultiThreading/RunnableWorkersPool.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -77,6 +77,10 @@ { LOG(ERROR) << "Not enough memory to handle some runnable object"; } + catch (std::exception& e) + { + LOG(ERROR) << "std::exception while handling some runnable object: " << e.what(); + } catch (...) { LOG(ERROR) << "Native exception while handling some runnable object"; diff -r c6defdc4c611 -r 1bdc4cc68171 Core/SQLite/Connection.h --- a/Core/SQLite/Connection.h Wed May 10 22:40:59 2017 +0200 +++ b/Core/SQLite/Connection.h Wed Jul 12 09:12:12 2017 +0200 @@ -39,13 +39,11 @@ #include "Statement.h" #include "IScalarFunction.h" +#include "SQLiteTypes.h" #include #include -struct sqlite3; -struct sqlite3_stmt; - #define SQLITE_FROM_HERE ::Orthanc::SQLite::StatementId(__FILE__, __LINE__) namespace Orthanc diff -r c6defdc4c611 -r 1bdc4cc68171 Core/SQLite/FunctionContext.cpp --- a/Core/SQLite/FunctionContext.cpp Wed May 10 22:40:59 2017 +0200 +++ b/Core/SQLite/FunctionContext.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -49,7 +49,7 @@ { FunctionContext::FunctionContext(struct sqlite3_context* context, int argc, - struct ::Mem** argv) + Internals::SQLiteValue** argv) { assert(context != NULL); assert(argc >= 0); diff -r c6defdc4c611 -r 1bdc4cc68171 Core/SQLite/FunctionContext.h --- a/Core/SQLite/FunctionContext.h Wed May 10 22:40:59 2017 +0200 +++ b/Core/SQLite/FunctionContext.h Wed Jul 12 09:12:12 2017 +0200 @@ -36,9 +36,6 @@ #include "Statement.h" -struct sqlite3_context; -struct Mem; // This corresponds to the opaque type "sqlite3_value" - namespace Orthanc { namespace SQLite @@ -50,14 +47,14 @@ private: struct sqlite3_context* context_; unsigned int argc_; - struct ::Mem** argv_; + Internals::SQLiteValue** argv_; void CheckIndex(unsigned int index) const; public: FunctionContext(struct sqlite3_context* context, int argc, - struct ::Mem** argv); + Internals::SQLiteValue** argv); ColumnType GetColumnType(unsigned int index) const; diff -r c6defdc4c611 -r 1bdc4cc68171 Core/SQLite/SQLiteTypes.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/SQLite/SQLiteTypes.h Wed Jul 12 09:12:12 2017 +0200 @@ -0,0 +1,73 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * + * Copyright (C) 2012-2016 Sebastien Jodogne , + * Medical Physics Department, CHU of Liege, Belgium + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of the CHU of Liege, nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + +#pragma once + +struct sqlite3; +struct sqlite3_context; +struct sqlite3_stmt; + +#if !defined(ORTHANC_SQLITE_VERSION) +#error Please define macro ORTHANC_SQLITE_VERSION +#endif + + +/** + * "sqlite3_value" is defined as: + * - "typedef struct Mem sqlite3_value;" up to SQLite <= 3.18.2 + * - "typedef struct sqlite3_value sqlite3_value;" since SQLite >= 3.19.0. + * We create our own copy of this typedef to get around this API incompatibility. + * https://github.com/mackyle/sqlite/commit/db1d90df06a78264775a14d22c3361eb5b42be17 + **/ + +#if ORTHANC_SQLITE_VERSION < 3019000 +struct Mem; +#else +struct sqlite3_value; +#endif + +namespace Orthanc +{ + namespace SQLite + { + namespace Internals + { +#if ORTHANC_SQLITE_VERSION < 3019000 + typedef struct ::Mem SQLiteValue; +#else + typedef struct ::sqlite3_value SQLiteValue; +#endif + } + } +} diff -r c6defdc4c611 -r 1bdc4cc68171 Core/SQLite/Statement.h --- a/Core/SQLite/Statement.h Wed May 10 22:40:59 2017 +0200 +++ b/Core/SQLite/Statement.h Wed Jul 12 09:12:12 2017 +0200 @@ -49,8 +49,6 @@ #include #endif -struct sqlite3_stmt; - namespace Orthanc { diff -r c6defdc4c611 -r 1bdc4cc68171 Core/SQLite/StatementReference.h --- a/Core/SQLite/StatementReference.h Wed May 10 22:40:59 2017 +0200 +++ b/Core/SQLite/StatementReference.h Wed Jul 12 09:12:12 2017 +0200 @@ -38,13 +38,12 @@ #pragma once #include "NonCopyable.h" +#include "SQLiteTypes.h" #include #include #include -struct sqlite3; -struct sqlite3_stmt; namespace Orthanc { diff -r c6defdc4c611 -r 1bdc4cc68171 NEWS --- a/NEWS Wed May 10 22:40:59 2017 +0200 +++ b/NEWS Wed Jul 12 09:12:12 2017 +0200 @@ -1,6 +1,11 @@ Pending changes in the mainline =============================== +REST API +-------- + +* Argument "Since" in URI "/tools/find" (related to issue 53) + Plugins ------- @@ -11,10 +16,23 @@ * Added HTTP headers support for Lua HttpPost/HttpGet/HttpPut/HttpDelete +Orthanc Explorer +---------------- + +* Query/retrieve: Added button for "DR" modality + Maintenance ----------- +* Ability to retrieve raw frames encoded as unsigned 32-bits integers +* Fix issue 29 (more consistent handling of the "--upgrade" argument) * Fix issue 35 (AET name is not transferred to Orthanc using DCMTK 3.6.0) +* Fix issue 44 (Bad interpretation of photometric interpretation MONOCHROME1) +* Fix issue 45 (crash when providing a folder to "--config" command-line option) +* Fix issue 49 (Worklists: accentuated characters are removed from C-Find responses) +* Fix Debian #865606 (orthanc FTBFS with libdcmtk-dev 3.6.1~20170228-2) +* Fix XSS inside DICOM in Orthanc Explorer (as reported by Victor Pasnkel, Morphus Labs) +* Upgrade forthcoming DCMTK 3.6.1 to snapshot 20170228 Version 1.2.0 (2016/12/13) diff -r c6defdc4c611 -r 1bdc4cc68171 OrthancExplorer/explorer.html --- a/OrthancExplorer/explorer.html Wed May 10 22:40:59 2017 +0200 +++ b/OrthancExplorer/explorer.html Wed Jul 12 09:12:12 2017 +0200 @@ -349,6 +349,7 @@ + diff -r c6defdc4c611 -r 1bdc4cc68171 OrthancExplorer/explorer.js --- a/OrthancExplorer/explorer.js Wed May 10 22:40:59 2017 +0200 +++ b/OrthancExplorer/explorer.js Wed Jul 12 09:12:12 2017 +0200 @@ -17,19 +17,6 @@ var currentUuid = ''; -// http://stackoverflow.com/a/4673436 -String.prototype.format = function() { - var args = arguments; - return this.replace(/{(\d+)}/g, function(match, number) { - /*return typeof args[number] != 'undefined' - ? args[number] - : match;*/ - - return args[number]; - }); -}; - - function DeepCopy(obj) { return jQuery.extend(true, {}, obj); @@ -209,29 +196,34 @@ } -function CompleteFormatting(s, link, isReverse) +function CompleteFormatting(node, link, isReverse, count) { + if (count != null) + { + node = node.add($('') + .addClass('ui-li-count') + .text(count)); + } + if (link != null) { - s = 'href="' + link + '">' + s + ''; - + node = $('').attr('href', link).append(node); + if (isReverse) - s = 'data-direction="reverse" '+ s; - - s = '').append(node); + if (isReverse) - return '
  • ' + s + '
  • '; - else - return '
  • ' + s + '
  • '; + node.attr('data-icon', 'back'); + + return node; } -function FormatMainDicomTags(tags, tagsToIgnore) +function FormatMainDicomTags(target, tags, tagsToIgnore) { - var s = ''; - for (var i in tags) { if (tagsToIgnore.indexOf(i) == -1) @@ -250,47 +242,38 @@ v = SplitLongUid(v); } - - s += ('

    {0}: {1}

    ').format(i, v); + target.append($('

    ') + .text(i + ': ') + .append($('').text(v))); } } - - return s; } function FormatPatient(patient, link, isReverse) { - var s = ('

    {0}

    {1}' + - '{2}' - ).format - (patient.MainDicomTags.PatientName, - FormatMainDicomTags(patient.MainDicomTags, [ - "PatientName" - /*"OtherPatientIDs" */ - ]), - patient.Studies.length - ); + var node = $('
    ').append($('

    ').text(patient.MainDicomTags.PatientName)); - return CompleteFormatting(s, link, isReverse); + FormatMainDicomTags(node, patient.MainDicomTags, [ + "PatientName" + // "OtherPatientIDs" + ]); + + return CompleteFormatting(node, link, isReverse, patient.Studies.length); } function FormatStudy(study, link, isReverse) { - var s = ('

    {0}

    {1}' + - '{2}' - ).format - (study.MainDicomTags.StudyDescription, - FormatMainDicomTags(study.MainDicomTags, [ + var node = $('
    ').append($('

    ').text(study.MainDicomTags.StudyDescription)); + + FormatMainDicomTags(node, study.MainDicomTags, [ "StudyDescription", "StudyTime" - ]), - study.Series.length - ); - - return CompleteFormatting(s, link, isReverse); + ]); + + return CompleteFormatting(node, link, isReverse, study.Series.length); } @@ -307,41 +290,39 @@ { c = series.Instances.length + '/' + series.ExpectedNumberOfInstances; } + + var node = $('
    ') + .append($('

    ').text(series.MainDicomTags.SeriesDescription)) + .append($('

    ').append($('') + .text('Status: ') + .append($('').text(series.Status)))); - var s = ('

    {0}

    ' + - '

    Status: {1}

    {2}' + - '{3}').format - (series.MainDicomTags.SeriesDescription, - series.Status, - FormatMainDicomTags(series.MainDicomTags, [ + FormatMainDicomTags(node, series.MainDicomTags, [ "SeriesDescription", "SeriesTime", "Manufacturer", "ImagesInAcquisition", "SeriesDate", "ImageOrientationPatient" - ]), - c - ); - - return CompleteFormatting(s, link, isReverse); + ]); + + return CompleteFormatting(node, link, isReverse, c); } function FormatInstance(instance, link, isReverse) { - var s = ('

    Instance {0}

    {1}').format - (instance.IndexInSeries, - FormatMainDicomTags(instance.MainDicomTags, [ - "AcquisitionNumber", - "InstanceNumber", - "InstanceCreationDate", - "InstanceCreationTime", - "ImagePositionPatient" - ]) - ); + var node = $('
    ').append($('

    ').text('Instance: ' + instance.IndexInSeries)); - return CompleteFormatting(s, link, isReverse); + FormatMainDicomTags(node, instance.MainDicomTags, [ + "AcquisitionNumber", + "InstanceNumber", + "InstanceCreationDate", + "InstanceCreationTime", + "ImagePositionPatient" + ]); + + return CompleteFormatting(node, link, isReverse); } @@ -353,7 +334,11 @@ cache: false, success: function(s) { if (s.Name != "") { - $('.orthanc-name').html('' + s.Name + ' » '); + $('.orthanc-name').append($('') + .addClass('ui-link') + .attr('href', 'explorer.html') + .text(s.Name) + .append(' » ')); } } }); @@ -417,8 +402,9 @@ for (var i = 0; i < studies.length; i++) { if (i == 0 || studies[i].MainDicomTags.StudyDate != studies[i - 1].MainDicomTags.StudyDate) { - target.append('
  • {0}
  • '.format - (FormatDicomDate(studies[i].MainDicomTags.StudyDate))); + target.append($('
  • ') + .attr('data-role', 'list-divider') + .text(FormatDicomDate(studies[i].MainDicomTags.StudyDate))); } target.append(FormatStudy(studies[i], '#study?uuid=' + studies[i].ID)); @@ -477,9 +463,11 @@ for (var i = 0; i < series.length; i++) { if (i == 0 || series[i].MainDicomTags.SeriesDate != series[i - 1].MainDicomTags.SeriesDate) { - target.append('
  • {0}
  • '.format - (FormatDicomDate(series[i].MainDicomTags.SeriesDate))); + target.append($('
  • ') + .attr('data-role', 'list-divider') + .text(FormatDicomDate(series[i].MainDicomTags.SeriesDate))); } + target.append(FormatSeries(series[i], '#series?uuid=' + series[i].ID)); } target.listview('refresh'); @@ -537,6 +525,24 @@ } +function EscapeHtml(value) +{ + var ENTITY_MAP = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' + }; + + return String(value).replace(/[&<>"'`=\/]/g, function (s) { + return ENTITY_MAP[s]; + }); +} + function ConvertForTree(dicom) { @@ -544,12 +550,14 @@ for (var i in dicom) { if (dicom[i] != null) { - var label = i + ' (' + dicom[i]["Name"] + '): '; + var label = (i + ' (' + + EscapeHtml(dicom[i]["Name"]) + + '): '); if (dicom[i]["Type"] == 'String') { result.push({ - label: label + '' + dicom[i]["Value"] + '', + label: label + '' + EscapeHtml(dicom[i]["Value"]) + '', children: [] }); } @@ -797,7 +805,7 @@ var images = []; for (var i = 0; i < instances.length; i++) { images.push([ '../instances/' + instances[i].ID + '/preview', - '{0}/{1}'.format(i + 1, instances.length) ]) + (i + 1).toString() + '/' + instances.length.toString() ]) } jQuery.slimbox(images, 0, { diff -r c6defdc4c611 -r 1bdc4cc68171 OrthancServer/FromDcmtkBridge.cpp --- a/OrthancServer/FromDcmtkBridge.cpp Wed May 10 22:40:59 2017 +0200 +++ b/OrthancServer/FromDcmtkBridge.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -181,6 +181,8 @@ void FromDcmtkBridge::InitializeDictionary(bool loadPrivateDictionary) { + LOG(INFO) << "Using DCTMK version: " << DCMTK_VERSION_NUMBER; + { DictionaryLocker locker; @@ -1681,7 +1683,7 @@ throw OrthancException(ErrorCode_BadParameterType); } - DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key, value.size()); + DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key); element.reset(sequence); for (Json::Value::ArrayIndex i = 0; i < value.size(); i++) diff -r c6defdc4c611 -r 1bdc4cc68171 OrthancServer/Internals/DicomImageDecoder.cpp --- a/OrthancServer/Internals/DicomImageDecoder.cpp Wed May 10 22:40:59 2017 +0200 +++ b/OrthancServer/Internals/DicomImageDecoder.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -727,7 +727,8 @@ void DicomImageDecoder::ApplyExtractionMode(std::auto_ptr& image, - ImageExtractionMode mode) + ImageExtractionMode mode, + bool invert) { if (image.get() == NULL) { @@ -761,6 +762,11 @@ if (ok) { assert(image.get() != NULL); + + if (invert) + { + Orthanc::ImageProcessing::Invert(*image); + } } else { @@ -771,9 +777,10 @@ void DicomImageDecoder::ExtractPngImage(std::string& result, std::auto_ptr& image, - ImageExtractionMode mode) + ImageExtractionMode mode, + bool invert) { - ApplyExtractionMode(image, mode); + ApplyExtractionMode(image, mode, invert); PngWriter writer; writer.WriteToMemory(result, *image); @@ -783,6 +790,7 @@ void DicomImageDecoder::ExtractJpegImage(std::string& result, std::auto_ptr& image, ImageExtractionMode mode, + bool invert, uint8_t quality) { if (mode != ImageExtractionMode_UInt8 && @@ -791,7 +799,7 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } - ApplyExtractionMode(image, mode); + ApplyExtractionMode(image, mode, invert); JpegWriter writer; writer.SetQuality(quality); diff -r c6defdc4c611 -r 1bdc4cc68171 OrthancServer/Internals/DicomImageDecoder.h --- a/OrthancServer/Internals/DicomImageDecoder.h Wed May 10 22:40:59 2017 +0200 +++ b/OrthancServer/Internals/DicomImageDecoder.h Wed Jul 12 09:12:12 2017 +0200 @@ -79,7 +79,8 @@ static bool PreviewDecodedImage(std::auto_ptr& image); static void ApplyExtractionMode(std::auto_ptr& image, - ImageExtractionMode mode); + ImageExtractionMode mode, + bool invert); public: static bool IsPsmctRle1(DcmDataset& dataset); @@ -92,11 +93,13 @@ static void ExtractPngImage(std::string& result, std::auto_ptr& image, - ImageExtractionMode mode); + ImageExtractionMode mode, + bool invert); static void ExtractJpegImage(std::string& result, std::auto_ptr& image, ImageExtractionMode mode, + bool invert, uint8_t quality); }; } diff -r c6defdc4c611 -r 1bdc4cc68171 OrthancServer/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Wed May 10 22:40:59 2017 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -267,14 +267,17 @@ private: std::auto_ptr& image_; ImageExtractionMode mode_; + bool invert_; std::string format_; std::string answer_; public: ImageToEncode(std::auto_ptr& image, - ImageExtractionMode mode) : + ImageExtractionMode mode, + bool invert) : image_(image), - mode_(mode) + mode_(mode), + invert_(invert) { } @@ -286,13 +289,13 @@ void EncodeUsingPng() { format_ = "image/png"; - DicomImageDecoder::ExtractPngImage(answer_, image_, mode_); + DicomImageDecoder::ExtractPngImage(answer_, image_, mode_, invert_); } void EncodeUsingJpeg(uint8_t quality) { format_ = "image/jpeg"; - DicomImageDecoder::ExtractJpegImage(answer_, image_, mode_, quality); + DicomImageDecoder::ExtractJpegImage(answer_, image_, mode_, invert_, quality); } }; @@ -373,6 +376,7 @@ return; } + bool invert = false; std::auto_ptr decoded; try @@ -393,6 +397,21 @@ * to decode the image. This allows us to take advantage of * the cache below. **/ + + if (mode == ImageExtractionMode_Preview && + decoded.get() != NULL) + { + // TODO Optimize this lookup for photometric interpretation: + // It should be implemented by the plugin to avoid parsing + // twice the DICOM file + ParsedDicomFile parsed(dicomContent); + + PhotometricInterpretation photometric; + if (parsed.LookupPhotometricInterpretation(photometric)) + { + invert = (photometric == PhotometricInterpretation_Monochrome1); + } + } } #endif @@ -400,8 +419,15 @@ { // Use Orthanc's built-in decoder, using the cache to speed-up // things on multi-frame images - ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), publicId); + ServerContext::DicomCacheLocker locker(context, publicId); decoded.reset(DicomImageDecoder::Decode(locker.GetDicom(), frame)); + + PhotometricInterpretation photometric; + if (mode == ImageExtractionMode_Preview && + locker.GetDicom().LookupPhotometricInterpretation(photometric)) + { + invert = (photometric == PhotometricInterpretation_Monochrome1); + } } } catch (OrthancException& e) @@ -423,7 +449,7 @@ } } - ImageToEncode image(decoded, mode); + ImageToEncode image(decoded, mode, invert); HttpContentNegociation negociation; EncodePng png(image); negociation.Register("image/png", png); @@ -1128,7 +1154,8 @@ request["Level"].type() == Json::stringValue && request["Query"].type() == Json::objectValue && (!request.isMember("CaseSensitive") || request["CaseSensitive"].type() == Json::booleanValue) && - (!request.isMember("Limit") || request["Limit"].type() == Json::intValue)) + (!request.isMember("Limit") || request["Limit"].type() == Json::intValue) && + (!request.isMember("Since") || request["Since"].type() == Json::intValue)) { bool expand = false; if (request.isMember("Expand")) @@ -1154,6 +1181,18 @@ limit = static_cast(tmp); } + size_t since = 0; + if (request.isMember("Since")) + { + int tmp = request["Since"].asInt(); + if (tmp < 0) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + since = static_cast(tmp); + } + std::string level = request["Level"].asString(); LookupResource query(StringToResourceType(level.c_str())); @@ -1172,8 +1211,9 @@ } std::list resources; - context.Apply(resources, query, limit); - AnswerListOfResources(call.GetOutput(), context.GetIndex(), resources, query.GetLevel(), expand); + context.Apply(resources, query, since, limit); + AnswerListOfResources(call.GetOutput(), context.GetIndex(), + resources, query.GetLevel(), expand); } else { diff -r c6defdc4c611 -r 1bdc4cc68171 OrthancServer/ParsedDicomFile.cpp --- a/OrthancServer/ParsedDicomFile.cpp Wed May 10 22:40:59 2017 +0200 +++ b/OrthancServer/ParsedDicomFile.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -1409,4 +1409,25 @@ { return FromDcmtkBridge::LookupTransferSyntax(result, *pimpl_->file_); } + + + bool ParsedDicomFile::LookupPhotometricInterpretation(PhotometricInterpretation& result) const + { + DcmTagKey k(DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetGroup(), + DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetElement()); + + DcmDataset& dataset = *pimpl_->file_->getDataset(); + + const char *c = NULL; + if (dataset.findAndGetString(k, c).good() && + c != NULL) + { + result = StringToPhotometricInterpretation(c); + return true; + } + else + { + return false; + } + } } diff -r c6defdc4c611 -r 1bdc4cc68171 OrthancServer/ParsedDicomFile.h --- a/OrthancServer/ParsedDicomFile.h Wed May 10 22:40:59 2017 +0200 +++ b/OrthancServer/ParsedDicomFile.h Wed Jul 12 09:12:12 2017 +0200 @@ -183,5 +183,7 @@ void ExtractDicomAsJson(Json::Value& target) const; bool LookupTransferSyntax(std::string& result); + + bool LookupPhotometricInterpretation(PhotometricInterpretation& result) const; }; } diff -r c6defdc4c611 -r 1bdc4cc68171 OrthancServer/Search/HierarchicalMatcher.cpp --- a/OrthancServer/Search/HierarchicalMatcher.cpp Wed May 10 22:40:59 2017 +0200 +++ b/OrthancServer/Search/HierarchicalMatcher.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -333,7 +333,7 @@ dicom.GetEncoding())); std::auto_ptr result(new ParsedDicomFile(*dataset)); - result->SetEncoding(Encoding_Utf8); + result->SetEncoding(dicom.GetEncoding()); return result.release(); } diff -r c6defdc4c611 -r 1bdc4cc68171 OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Wed May 10 22:40:59 2017 +0200 +++ b/OrthancServer/ServerContext.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -601,9 +601,10 @@ } - bool ServerContext::Apply(std::list& result, + void ServerContext::Apply(std::list& result, const ::Orthanc::LookupResource& lookup, - size_t maxResults) + size_t since, + size_t limit) { result.clear(); @@ -612,6 +613,7 @@ assert(resources.size() == instances.size()); + size_t skipped = 0; for (size_t i = 0; i < instances.size(); i++) { Json::Value dicom; @@ -619,10 +621,14 @@ if (lookup.IsMatch(dicom)) { - if (maxResults != 0 && - result.size() >= maxResults) + if (skipped < since) { - return false; // too many results + skipped++; + } + else if (limit != 0 && + result.size() >= limit) + { + return; // too many results } else { @@ -630,8 +636,6 @@ } } } - - return true; // finished } } diff -r c6defdc4c611 -r 1bdc4cc68171 OrthancServer/ServerContext.h --- a/OrthancServer/ServerContext.h Wed May 10 22:40:59 2017 +0200 +++ b/OrthancServer/ServerContext.h Wed Jul 12 09:12:12 2017 +0200 @@ -258,9 +258,10 @@ void Stop(); - bool Apply(std::list& result, + void Apply(std::list& result, const ::Orthanc::LookupResource& lookup, - size_t maxResults); + size_t since, + size_t limit); /** diff -r c6defdc4c611 -r 1bdc4cc68171 OrthancServer/ServerEnumerations.h --- a/OrthancServer/ServerEnumerations.h Wed May 10 22:40:59 2017 +0200 +++ b/OrthancServer/ServerEnumerations.h Wed Jul 12 09:12:12 2017 +0200 @@ -146,7 +146,8 @@ { GlobalProperty_DatabaseSchemaVersion = 1, // Unused in the Orthanc core as of Orthanc 0.9.5 GlobalProperty_FlushSleep = 2, - GlobalProperty_AnonymizationSequence = 3 + GlobalProperty_AnonymizationSequence = 3, + GlobalProperty_DatabasePatchLevel = 4 // Reserved for internal use of the database plugins }; enum MetadataType diff -r c6defdc4c611 -r 1bdc4cc68171 OrthancServer/main.cpp --- a/OrthancServer/main.cpp Wed May 10 22:40:59 2017 +0200 +++ b/OrthancServer/main.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -881,30 +881,27 @@ } -static bool UpgradeDatabase(IDatabaseWrapper& database, - IStorageArea& storageArea, - bool allowDatabaseUpgrade) +static void UpgradeDatabase(IDatabaseWrapper& database, + IStorageArea& storageArea) { // Upgrade the schema of the database, if needed unsigned int currentVersion = database.GetDatabaseVersion(); + + LOG(WARNING) << "Starting the upgrade of the database schema"; + LOG(WARNING) << "Current database version: " << currentVersion; + LOG(WARNING) << "Database version expected by Orthanc: " << ORTHANC_DATABASE_VERSION; + if (currentVersion == ORTHANC_DATABASE_VERSION) { - return true; + LOG(WARNING) << "No upgrade is needed, start Orthanc without the \"--upgrade\" argument"; + return; } if (currentVersion > ORTHANC_DATABASE_VERSION) { LOG(ERROR) << "The version of the database schema (" << currentVersion << ") is too recent for this version of Orthanc. Please upgrade Orthanc."; - return false; - } - - if (!allowDatabaseUpgrade) - { - LOG(ERROR) << "The database schema must be upgraded from version " - << currentVersion << " to " << ORTHANC_DATABASE_VERSION - << ": Please run Orthanc with the \"--upgrade\" command-line option"; - return false; + throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); } LOG(WARNING) << "Upgrading the database from schema version " @@ -926,10 +923,13 @@ if (ORTHANC_DATABASE_VERSION != currentVersion) { LOG(ERROR) << "The database schema was not properly upgraded, it is still at version " << currentVersion; - throw OrthancException(ErrorCode_InternalError); + throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); } - - return true; + else + { + LOG(WARNING) << "The database schema was successfully upgraded, " + << "you can now start Orthanc without the \"--upgrade\" argument"; + } } @@ -1015,13 +1015,23 @@ static bool ConfigureDatabase(IDatabaseWrapper& database, IStorageArea& storageArea, OrthancPlugins *plugins, - bool allowDatabaseUpgrade) + bool upgradeDatabase) { database.Open(); + + unsigned int currentVersion = database.GetDatabaseVersion(); - if (!UpgradeDatabase(database, storageArea, allowDatabaseUpgrade)) + if (upgradeDatabase) { - return false; + UpgradeDatabase(database, storageArea); + return false; // Stop and don't restart Orthanc (cf. issue 29) + } + else if (currentVersion != ORTHANC_DATABASE_VERSION) + { + LOG(ERROR) << "The database schema must be changed from version " + << currentVersion << " to " << ORTHANC_DATABASE_VERSION + << ": Please run Orthanc with the \"--upgrade\" argument"; + throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); } bool success = ConfigureServerContext(database, storageArea, plugins); @@ -1034,7 +1044,7 @@ static bool ConfigurePlugins(int argc, char* argv[], - bool allowDatabaseUpgrade) + bool upgradeDatabase) { std::auto_ptr databasePtr; std::auto_ptr storage; @@ -1069,14 +1079,14 @@ assert(database != NULL); assert(storage.get() != NULL); - return ConfigureDatabase(*database, *storage, &plugins, allowDatabaseUpgrade); + return ConfigureDatabase(*database, *storage, &plugins, upgradeDatabase); #elif ORTHANC_ENABLE_PLUGINS == 0 // The plugins are disabled databasePtr.reset(Configuration::CreateDatabaseWrapper()); storage.reset(Configuration::CreateStorageArea()); - return ConfigureDatabase(*databasePtr, *storage, NULL, allowDatabaseUpgrade); + return ConfigureDatabase(*databasePtr, *storage, NULL, upgradeDatabase); #else # error The macro ORTHANC_ENABLE_PLUGINS must be set to 0 or 1 @@ -1086,9 +1096,9 @@ static bool StartOrthanc(int argc, char* argv[], - bool allowDatabaseUpgrade) + bool upgradeDatabase) { - return ConfigurePlugins(argc, argv, allowDatabaseUpgrade); + return ConfigurePlugins(argc, argv, upgradeDatabase); } @@ -1104,7 +1114,7 @@ { Logging::Initialize(); - bool allowDatabaseUpgrade = false; + bool upgradeDatabase = false; const char* configurationFile = NULL; @@ -1193,7 +1203,7 @@ } else if (argument == "--upgrade") { - allowDatabaseUpgrade = true; + upgradeDatabase = true; } else if (boost::starts_with(argument, "--config=")) { @@ -1207,8 +1217,17 @@ #endif std::string target = argument.substr(9); - SystemToolbox::WriteFile(configurationSample, target); - return 0; + + try + { + SystemToolbox::WriteFile(configurationSample, target); + return 0; + } + catch (OrthancException&) + { + LOG(ERROR) << "Cannot write sample configuration as file \"" << target << "\""; + return -1; + } } else { @@ -1249,7 +1268,7 @@ { OrthancInitialize(configurationFile); - bool restart = StartOrthanc(argc, argv, allowDatabaseUpgrade); + bool restart = StartOrthanc(argc, argv, upgradeDatabase); if (restart) { OrthancFinalize(); diff -r c6defdc4c611 -r 1bdc4cc68171 Plugins/Samples/Common/FullOrthancDataset.cpp --- a/Plugins/Samples/Common/FullOrthancDataset.cpp Wed May 10 22:40:59 2017 +0200 +++ b/Plugins/Samples/Common/FullOrthancDataset.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -166,6 +166,21 @@ } + FullOrthancDataset::FullOrthancDataset(const void* content, + size_t size) + { + IOrthancConnection::ParseJson(root_, content, size); + CheckRoot(); + } + + + FullOrthancDataset::FullOrthancDataset(const Json::Value& root) : + root_(root) + { + CheckRoot(); + } + + bool FullOrthancDataset::GetStringValue(std::string& result, const DicomPath& path) const { diff -r c6defdc4c611 -r 1bdc4cc68171 Plugins/Samples/Common/FullOrthancDataset.h --- a/Plugins/Samples/Common/FullOrthancDataset.h Wed May 10 22:40:59 2017 +0200 +++ b/Plugins/Samples/Common/FullOrthancDataset.h Wed Jul 12 09:12:12 2017 +0200 @@ -55,6 +55,11 @@ FullOrthancDataset(const std::string& content); + FullOrthancDataset(const void* content, + size_t size); + + FullOrthancDataset(const Json::Value& root); + virtual bool GetStringValue(std::string& result, const DicomPath& path) const; diff -r c6defdc4c611 -r 1bdc4cc68171 Plugins/Samples/Common/IOrthancConnection.cpp --- a/Plugins/Samples/Common/IOrthancConnection.cpp Wed May 10 22:40:59 2017 +0200 +++ b/Plugins/Samples/Common/IOrthancConnection.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -51,6 +51,20 @@ } + void IOrthancConnection::ParseJson(Json::Value& result, + const void* content, + size_t size) + { + Json::Reader reader; + + if (!reader.parse(reinterpret_cast(content), + reinterpret_cast(content) + size, result)) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + void IOrthancConnection::RestApiGet(Json::Value& result, IOrthancConnection& orthanc, const std::string& uri) diff -r c6defdc4c611 -r 1bdc4cc68171 Plugins/Samples/Common/IOrthancConnection.h --- a/Plugins/Samples/Common/IOrthancConnection.h Wed May 10 22:40:59 2017 +0200 +++ b/Plugins/Samples/Common/IOrthancConnection.h Wed Jul 12 09:12:12 2017 +0200 @@ -64,6 +64,10 @@ static void ParseJson(Json::Value& result, const std::string& content); + static void ParseJson(Json::Value& result, + const void* content, + size_t size); + static void RestApiGet(Json::Value& result, IOrthancConnection& orthanc, const std::string& uri); diff -r c6defdc4c611 -r 1bdc4cc68171 Plugins/Samples/ModalityWorklists/Plugin.cpp --- a/Plugins/Samples/ModalityWorklists/Plugin.cpp Wed May 10 22:40:59 2017 +0200 +++ b/Plugins/Samples/ModalityWorklists/Plugin.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -8,7 +8,7 @@ * 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 @@ -35,7 +35,7 @@ /** * This is the main function for matching a DICOM worklist against a query. **/ -static void MatchWorklist(OrthancPluginWorklistAnswers* answers, +static bool MatchWorklist(OrthancPluginWorklistAnswers* answers, const OrthancPluginWorklistQuery* query, const OrthancPlugins::FindMatcher& matcher, const std::string& path) @@ -54,7 +54,11 @@ OrthancPlugins::LogError(context_, "Error while adding an answer to a worklist request"); ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); } + + return true; } + + return false; } @@ -67,10 +71,10 @@ // Convert the DICOM as JSON, and dump it to the user in "--verbose" mode Json::Value json; - dicom.DicomToJson(json, OrthancPluginDicomToJsonFormat_Short, + dicom.DicomToJson(json, OrthancPluginDicomToJsonFormat_Short, static_cast(0), 0); - - OrthancPlugins::LogInfo(context_, "Received worklist query from remote modality " + + + OrthancPlugins::LogInfo(context_, "Received worklist query from remote modality " + std::string(issuerAet) + ":\n" + json.toStyledString()); if (!filterIssuerAet_) @@ -140,10 +144,12 @@ std::auto_ptr matcher(CreateMatcher(query, issuerAet)); // Loop over the regular files in the database folder - namespace fs = boost::filesystem; + namespace fs = boost::filesystem; fs::path source(folder_); fs::directory_iterator end; + int parsedFilesCount = 0; + int matchedWorklistCount = 0; try { @@ -159,11 +165,21 @@ if (extension == ".wl") { + parsedFilesCount++; // We found a worklist (i.e. a DICOM find with extension ".wl"), match it against the query - MatchWorklist(answers, query, *matcher, it->path().string()); + if (MatchWorklist(answers, query, *matcher, it->path().string())) + { + OrthancPlugins::LogInfo(context_, "Worklist matched: " + it->path().string()); + matchedWorklistCount++; + } } } } + + std::ostringstream message; + message << "Worklist C-Find: parsed " << parsedFilesCount << " files, found " << matchedWorklistCount << " match(es)"; + OrthancPlugins::LogInfo(context_, message.str()); + } catch (fs::filesystem_error&) { @@ -192,7 +208,7 @@ /* Check the version of the Orthanc core */ if (OrthancPluginCheckVersion(c) == 0) { - OrthancPlugins::ReportMinimalOrthancVersion(context_, + OrthancPlugins::ReportMinimalOrthancVersion(context_, ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); diff -r c6defdc4c611 -r 1bdc4cc68171 Resources/CMake/DcmtkConfiguration.cmake --- a/Resources/CMake/DcmtkConfiguration.cmake Wed May 10 22:40:59 2017 +0200 +++ b/Resources/CMake/DcmtkConfiguration.cmake Wed Jul 12 09:12:12 2017 +0200 @@ -6,9 +6,9 @@ if (USE_DCMTK_361) SET(DCMTK_VERSION_NUMBER 361) SET(DCMTK_PACKAGE_VERSION "3.6.1") - SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.1_20160216) - SET(DCMTK_URL "http://www.orthanc-server.com/downloads/third-party/dcmtk-3.6.1_20160216.tar.gz") - SET(DCMTK_MD5 "273c8a544b9fe09b8a4fb4eb51df8e52") + SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.1_20170228) + SET(DCMTK_URL "http://www.orthanc-server.com/downloads/third-party/dcmtk-3.6.1_20170228.tar.gz") + SET(DCMTK_MD5 "65f3520fce5d084c3530ae7252e39f3e") SET(DCMTK_PATCH_SPEED "${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.1-speed.patch") macro(DCMTK_UNSET) @@ -270,10 +270,14 @@ set(DCMTK_CONFIGURATION_FILE "${DCMTK_config_INCLUDE_DIR}/cfunix.h") elseif (EXISTS "${DCMTK_config_INCLUDE_DIR}/osconfig.h") # This is for Arch Linux set(DCMTK_CONFIGURATION_FILE "${DCMTK_config_INCLUDE_DIR}/osconfig.h") + elseif (EXISTS "${DCMTK_INCLUDE_DIRS}/dcmtk/config/osconfig.h") # This is for Debian Buster + set(DCMTK_CONFIGURATION_FILE "${DCMTK_INCLUDE_DIRS}/dcmtk/config/osconfig.h") else() message(FATAL_ERROR "Please install libdcmtk*-dev") endif() + message("DCMTK configuration file: ${DCMTK_CONFIGURATION_FILE}") + # Autodetection of the version of DCMTK file(STRINGS "${DCMTK_CONFIGURATION_FILE}" @@ -309,6 +313,13 @@ /usr/share/libdcmtk7 /usr/share/libdcmtk8 /usr/share/libdcmtk9 + /usr/share/libdcmtk10 + /usr/share/libdcmtk11 + /usr/share/libdcmtk12 + /usr/share/libdcmtk13 + /usr/share/libdcmtk14 + /usr/share/libdcmtk15 + /usr/share/libdcmtk16 /usr/local/share/dcmtk ) diff -r c6defdc4c611 -r 1bdc4cc68171 Resources/CMake/SQLiteConfiguration.cmake --- a/Resources/CMake/SQLiteConfiguration.cmake Wed May 10 22:40:59 2017 +0200 +++ b/Resources/CMake/SQLiteConfiguration.cmake Wed Jul 12 09:12:12 2017 +0200 @@ -19,6 +19,8 @@ SET(SQLITE_MD5 "5fbeff9645ab035a1f580e90b279a16d") SET(SQLITE_URL "http://www.orthanc-server.com/downloads/third-party/sqlite-amalgamation-3071300.zip") + add_definitions(-DORTHANC_SQLITE_VERSION=3007013) + DownloadPackage(${SQLITE_MD5} ${SQLITE_URL} "${SQLITE_SOURCES_DIR}") set(SQLITE_SOURCES @@ -52,7 +54,10 @@ # Autodetection of the version of SQLite file(STRINGS "${SQLITE_INCLUDE_DIR}/sqlite3.h" SQLITE_VERSION_NUMBER1 REGEX "#define SQLITE_VERSION_NUMBER.*$") - string(REGEX REPLACE "#define SQLITE_VERSION_NUMBER(.*)$" "\\1" SQLITE_VERSION_NUMBER ${SQLITE_VERSION_NUMBER1}) + string(REGEX REPLACE "#define SQLITE_VERSION_NUMBER(.*)$" "\\1" SQLITE_VERSION_NUMBER2 ${SQLITE_VERSION_NUMBER1}) + + # Remove the trailing spaces to convert the string to a proper integer + string(STRIP ${SQLITE_VERSION_NUMBER2} SQLITE_VERSION_NUMBER) message("Detected version of SQLite: ${SQLITE_VERSION_NUMBER}") @@ -61,5 +66,7 @@ message(FATAL_ERROR "SQLite version must be above 3.7.0. Please set the CMake variable USE_SYSTEM_SQLITE to OFF.") ENDIF() + add_definitions(-DORTHANC_SQLITE_VERSION=${SQLITE_VERSION_NUMBER}) + link_libraries(sqlite3) endif() diff -r c6defdc4c611 -r 1bdc4cc68171 Resources/Patches/dcmtk-3.6.1-speed.patch --- a/Resources/Patches/dcmtk-3.6.1-speed.patch Wed May 10 22:40:59 2017 +0200 +++ b/Resources/Patches/dcmtk-3.6.1-speed.patch Wed Jul 12 09:12:12 2017 +0200 @@ -1,26 +1,24 @@ -diff -urEb dcmtk-3.6.1_20160216.orig/dcmnet/libsrc/dul.cc dcmtk-3.6.1_20160216/dcmnet/libsrc/dul.cc ---- dcmtk-3.6.1_20160216.orig/dcmnet/libsrc/dul.cc 2016-04-05 12:56:28.962230391 +0200 -+++ dcmtk-3.6.1_20160216/dcmnet/libsrc/dul.cc 2016-04-05 12:57:15.814232296 +0200 -@@ -1841,7 +1841,7 @@ +diff -urEb dcmtk-3.6.1_20170228.orig/dcmnet/libsrc/dul.cc dcmtk-3.6.1_20170228/dcmnet/libsrc/dul.cc +--- dcmtk-3.6.1_20170228.orig/dcmnet/libsrc/dul.cc 2017-07-05 12:57:20.707214499 +0200 ++++ dcmtk-3.6.1_20170228/dcmnet/libsrc/dul.cc 2017-07-05 12:58:03.563590489 +0200 +@@ -1789,7 +1789,7 @@ return makeDcmnetCondition(DULC_TCPINITERROR, OF_error, msg.c_str()); } #endif - setTCPBufferLength(sock); + //setTCPBufferLength(sock); - #ifndef DONT_DISABLE_NAGLE_ALGORITHM /* -Only in dcmtk-3.6.1_20160216/dcmnet/libsrc: dul.cc~ -diff -urEb dcmtk-3.6.1_20160216.orig/dcmnet/libsrc/dulfsm.cc dcmtk-3.6.1_20160216/dcmnet/libsrc/dulfsm.cc ---- dcmtk-3.6.1_20160216.orig/dcmnet/libsrc/dulfsm.cc 2016-04-05 12:56:28.962230391 +0200 -+++ dcmtk-3.6.1_20160216/dcmnet/libsrc/dulfsm.cc 2016-04-05 12:57:31.946232952 +0200 -@@ -2431,7 +2431,7 @@ + * Disable the so-called Nagle algorithm (if requested). +diff -urEb dcmtk-3.6.1_20170228.orig/dcmnet/libsrc/dulfsm.cc dcmtk-3.6.1_20170228/dcmnet/libsrc/dulfsm.cc +--- dcmtk-3.6.1_20170228.orig/dcmnet/libsrc/dulfsm.cc 2017-07-05 12:57:20.707214499 +0200 ++++ dcmtk-3.6.1_20170228/dcmnet/libsrc/dulfsm.cc 2017-07-05 12:58:17.995717258 +0200 +@@ -2419,7 +2419,7 @@ return makeDcmnetCondition(DULC_TCPINITERROR, OF_error, msg.c_str()); } #endif - setTCPBufferLength(s); + //setTCPBufferLength(s); - #ifndef DONT_DISABLE_NAGLE_ALGORITHM /* -Only in dcmtk-3.6.1_20160216/dcmnet/libsrc: dulfsm.cc~ + * Disable the so-called Nagle algorithm (if requested). diff -r c6defdc4c611 -r 1bdc4cc68171 Resources/Patches/dcmtk.txt --- a/Resources/Patches/dcmtk.txt Wed May 10 22:40:59 2017 +0200 +++ b/Resources/Patches/dcmtk.txt Wed Jul 12 09:12:12 2017 +0200 @@ -7,4 +7,4 @@ For "dcmtk-3.6.1-private.dic" ============================= -# cp ../../ThirdPartyDownloads/dcmtk-3.6.1_20160216/dcmdata/data/private.dic dcmtk-3.6.1-private.dic +# cp ../../ThirdPartyDownloads/dcmtk-3.6.1_20170228/dcmdata/data/private.dic dcmtk-3.6.1-private.dic diff -r c6defdc4c611 -r 1bdc4cc68171 Resources/Samples/Lua/AutomatedJpeg2kCompression.lua --- a/Resources/Samples/Lua/AutomatedJpeg2kCompression.lua Wed May 10 22:40:59 2017 +0200 +++ b/Resources/Samples/Lua/AutomatedJpeg2kCompression.lua Wed Jul 12 09:12:12 2017 +0200 @@ -16,7 +16,7 @@ -- Compress to JPEG2000 using gdcm local compressed = instanceId .. '-compressed.dcm' - os.execute('gdcmconv --j2k ' .. uncompressed .. ' ' .. compressed) + os.execute('gdcmconv -U --j2k ' .. uncompressed .. ' ' .. compressed) -- Generate a new SOPInstanceUID for the JPEG2000 file, as -- gdcmconv does not do this by itself diff -r c6defdc4c611 -r 1bdc4cc68171 TODO --- a/TODO Wed May 10 22:40:59 2017 +0200 +++ b/TODO Wed Jul 12 09:12:12 2017 +0200 @@ -30,6 +30,13 @@ * Create multi-frame images with /tools/create-dicom (by adding a "MultiFrame" flag to avoid creating a series) +* In the POST /instances, add a ?force=true query arguments to force Orthanc to replace + the uploaded file even if it already exists in Orthanc. This is useful when transcoding + an image outside Orthanc and reuploading it. Since it has the same ids, its Orthanc ID + will be the same as the previous one. +* In the /studies/{id}/anonymize route, add an option to remove secondary captures. + They usually contains Patient info in the image. The SOPClassUID might be used to + identify such secondary captures. --------- Long-term @@ -79,6 +86,8 @@ Ideas of plugins ---------------- +* Complex anonymization, with recursive mapping of UIDs + https://bitbucket.org/sjodogne/orthanc/issues/46/ * DICOM-RT primitives (RT-STRUCT, RT-PLAN, RT-DOSE) * Converter to/from NIfTI * MySQL database plugin @@ -87,6 +96,8 @@ https://groups.google.com/d/msg/orthanc-users/KompazkxRSs/5Rh03mzgDAAJ * More generally, expose more callbacks of the plugin SDK in Lua: https://groups.google.com/d/msg/orthanc-users/_FbiRHuXPGM/J-OAv7zaCAAJ +* Authorization plugin for the DICOM protocol: + https://groups.google.com/d/msg/orthanc-users/Wv-QEwTE0IA/rvJxoOjcAQAJ === diff -r c6defdc4c611 -r 1bdc4cc68171 UnitTestsSources/UnitTestsMain.cpp --- a/UnitTestsSources/UnitTestsMain.cpp Wed May 10 22:40:59 2017 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Wed Jul 12 09:12:12 2017 +0200 @@ -631,6 +631,23 @@ ASSERT_EQ(ResourceType_Instance, StringToResourceType(EnumerationToString(ResourceType_Instance))); ASSERT_EQ(ImageFormat_Png, StringToImageFormat(EnumerationToString(ImageFormat_Png))); + + ASSERT_EQ(PhotometricInterpretation_ARGB, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_ARGB))); + ASSERT_EQ(PhotometricInterpretation_CMYK, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_CMYK))); + ASSERT_EQ(PhotometricInterpretation_HSV, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_HSV))); + ASSERT_EQ(PhotometricInterpretation_Monochrome1, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_Monochrome1))); + ASSERT_EQ(PhotometricInterpretation_Monochrome2, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_Monochrome2))); + ASSERT_EQ(PhotometricInterpretation_Palette, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_Palette))); + ASSERT_EQ(PhotometricInterpretation_RGB, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_RGB))); + ASSERT_EQ(PhotometricInterpretation_YBRFull, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBRFull))); + ASSERT_EQ(PhotometricInterpretation_YBRFull422, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBRFull422))); + ASSERT_EQ(PhotometricInterpretation_YBRPartial420, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBRPartial420))); + ASSERT_EQ(PhotometricInterpretation_YBRPartial422, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBRPartial422))); + ASSERT_EQ(PhotometricInterpretation_YBR_ICT, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBR_ICT))); + ASSERT_EQ(PhotometricInterpretation_YBR_RCT, StringToPhotometricInterpretation(EnumerationToString(PhotometricInterpretation_YBR_RCT))); + + ASSERT_STREQ("Unknown", EnumerationToString(PhotometricInterpretation_Unknown)); + ASSERT_THROW(StringToPhotometricInterpretation("Unknown"), OrthancException); }