Mercurial > hg > orthanc-wsi
diff Applications/Dicomizer.cpp @ 0:4a7a53257c7d
initial commit
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Sat, 22 Oct 2016 21:48:33 +0200 |
parents | |
children | 62adabb8c122 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Dicomizer.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,877 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../Framework/Algorithms/ReconstructPyramidCommand.h" +#include "../Framework/Algorithms/TranscodeTileCommand.h" +#include "../Framework/DicomToolbox.h" +#include "../Framework/DicomizerParameters.h" +#include "../Framework/ImagedVolumeParameters.h" +#include "../Framework/Inputs/HierarchicalTiff.h" +#include "../Framework/Inputs/OpenSlidePyramid.h" +#include "../Framework/Inputs/TiledJpegImage.h" +#include "../Framework/Inputs/TiledPngImage.h" +#include "../Framework/Inputs/TiledPyramidStatistics.h" +#include "../Framework/Orthanc/Core/HttpClient.h" +#include "../Framework/Orthanc/Core/Logging.h" +#include "../Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h" +#include "../Framework/Orthanc/Core/Toolbox.h" +#include "../Framework/Orthanc/OrthancServer/FromDcmtkBridge.h" +#include "../Framework/Outputs/DicomPyramidWriter.h" +#include "../Framework/Outputs/TruncatedPyramidWriter.h" + +#include "ApplicationToolbox.h" + +#include <EmbeddedResources.h> + +#include <boost/program_options.hpp> + +#include <dcmtk/dcmdata/dcdeftag.h> +#include <dcmtk/dcmdata/dcuid.h> +#include <dcmtk/dcmdata/dcvrobow.h> +#include <dcmtk/dcmdata/dcvrat.h> + + +static void TranscodePyramid(OrthancWSI::PyramidWriterBase& target, + OrthancWSI::ITiledPyramid& source, + const OrthancWSI::DicomizerParameters& parameters) +{ + Orthanc::BagOfTasks tasks; + + for (unsigned int i = 0; i < source.GetLevelCount(); i++) + { + LOG(WARNING) << "Creating level " << i << " of size " + << source.GetLevelWidth(i) << "x" << source.GetLevelHeight(i); + target.AddLevel(source.GetLevelWidth(i), source.GetLevelHeight(i)); + } + + LOG(WARNING) << "Transcoding the source pyramid"; + + OrthancWSI::TranscodeTileCommand::PrepareBagOfTasks(tasks, target, source, parameters); + OrthancWSI::ApplicationToolbox::Execute(tasks, parameters.GetThreadsCount()); +} + + +static void ReconstructPyramid(OrthancWSI::PyramidWriterBase& target, + OrthancWSI::ITiledPyramid& source, + const OrthancWSI::DicomizerParameters& parameters) +{ + Orthanc::BagOfTasks tasks; + + unsigned int levelsCount = parameters.GetPyramidLevelsCount(target, source); + LOG(WARNING) << "The target pyramid will have " << levelsCount << " levels"; + assert(levelsCount >= 1); + + for (unsigned int i = 0; i < levelsCount; i++) + { + unsigned int width = OrthancWSI::CeilingDivision(source.GetLevelWidth(0), 1 << i); + unsigned int height = OrthancWSI::CeilingDivision(source.GetLevelHeight(0), 1 << i); + + LOG(WARNING) << "Creating level " << i << " of size " << width << "x" << height; + target.AddLevel(width, height); + } + + unsigned int lowerLevelsCount = parameters.GetPyramidLowerLevelsCount(target, source); + if (lowerLevelsCount > levelsCount) + { + LOG(WARNING) << "The number of lower levels (" << lowerLevelsCount + << ") exceeds the number of levels (" << levelsCount + << "), cropping it"; + lowerLevelsCount = levelsCount; + } + + assert(lowerLevelsCount <= levelsCount); + if (lowerLevelsCount != levelsCount) + { + LOG(WARNING) << "Constructing the " << lowerLevelsCount << " lower levels of the pyramid"; + OrthancWSI::TruncatedPyramidWriter truncated(target, lowerLevelsCount); + OrthancWSI::ReconstructPyramidCommand::PrepareBagOfTasks + (tasks, truncated, source, lowerLevelsCount + 1, 0, parameters); + OrthancWSI::ApplicationToolbox::Execute(tasks, parameters.GetThreadsCount()); + + assert(tasks.GetSize() == 0); + + const unsigned int upperLevelsCount = levelsCount - lowerLevelsCount; + LOG(WARNING) << "Constructing the " << upperLevelsCount << " upper levels of the pyramid"; + OrthancWSI::ReconstructPyramidCommand::PrepareBagOfTasks + (tasks, target, truncated.GetUpperLevel(), + upperLevelsCount, lowerLevelsCount, parameters); + OrthancWSI::ApplicationToolbox::Execute(tasks, parameters.GetThreadsCount()); + } + else + { + LOG(WARNING) << "Constructing the pyramid"; + OrthancWSI::ReconstructPyramidCommand::PrepareBagOfTasks + (tasks, target, source, levelsCount, 0, parameters); + OrthancWSI::ApplicationToolbox::Execute(tasks, parameters.GetThreadsCount()); + } +} + + +static void Recompress(OrthancWSI::IFileTarget& output, + OrthancWSI::ITiledPyramid& source, + const DcmDataset& dataset, + const OrthancWSI::DicomizerParameters& parameters, + const OrthancWSI::ImagedVolumeParameters& volume) +{ + OrthancWSI::TiledPyramidStatistics stats(source); + + LOG(WARNING) << "Size of source tiles: " << stats.GetTileWidth() << "x" << stats.GetTileHeight(); + LOG(WARNING) << "Source image compression: " << OrthancWSI::EnumerationToString(stats.GetImageCompression()); + LOG(WARNING) << "Pixel format: " << Orthanc::EnumerationToString(stats.GetPixelFormat()); + LOG(WARNING) << "Smoothing is " << (parameters.IsSmoothEnabled() ? "enabled" : "disabled"); + + if (parameters.IsRepaintBackground()) + { + LOG(WARNING) << "Repainting the background with color: (" + << static_cast<int>(parameters.GetBackgroundColorRed()) << "," + << static_cast<int>(parameters.GetBackgroundColorGreen()) << "," + << static_cast<int>(parameters.GetBackgroundColorBlue()) << ")"; + } + else + { + LOG(WARNING) << "No repainting of the background"; + } + + OrthancWSI::DicomPyramidWriter target(output, dataset, + source.GetPixelFormat(), + parameters.GetTargetCompression(), + parameters.GetTargetTileWidth(source), + parameters.GetTargetTileHeight(source), + parameters.GetDicomMaxFileSize(), + volume); + + LOG(WARNING) << "Size of target tiles: " << target.GetTileWidth() << "x" << target.GetTileHeight(); + + if (target.GetImageCompression() == OrthancWSI::ImageCompression_Jpeg) + { + LOG(WARNING) << "Target image compression: Jpeg with quality " << static_cast<int>(target.GetJpegQuality()); + target.SetJpegQuality(target.GetJpegQuality()); + } + else + { + LOG(WARNING) << "Target image compression: " << OrthancWSI::EnumerationToString(target.GetImageCompression()); + } + + if (stats.GetTileWidth() % target.GetTileWidth() != 0 || + stats.GetTileHeight() % target.GetTileHeight() != 0) + { + LOG(ERROR) << "When resampling the tile size, it must be a integer divisor of the original tile size"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + if (target.GetTileWidth() <= 16 || + target.GetTileHeight() <= 16) + { + LOG(ERROR) << "Tiles are too small (16 pixels minimum): " + << target.GetTileWidth() << "x" << target.GetTileHeight(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + if (parameters.IsReconstructPyramid()) + { + ReconstructPyramid(target, stats, parameters); + } + else + { + TranscodePyramid(target, stats, parameters); + } + + target.Flush(); +} + + + +static DcmDataset* ParseDataset(const std::string& path) +{ + Json::Value json; + + if (path.empty()) + { + json = Json::objectValue; // Empty dataset => TODO EMBED + } + else + { + std::string content; + Orthanc::Toolbox::ReadFile(content, path); + + Json::Reader reader; + if (!reader.parse(content, json, false)) + { + LOG(ERROR) << "Cannot parse the JSON file in: " << path; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + std::auto_ptr<DcmDataset> dataset(Orthanc::FromDcmtkBridge::FromJson(json, true, true, Orthanc::Encoding_Latin1)); + if (dataset.get() == NULL) + { + LOG(ERROR) << "Cannot convert to JSON file to a DICOM dataset: " << path; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + // VL Whole Slide Microscopy Image IOD + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_SOPClassUID, "1.2.840.10008.5.1.4.1.1.77.1.6"); + + // Slide Microscopy + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_Modality, "SM"); + + // Patient orientation makes no sense in whole-slide images + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_PatientOrientation, ""); + + // Some basic coordinate information + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_VolumetricProperties, "VOLUME"); + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_ImageOrientationSlide, "0\\-1\\0\\-1\\0\\0"); + + std::string date, time; + Orthanc::Toolbox::GetNowDicom(date, time); + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_StudyDate, date); + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_StudyTime, time); + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_SeriesDate, date); + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_SeriesTime, time); + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_ContentDate, date); + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_ContentTime, time); + OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_AcquisitionDateTime, date + time); + + return dataset.release(); +} + + + +static void SetupDimension(DcmDataset& dataset, + const std::string& opticalPathId, + const OrthancWSI::ITiledPyramid& source, + const OrthancWSI::ImagedVolumeParameters& volume) +{ + std::string uid; + DcmItem* previous = OrthancWSI::DicomToolbox::ExtractSingleSequenceItem(dataset, DCM_DimensionOrganizationSequence); + + if (previous != NULL) + { + const char* tmp = NULL; + if (previous->findAndGetString(DCM_DimensionOrganizationUID, tmp).good() && + tmp != NULL) + { + uid.assign(tmp); + } + } + + if (uid.empty()) + { + // Generate an unique identifier for the Dimension Organization + uid = Orthanc::FromDcmtkBridge::GenerateUniqueIdentifier(Orthanc::ResourceType_Instance); + } + + dataset.remove(DCM_DimensionIndexSequence); + + std::auto_ptr<DcmItem> item(new DcmItem); + std::auto_ptr<DcmSequenceOfItems> sequence(new DcmSequenceOfItems(DCM_DimensionOrganizationSequence)); + + if (!item->putAndInsertString(DCM_DimensionOrganizationUID, uid.c_str()).good() || + !sequence->insert(item.release(), false, false).good() || + !dataset.insert(sequence.release(), true, false).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + item.reset(new DcmItem); + sequence.reset(new DcmSequenceOfItems(DCM_DimensionIndexSequence)); + + std::auto_ptr<DcmAttributeTag> a1(new DcmAttributeTag(DCM_FunctionalGroupPointer)); + std::auto_ptr<DcmAttributeTag> a2(new DcmAttributeTag(DCM_DimensionIndexPointer)); + + if (!item->putAndInsertString(DCM_DimensionOrganizationUID, uid.c_str()).good() || + !a1->putTagVal(DCM_FrameContentSequence).good() || + !a2->putTagVal(DCM_DimensionIndexValues).good() || + !item->insert(a1.release(), uid.c_str()).good() || + !item->insert(a2.release(), uid.c_str()).good() || + !sequence->insert(item.release(), false, false).good() || + !dataset.insert(sequence.release(), true, false).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + float spacingX = volume.GetWidth() / static_cast<float>(source.GetLevelHeight(0)); // Remember to switch X/Y! + float spacingY = volume.GetHeight() / static_cast<float>(source.GetLevelWidth(0)); // Remember to switch X/Y! + std::string spacing = (boost::lexical_cast<std::string>(spacingX) + '\\' + + boost::lexical_cast<std::string>(spacingY)); + + item.reset(new DcmItem); + sequence.reset(new DcmSequenceOfItems(DCM_SharedFunctionalGroupsSequence)); + std::auto_ptr<DcmItem> item2(new DcmItem); + std::auto_ptr<DcmItem> item3(new DcmItem); + std::auto_ptr<DcmSequenceOfItems> sequence2(new DcmSequenceOfItems(DCM_PixelMeasuresSequence)); + std::auto_ptr<DcmSequenceOfItems> sequence3(new DcmSequenceOfItems(DCM_OpticalPathIdentificationSequence)); + + OrthancWSI::DicomToolbox::SetStringTag(*item2, DCM_SliceThickness, boost::lexical_cast<std::string>(volume.GetDepth())); + OrthancWSI::DicomToolbox::SetStringTag(*item2, DCM_PixelSpacing, spacing); + OrthancWSI::DicomToolbox::SetStringTag(*item3, DCM_OpticalPathIdentifier, opticalPathId); + + if (!sequence2->insert(item2.release(), false, false).good() || + !sequence3->insert(item3.release(), false, false).good() || + !item->insert(sequence2.release(), false, false).good() || + !item->insert(sequence3.release(), false, false).good() || + !sequence->insert(item.release(), false, false).good() || + !dataset.insert(sequence.release(), true, false).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } +} + + +static void EnrichDataset(DcmDataset& dataset, + const OrthancWSI::ITiledPyramid& source, + const OrthancWSI::DicomizerParameters& parameters, + const OrthancWSI::ImagedVolumeParameters& volume) +{ + Orthanc::Encoding encoding = Orthanc::FromDcmtkBridge::DetectEncoding(dataset, Orthanc::Encoding_Latin1); + + if (source.GetImageCompression() == OrthancWSI::ImageCompression_Jpeg || + parameters.GetTargetCompression() == OrthancWSI::ImageCompression_Jpeg) + { + // Takes as estimation a 1:10 compression ratio + OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_LossyImageCompression, "01"); + OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_LossyImageCompressionRatio, "10"); + OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_LossyImageCompressionMethod, "ISO_10918_1"); // JPEG Lossy Compression + } + else + { + OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_LossyImageCompression, "00"); + } + + OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_ImagedVolumeWidth, boost::lexical_cast<std::string>(volume.GetWidth())); + OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_ImagedVolumeHeight, boost::lexical_cast<std::string>(volume.GetHeight())); + OrthancWSI::DicomToolbox::SetStringTag(dataset, DCM_ImagedVolumeDepth, boost::lexical_cast<std::string>(volume.GetDepth())); + + std::auto_ptr<DcmItem> origin(new DcmItem); + OrthancWSI::DicomToolbox::SetStringTag(*origin, DCM_XOffsetInSlideCoordinateSystem, + boost::lexical_cast<std::string>(volume.GetOffsetX())); + OrthancWSI::DicomToolbox::SetStringTag(*origin, DCM_YOffsetInSlideCoordinateSystem, + boost::lexical_cast<std::string>(volume.GetOffsetY())); + + std::auto_ptr<DcmSequenceOfItems> sequenceOrigin(new DcmSequenceOfItems(DCM_TotalPixelMatrixOriginSequence)); + if (!sequenceOrigin->insert(origin.release(), false, false).good() || + !dataset.insert(sequenceOrigin.release(), false, false).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + + if (parameters.GetOpticalPath() == OrthancWSI::OpticalPath_Brightfield) + { + if (dataset.tagExists(DCM_OpticalPathSequence)) + { + LOG(ERROR) << "The user DICOM dataset already contains an optical path sequence, giving up"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + std::string brightfield; + Orthanc::EmbeddedResources::GetFileResource(brightfield, Orthanc::EmbeddedResources::BRIGHTFIELD_OPTICAL_PATH); + + Json::Value json; + Json::Reader reader; + if (!reader.parse(brightfield, json, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + std::auto_ptr<DcmElement> element(Orthanc::FromDcmtkBridge::FromJson( + Orthanc::DicomTag(DCM_OpticalPathSequence.getGroup(), + DCM_OpticalPathSequence.getElement()), + json, false, encoding)); + if (!dataset.insert(element.release()).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + std::string profile; + if (parameters.GetIccProfilePath().empty()) + { + Orthanc::EmbeddedResources::GetFileResource(profile, Orthanc::EmbeddedResources::SRGB_ICC_PROFILE); + } + else + { + Orthanc::Toolbox::ReadFile(profile, parameters.GetIccProfilePath()); + } + + + DcmItem* opticalPath = OrthancWSI::DicomToolbox::ExtractSingleSequenceItem(dataset, DCM_OpticalPathSequence); + if (opticalPath == NULL) + { + LOG(ERROR) << "No optical path specified"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + if (!opticalPath->tagExists(DCM_ICCProfile)) + { + std::auto_ptr<DcmOtherByteOtherWord> icc(new DcmOtherByteOtherWord(DCM_ICCProfile)); + + if (!icc->putUint8Array(reinterpret_cast<const Uint8*>(profile.c_str()), profile.size()).good() || + !opticalPath->insert(icc.release()).good()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + const char* opticalPathId = NULL; + if (!opticalPath->findAndGetString(DCM_OpticalPathIdentifier, opticalPathId).good() || + opticalPathId == NULL) + { + LOG(ERROR) << "No identifier in the optical path"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + SetupDimension(dataset, opticalPathId, source, volume); +} + + +static bool ParseParameters(int& exitStatus, + OrthancWSI::DicomizerParameters& parameters, + OrthancWSI::ImagedVolumeParameters& volume, + int argc, + char* argv[]) +{ + // Declare the supported parameters + boost::program_options::options_description generic("Generic options"); + generic.add_options() + ("help", "Display this help and exit") + ("verbose", "Be verbose in logs") + ("threads", boost::program_options::value<int>()->default_value(parameters.GetThreadsCount()), + "Number of processing threads to be used") + ("openslide", boost::program_options::value<std::string>(), + "Path to the shared library of OpenSlide (not necessary if converting from standard hierarchical TIFF)") + ; + + boost::program_options::options_description source("Options for the source image"); + source.add_options() + ("dataset", boost::program_options::value<std::string>(), "Path to a JSON file containing the DICOM dataset") + ("sample-dataset", "Display a minimalistic sample DICOM dataset in JSON format, then exit") + ("reencode", boost::program_options::value<bool>(), "Whether to reencode each tile (no transcoding, much slower) (Boolean)") + ("repaint", boost::program_options::value<bool>(), "Whether to repaint the background of the image (Boolean)") + ("color", boost::program_options::value<std::string>(), "Color of the background (e.g. \"255,0,0\")") + ; + + boost::program_options::options_description pyramid("Options to construct the pyramid"); + pyramid.add_options() + ("pyramid", boost::program_options::value<bool>()->default_value(false), + "Reconstruct the full pyramid (slow) (Boolean)") + ("smooth", boost::program_options::value<bool>()->default_value(false), + "Apply smoothing when reconstructing the pyramid " + "(slower, but higher quality) (Boolean)") + ("levels", boost::program_options::value<int>(), "Number of levels in the target pyramid") + ; + + boost::program_options::options_description target("Options for the target image"); + target.add_options() + ("tile-width", boost::program_options::value<int>(), "Width of the tiles in the target image") + ("tile-height", boost::program_options::value<int>(), "Height of the tiles in the target image") + ("compression", boost::program_options::value<std::string>(), + "Compression of the target image (\"none\", \"jpeg\" or \"jpeg2000\")") + ("jpeg-quality", boost::program_options::value<int>(), "Set quality level for JPEG (0..100)") + ("max-size", boost::program_options::value<int>()->default_value(10), "Maximum size per DICOM instance (in MB)") + ("folder", boost::program_options::value<std::string>(), + "Folder where to store the output DICOM instances") + ("folder-pattern", boost::program_options::value<std::string>()->default_value("wsi-%06d.dcm"), + "Pattern for the files in the output folder") + ("orthanc", boost::program_options::value<std::string>()->default_value("http://localhost:8042/"), + "URL to the REST API of the target Orthanc server") + ("username", boost::program_options::value<std::string>(), "Username for the target Orthanc server") + ("password", boost::program_options::value<std::string>(), "Password for the target Orthanc server") + ; + + boost::program_options::options_description volumeOptions("Description of the imaged volume"); + volumeOptions.add_options() + ("imaged-width", boost::program_options::value<float>()->default_value(15), "With of the specimen (in mm)") + ("imaged-height", boost::program_options::value<float>()->default_value(15), "Height of the specimen (in mm)") + ("imaged-depth", boost::program_options::value<float>()->default_value(1), "Depth of the specimen (in mm)") + ("offset-x", boost::program_options::value<float>()->default_value(20), + "X offset the specimen, wrt. slide coordinates origin (in mm)") + ("offset-y", boost::program_options::value<float>()->default_value(40), + "Y offset the specimen, wrt. slide coordinates origin (in mm)") + ; + + boost::program_options::options_description advancedOptions("Advanced options"); + advancedOptions.add_options() + ("optical-path", boost::program_options::value<std::string>()->default_value("brightfield"), + "Optical path to be automatically added to the DICOM dataset (\"none\" or \"brightfield\")") + ("icc-profile", boost::program_options::value<std::string>(), + "Path to the ICC profile to be included. If empty, a default sRGB profile will be added.") + ("safety", boost::program_options::value<bool>()->default_value(true), + "Whether to do additional checks to verify the source image is supported (might slow down) (Boolean)") + ("lower-levels", boost::program_options::value<int>(), "Number of pyramid levels up to which multithreading " + "should be applied (only for performance/memory tuning)") + ; + + boost::program_options::options_description hidden; + hidden.add_options() + ("input", boost::program_options::value<std::string>(), "Input file"); + ; + + boost::program_options::options_description allWithoutHidden; + allWithoutHidden.add(generic).add(source).add(pyramid).add(target).add(volumeOptions).add(advancedOptions); + + boost::program_options::options_description all = allWithoutHidden; + all.add(hidden); + + boost::program_options::positional_options_description positional; + positional.add("input", 1); + + boost::program_options::variables_map options; + bool error = false; + + try + { + boost::program_options::store(boost::program_options::command_line_parser(argc, argv). + options(all).positional(positional).run(), options); + boost::program_options::notify(options); + } + catch (boost::program_options::error& e) + { + LOG(ERROR) << "Error while parsing the command-line arguments: " << e.what(); + error = true; + } + + if (!error && + options.count("sample-dataset")) + { + std::string sample; + Orthanc::EmbeddedResources::GetFileResource(sample, Orthanc::EmbeddedResources::SAMPLE_DATASET); + + std::cout << std::endl << sample << std::endl; + + return false; + } + + if (!error && + options.count("help") == 0 && + options.count("input") != 1) + { + LOG(ERROR) << "No input file was specified"; + error = true; + } + + if (error || options.count("help")) + { + std::cout << std::endl + << "Usage: " << argv[0] << " [OPTION]... [INPUT]" + << std::endl + << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." + << std::endl << std::endl + << "Create a DICOM file from a digital pathology image." + << std::endl; + + std::cout << allWithoutHidden << "\n"; + + if (error) + { + exitStatus = -1; + } + + return false; + } + + if (options.count("verbose")) + { + Orthanc::Logging::EnableInfoLevel(true); + } + + if (options.count("openslide")) + { + OrthancWSI::OpenSlideLibrary::Initialize(options["openslide"].as<std::string>()); + } + + if (options.count("pyramid") && + options["pyramid"].as<bool>()) + { + parameters.SetReconstructPyramid(true); + } + + if (options.count("smooth") && + options["smooth"].as<bool>()) + { + parameters.SetSmoothEnabled(true); + } + + if (options.count("safety") && + options["safety"].as<bool>()) + { + parameters.SetSafetyCheck(true); + } + + if (options.count("reencode") && + options["reencode"].as<bool>()) + { + parameters.SetForceReencode(true); + } + + if (options.count("repaint") && + options["repaint"].as<bool>()) + { + parameters.SetRepaintBackground(true); + } + + if (options.count("tile-width") || + options.count("tile-height")) + { + int w = 0; + if (options.count("tile-width")) + { + w = options["tile-width"].as<int>(); + } + + unsigned int h = 0; + if (options.count("tile-height")) + { + h = options["tile-height"].as<int>(); + } + + if (w < 0 || h < 0) + { + LOG(ERROR) << "Negative target tile size specified: " << w << "x" << h; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + parameters.SetTargetTileSize(w, h); + } + + parameters.SetInputFile(options["input"].as<std::string>()); + + if (options.count("color")) + { + uint8_t r, g, b; + OrthancWSI::ApplicationToolbox::ParseColor(r, g, b, options["color"].as<std::string>()); + parameters.SetBackgroundColor(r, g, b); + } + + if (options.count("compression")) + { + std::string s = options["compression"].as<std::string>(); + if (s == "none") + { + parameters.SetTargetCompression(OrthancWSI::ImageCompression_None); + } + else if (s == "jpeg") + { + parameters.SetTargetCompression(OrthancWSI::ImageCompression_Jpeg); + } + else if (s == "jpeg2000") + { + parameters.SetTargetCompression(OrthancWSI::ImageCompression_Jpeg2000); + } + else + { + LOG(ERROR) << "Unknown image compression for the target image: " << s; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + if (options.count("jpeg-quality")) + { + parameters.SetJpegQuality(options["jpeg-quality"].as<int>()); + } + + if (options.count("levels")) + { + parameters.SetPyramidLevelsCount(options["levels"].as<int>()); + } + + if (options.count("lower-levels")) + { + parameters.SetPyramidLowerLevelsCount(options["lower-levels"].as<int>()); + } + + if (options.count("threads")) + { + parameters.SetThreadsCount(options["threads"].as<int>()); + } + + if (options.count("max-size")) + { + parameters.SetDicomMaxFileSize(options["max-size"].as<int>() * 1024 * 1024); + } + + if (options.count("folder")) + { + parameters.SetTargetFolder(options["folder"].as<std::string>()); + } + + if (options.count("folder-pattern")) + { + parameters.SetTargetFolderPattern(options["folder-pattern"].as<std::string>()); + } + + if (options.count("orthanc")) + { + parameters.GetOrthancParameters().SetUrl(options["orthanc"].as<std::string>()); + + if (options.count("username") && + options.count("password")) + { + parameters.GetOrthancParameters().SetUsername(options["username"].as<std::string>()); + parameters.GetOrthancParameters().SetPassword(options["password"].as<std::string>()); + } + } + + if (options.count("dataset")) + { + parameters.SetDatasetPath(options["dataset"].as<std::string>()); + } + + if (options.count("imaged-width")) + { + volume.SetWidth(options["imaged-width"].as<float>()); + } + + if (options.count("imaged-height")) + { + volume.SetHeight(options["imaged-height"].as<float>()); + } + + if (options.count("imaged-depth")) + { + volume.SetDepth(options["imaged-depth"].as<float>()); + } + + if (options.count("offset-x")) + { + volume.SetOffsetX(options["offset-x"].as<float>()); + } + + if (options.count("offset-y")) + { + volume.SetOffsetY(options["offset-y"].as<float>()); + } + + if (options.count("optical-path")) + { + std::string s = options["optical-path"].as<std::string>(); + if (s == "none") + { + parameters.SetOpticalPath(OrthancWSI::OpticalPath_None); + } + else if (s == "brightfield") + { + parameters.SetOpticalPath(OrthancWSI::OpticalPath_Brightfield); + } + else + { + LOG(ERROR) << "Unknown optical path definition: " << s; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + if (options.count("icc-profile")) + { + parameters.SetIccProfilePath(options["icc-profile"].as<std::string>()); + } + + return true; +} + + +OrthancWSI::ITiledPyramid* OpenInputPyramid(const std::string& path, + const OrthancWSI::DicomizerParameters& parameters) +{ + LOG(WARNING) << "The input image is: " << path; + + OrthancWSI::ImageCompression format = OrthancWSI::DetectFormatFromFile(path); + LOG(WARNING) << "File format of the input image: " << EnumerationToString(format); + + switch (format) + { + case OrthancWSI::ImageCompression_Png: + return new OrthancWSI::TiledPngImage(path, + parameters.GetTargetTileWidth(512), + parameters.GetTargetTileHeight(512)); + + case OrthancWSI::ImageCompression_Jpeg: + return new OrthancWSI::TiledJpegImage(path, + parameters.GetTargetTileWidth(512), + parameters.GetTargetTileHeight(512)); + + case OrthancWSI::ImageCompression_Tiff: + { + try + { + return new OrthancWSI::HierarchicalTiff(path); + } + catch (Orthanc::OrthancException&) + { + LOG(WARNING) << "This is not a standard hierarchical TIFF file"; + } + } + + default: + break; + } + + try + { + LOG(WARNING) << "Trying to open the input pyramid with OpenSlide"; + return new OrthancWSI::OpenSlidePyramid(path, + parameters.GetTargetTileWidth(512), + parameters.GetTargetTileHeight(512)); + } + catch (Orthanc::OrthancException&) + { + LOG(ERROR) << "This file is not supported by OpenSlide"; + return NULL; + } +} + + +int main(int argc, char* argv[]) +{ + OrthancWSI::ApplicationToolbox::GlobalInitialize(); + + int exitStatus = 0; + + try + { + OrthancWSI::DicomizerParameters parameters; + OrthancWSI::ImagedVolumeParameters volume; + + if (ParseParameters(exitStatus, parameters, volume, argc, argv)) + { + std::auto_ptr<OrthancWSI::ITiledPyramid> source(OpenInputPyramid(parameters.GetInputFile(), parameters)); + if (source.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + // Create the shared DICOM tags + std::auto_ptr<DcmDataset> dataset(ParseDataset(parameters.GetDatasetPath())); + EnrichDataset(*dataset, *source, parameters, volume); + + std::auto_ptr<OrthancWSI::IFileTarget> output(parameters.CreateTarget()); + Recompress(*output, *source, *dataset, parameters, volume); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Terminating on exception: " << e.What(); + exitStatus = -1; + } + + OrthancWSI::ApplicationToolbox::GlobalFinalize(); + + return exitStatus; +}