Commit ce60603a authored by Andreas Tille's avatar Andreas Tille

New upstream version 0.1+dfsg

parent 1d193e95
repo: 4a7a53257c7df5a97aea39377b8c9a6e815c9763
node: b1d6a0efe09b27f8d7095a792823c6da658b45b3
branch: OrthancWSI-0.1
latesttag: null
latesttagdistance: 35
Orthanc for Whole-Slide Imaging
===============================
Authors
-------
* Sebastien Jodogne <s.jodogne@gmail.com>
Department of Medical Physics
University Hospital of Liege
Belgium
Overall design and lead developer.
/**
* 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 "ApplicationToolbox.h"
#include "../Framework/Inputs/OpenSlideLibrary.h"
#include "../Framework/Orthanc/Core/HttpClient.h"
#include "../Framework/Orthanc/Core/Logging.h"
#include "../Framework/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h"
#include "../Framework/Orthanc/OrthancServer/FromDcmtkBridge.h"
#include <boost/lexical_cast.hpp>
#include <boost/regex.hpp>
namespace OrthancWSI
{
namespace ApplicationToolbox
{
void GlobalInitialize()
{
Orthanc::Logging::Initialize();
Orthanc::HttpClient::InitializeOpenSsl();
Orthanc::HttpClient::GlobalInitialize();
Orthanc::FromDcmtkBridge::InitializeDictionary();
}
void GlobalFinalize()
{
OrthancWSI::OpenSlideLibrary::Finalize();
Orthanc::HttpClient::GlobalFinalize();
Orthanc::HttpClient::FinalizeOpenSsl();
}
static void PrintProgress(Orthanc::BagOfTasksProcessor::Handle* handle,
bool* done)
{
unsigned int previous = 0;
while (!*done)
{
unsigned int progress = static_cast<unsigned int>(100.0f * handle->GetProgress());
if (previous != progress)
{
LOG(WARNING) << "Progress: " << progress << "%";
previous = progress;
}
boost::this_thread::sleep(boost::posix_time::milliseconds(100));
}
}
void Execute(Orthanc::BagOfTasks& tasks,
unsigned int threadsCount)
{
if (threadsCount > 1)
{
// Submit the tasks to a newly-created processor
LOG(WARNING) << "Running " << tasks.GetSize() << " tasks";
LOG(WARNING) << "Using " << threadsCount << " threads for the computation";
Orthanc::BagOfTasksProcessor processor(threadsCount);
std::auto_ptr<Orthanc::BagOfTasksProcessor::Handle> handle(processor.Submit(tasks));
// Start a thread to display the progress
bool done = false;
boost::thread progress(PrintProgress, handle.get(), &done);
// Wait for the completion of the tasks
bool success = handle->Join();
// Stop the progress-printing thread
done = true;
if (progress.joinable())
{
progress.join();
}
if (success)
{
LOG(WARNING) << "All tasks have finished";
}
else
{
LOG(ERROR) << "Error has occurred, aborting";
throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
}
}
else
{
LOG(WARNING) << "Running " << tasks.GetSize() << " tasks without multithreading";
unsigned int previous = 0;
unsigned int size = tasks.GetSize();
// No multithreading
while (!tasks.IsEmpty())
{
std::auto_ptr<Orthanc::ICommand> task(tasks.Pop());
if (task->Execute())
{
unsigned int progress = static_cast<unsigned int>(100.0f *
static_cast<float>((size - tasks.GetSize())) /
static_cast<float>(size));
if (progress != previous)
{
LOG(WARNING) << "Progress: " << progress << "%";
previous = progress;
}
}
else
{
LOG(ERROR) << "Error has occurred, aborting";
throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
}
}
}
}
void ParseColor(uint8_t& red,
uint8_t& green,
uint8_t& blue,
const std::string& color)
{
boost::regex pattern("([0-9]*),([0-9]*),([0-9]*)");
bool ok = false;
boost::cmatch what;
try
{
if (regex_match(color.c_str(), what, pattern))
{
int r = boost::lexical_cast<int>(what[1]);
int g = boost::lexical_cast<int>(what[2]);
int b = boost::lexical_cast<int>(what[3]);
if (r >= 0 && r <= 255 &&
g >= 0 && g <= 255 &&
b >= 0 && b <= 255)
{
red = static_cast<uint8_t>(r);
green = static_cast<uint8_t>(g);
blue = static_cast<uint8_t>(b);
ok = true;
}
}
}
catch (boost::bad_lexical_cast&)
{
}
if (!ok)
{
LOG(ERROR) << "Bad color specification: " << color;
throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
}
}
void PrintVersion(const char* path)
{
std::cout
<< path << " " << ORTHANC_WSI_VERSION << std::endl
<< "Copyright (C) 2012-2016 Sebastien Jodogne, "
<< "Medical Physics Department, University Hospital of Liege (Belgium)" << std::endl
<< "Licensing AGPL: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl.html>." << std::endl
<< "This is free software: you are free to change and redistribute it." << std::endl
<< "There is NO WARRANTY, to the extent permitted by law." << std::endl
<< std::endl
<< "Written by Sebastien Jodogne <s.jodogne@gmail.com>" << std::endl;
}
}
}
/**
* 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/>.
**/
#pragma once
#include "../Framework/Orthanc/Core/MultiThreading/BagOfTasks.h"
#include <string>
#include <stdint.h>
namespace OrthancWSI
{
namespace ApplicationToolbox
{
void GlobalInitialize();
void GlobalFinalize();
void Execute(Orthanc::BagOfTasks& tasks,
unsigned int threadsCount);
void ParseColor(uint8_t& red,
uint8_t& green,
uint8_t& blue,
const std::string& color);
void PrintVersion(const char* path);
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/**
* 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 "../PrecompiledHeadersWSI.h"
#include "PyramidReader.h"
#include "../ImageToolbox.h"
#include "../Orthanc/Core/Logging.h"
#include "../Orthanc/Core/OrthancException.h"
#include <cassert>
namespace OrthancWSI
{
class PyramidReader::SourceTile : public boost::noncopyable
{
private:
PyramidReader& that_;
unsigned int tileX_;
unsigned int tileY_;
bool hasRawTile_;
std::string rawTile_;
std::auto_ptr<Orthanc::ImageAccessor> decoded_;
bool IsRepaintNeeded() const
{
return (that_.parameters_.IsRepaintBackground() &&
((tileX_ + 1) * that_.sourceTileWidth_ > that_.levelWidth_ ||
(tileY_ + 1) * that_.sourceTileHeight_ > that_.levelHeight_));
}
void RepaintBackground()
{
assert(decoded_.get() != NULL);
LOG(INFO) << "Repainting background of tile ("
<< tileX_ << "," << tileY_ << ") at level " << that_.level_;
if ((tileY_ + 1) * that_.sourceTileHeight_ > that_.levelHeight_)
{
// Bottom overflow
assert(tileY_ * that_.sourceTileHeight_ < that_.levelHeight_);
unsigned int bottom = that_.levelHeight_ - tileY_ * that_.sourceTileHeight_;
Orthanc::ImageAccessor a = decoded_->GetRegion(0, bottom,
that_.sourceTileWidth_,
that_.sourceTileHeight_ - bottom);
ImageToolbox::Set(a,
that_.parameters_.GetBackgroundColorRed(),
that_.parameters_.GetBackgroundColorGreen(),
that_.parameters_.GetBackgroundColorBlue());
}
if ((tileX_ + 1) * that_.sourceTileWidth_ > that_.levelWidth_)
{
// Right overflow
assert(tileX_ * that_.sourceTileWidth_ < that_.levelWidth_);
unsigned int right = that_.levelWidth_ - tileX_ * that_.sourceTileWidth_;
Orthanc::ImageAccessor a = decoded_->GetRegion(right, 0,
that_.sourceTileWidth_ - right,
that_.sourceTileHeight_);
ImageToolbox::Set(a,
that_.parameters_.GetBackgroundColorRed(),
that_.parameters_.GetBackgroundColorGreen(),
that_.parameters_.GetBackgroundColorBlue());
}
}
public:
SourceTile(PyramidReader& that,
unsigned int tileX,
unsigned int tileY) :
that_(that),
tileX_(tileX),
tileY_(tileY)
{
if (!that_.parameters_.IsForceReencode() &&
!IsRepaintNeeded() &&
that_.source_.ReadRawTile(rawTile_, that_.level_, tileX, tileY))
{
hasRawTile_ = true;
}
else
{
hasRawTile_ = false;
decoded_.reset(that_.source_.DecodeTile(that_.level_, tileX, tileY));
if (decoded_.get() == NULL)
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
}
RepaintBackground();
}
}
bool HasRawTile() const
{
return hasRawTile_;
}
const std::string& GetRawTile() const
{
if (hasRawTile_)
{
return rawTile_;
}
else
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
}
}
const Orthanc::ImageAccessor& GetDecodedTile()
{
if (decoded_.get() == NULL)
{
if (!hasRawTile_)
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
}
decoded_.reset(ImageToolbox::DecodeTile(rawTile_, that_.source_.GetImageCompression()));
if (decoded_.get() == NULL)
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
}
RepaintBackground();
}
return *decoded_;
}
};
Orthanc::ImageAccessor& PyramidReader::GetOutsideTile()
{
if (outside_.get() == NULL)
{
outside_.reset(ImageToolbox::Allocate(source_.GetPixelFormat(), targetTileWidth_, targetTileHeight_));
ImageToolbox::Set(*outside_,
parameters_.GetBackgroundColorRed(),
parameters_.GetBackgroundColorGreen(),
parameters_.GetBackgroundColorBlue());
}
return *outside_;
}
void PyramidReader::CheckTileSize(const Orthanc::ImageAccessor& tile) const
{
if (tile.GetWidth() != sourceTileWidth_ ||
tile.GetHeight() != sourceTileHeight_)
{
LOG(ERROR) << "One tile in the input image has size " << tile.GetWidth() << "x" << tile.GetHeight()
<< " instead of required " << source_.GetTileWidth() << "x" << source_.GetTileHeight();
throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
}
}
void PyramidReader::CheckTileSize(const std::string& tile) const
{
if (parameters_.IsSafetyCheck())
{
std::auto_ptr<Orthanc::ImageAccessor> decoded(ImageToolbox::DecodeTile(tile, source_.GetImageCompression()));
CheckTileSize(*decoded);
}
}
PyramidReader::SourceTile& PyramidReader::AccessSourceTile(const Location& location)
{
Cache::iterator found = cache_.find(location);
if (found != cache_.end())
{
return *found->second;
}
else
{
SourceTile *tile = new SourceTile(*this, location.first, location.second);
cache_[location] = tile;
return *tile;
}
}
PyramidReader::Location PyramidReader::MapTargetToSourceLocation(unsigned int tileX,
unsigned int tileY)
{
return std::make_pair(tileX / (sourceTileWidth_ / targetTileWidth_),
tileY / (sourceTileHeight_ / targetTileHeight_));
}
PyramidReader::PyramidReader(ITiledPyramid& source,
unsigned int level,
unsigned int targetTileWidth,
unsigned int targetTileHeight,
const DicomizerParameters& parameters) :
source_(source),
level_(level),
levelWidth_(source.GetLevelWidth(level)),
levelHeight_(source.GetLevelHeight(level)),
sourceTileWidth_(source.GetTileWidth()),
sourceTileHeight_(source.GetTileHeight()),
targetTileWidth_(targetTileWidth),
targetTileHeight_(targetTileHeight),
parameters_(parameters)
{
if (sourceTileWidth_ % targetTileWidth_ != 0 ||
sourceTileHeight_ % targetTileHeight_ != 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);
}
}
PyramidReader::~PyramidReader()
{
for (Cache::iterator it = cache_.begin(); it != cache_.end(); ++it)
{
assert(it->second != NULL);
delete it->second;
}
}
const std::string* PyramidReader::GetRawTile(unsigned int tileX,
unsigned int tileY)
{
if (sourceTileWidth_ != targetTileWidth_ ||
sourceTileHeight_ != targetTileHeight_)
{
return NULL;
}
SourceTile& source = AccessSourceTile(MapTargetToSourceLocation(tileX, tileY));
if (source.HasRawTile())
{
CheckTileSize(source.GetRawTile());
return &source.GetRawTile();
}
else
{
return NULL;
}
}
Orthanc::ImageAccessor PyramidReader::GetDecodedTile(unsigned int tileX,
unsigned int tileY)
{
if (tileX * targetTileWidth_ >= levelWidth_ ||
tileY * targetTileHeight_ >= levelHeight_)
{
// Accessing a tile out of the source image
return GetOutsideTile();
}
SourceTile& source = AccessSourceTile(MapTargetToSourceLocation(tileX, tileY));
const Orthanc::ImageAccessor& tile = source.GetDecodedTile();
CheckTileSize(tile);
assert(sourceTileWidth_ % targetTileWidth_ == 0 &&
sourceTileHeight_ % targetTileHeight_ == 0);
unsigned int xx = tileX % (sourceTileWidth_ / targetTileWidth_);
unsigned int yy = tileY % (sourceTileHeight_ / targetTileHeight_);
const uint8_t* bytes =
reinterpret_cast<const uint8_t*>(tile.GetConstRow(yy * targetTileHeight_)) +
GetBytesPerPixel(tile.GetFormat()) * xx * targetTileWidth_;
Orthanc::ImageAccessor region;
region.AssignReadOnly(tile.GetFormat(),
targetTileWidth_,
targetTileHeight_,
tile.GetPitch(), bytes);
return region;
}
}
/**
* 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/>.
**/
#pragma once
#include "../Inputs/ITiledPyramid.h"
#include "../DicomizerParameters.h"
#include <map>
#include <memory>
namespace OrthancWSI
{
// This class is not thread-safe
class PyramidReader : public boost::noncopyable
{
private:
class SourceTile;
typedef std::pair<unsigned int, unsigned int> Location;
typedef std::map<Location, SourceTile*> Cache;
ITiledPyramid& source_;
unsigned int level_;
unsigned int levelWidth_;
unsigned int levelHeight_;
unsigned int sourceTileWidth_;
unsigned int sourceTileHeight_;
unsigned int targetTileWidth_;
unsigned int targetTileHeight_;
const DicomizerParameters& parameters_;
Cache cache_;
std::auto_ptr<Orthanc::ImageAccessor> outside_;
Orthanc::ImageAccessor& GetOutsideTile();