0
|
1 /**
|
|
2 * Orthanc - A Lightweight, RESTful DICOM Store
|
|
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
|
|
4 * Department, University Hospital of Liege, Belgium
|
|
5 *
|
|
6 * This program is free software: you can redistribute it and/or
|
|
7 * modify it under the terms of the GNU Affero General Public License
|
|
8 * as published by the Free Software Foundation, either version 3 of
|
|
9 * the License, or (at your option) any later version.
|
|
10 *
|
|
11 * This program is distributed in the hope that it will be useful, but
|
|
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
14 * Affero General Public License for more details.
|
|
15 *
|
|
16 * You should have received a copy of the GNU Affero General Public License
|
|
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
18 **/
|
|
19
|
|
20
|
|
21 #include "PyramidReader.h"
|
|
22
|
|
23 #include "../ImageToolbox.h"
|
|
24 #include "../Orthanc/Core/Logging.h"
|
|
25 #include "../Orthanc/Core/OrthancException.h"
|
|
26
|
|
27 #include <cassert>
|
|
28
|
|
29 namespace OrthancWSI
|
|
30 {
|
|
31 class PyramidReader::SourceTile : public boost::noncopyable
|
|
32 {
|
|
33 private:
|
|
34 PyramidReader& that_;
|
|
35 unsigned int tileX_;
|
|
36 unsigned int tileY_;
|
|
37 bool hasRawTile_;
|
|
38 std::string rawTile_;
|
|
39
|
|
40 std::auto_ptr<Orthanc::ImageAccessor> decoded_;
|
|
41
|
|
42 bool IsRepaintNeeded() const
|
|
43 {
|
|
44 return (that_.parameters_.IsRepaintBackground() &&
|
|
45 ((tileX_ + 1) * that_.sourceTileWidth_ > that_.levelWidth_ ||
|
|
46 (tileY_ + 1) * that_.sourceTileHeight_ > that_.levelHeight_));
|
|
47 }
|
|
48
|
|
49 void RepaintBackground()
|
|
50 {
|
|
51 assert(decoded_.get() != NULL);
|
|
52 LOG(INFO) << "Repainting background of tile ("
|
|
53 << tileX_ << "," << tileY_ << ") at level " << that_.level_;
|
|
54
|
|
55 if ((tileY_ + 1) * that_.sourceTileHeight_ > that_.levelHeight_)
|
|
56 {
|
|
57 // Bottom overflow
|
|
58 assert(tileY_ * that_.sourceTileHeight_ < that_.levelHeight_);
|
|
59
|
|
60 unsigned int bottom = that_.levelHeight_ - tileY_ * that_.sourceTileHeight_;
|
|
61 Orthanc::ImageAccessor a = decoded_->GetRegion(0, bottom,
|
|
62 that_.sourceTileWidth_,
|
|
63 that_.sourceTileHeight_ - bottom);
|
|
64 ImageToolbox::Set(a,
|
|
65 that_.parameters_.GetBackgroundColorRed(),
|
|
66 that_.parameters_.GetBackgroundColorGreen(),
|
|
67 that_.parameters_.GetBackgroundColorBlue());
|
|
68
|
|
69 }
|
|
70
|
|
71 if ((tileX_ + 1) * that_.sourceTileWidth_ > that_.levelWidth_)
|
|
72 {
|
|
73 // Right overflow
|
|
74 assert(tileX_ * that_.sourceTileWidth_ < that_.levelWidth_);
|
|
75
|
|
76 unsigned int right = that_.levelWidth_ - tileX_ * that_.sourceTileWidth_;
|
|
77 Orthanc::ImageAccessor a = decoded_->GetRegion(right, 0,
|
|
78 that_.sourceTileWidth_ - right,
|
|
79 that_.sourceTileHeight_);
|
|
80 ImageToolbox::Set(a,
|
|
81 that_.parameters_.GetBackgroundColorRed(),
|
|
82 that_.parameters_.GetBackgroundColorGreen(),
|
|
83 that_.parameters_.GetBackgroundColorBlue());
|
|
84 }
|
|
85 }
|
|
86
|
|
87
|
|
88 public:
|
|
89 SourceTile(PyramidReader& that,
|
|
90 unsigned int tileX,
|
|
91 unsigned int tileY) :
|
|
92 that_(that),
|
|
93 tileX_(tileX),
|
|
94 tileY_(tileY)
|
|
95 {
|
|
96 if (!that_.parameters_.IsForceReencode() &&
|
|
97 !IsRepaintNeeded() &&
|
|
98 that_.source_.ReadRawTile(rawTile_, that_.level_, tileX, tileY))
|
|
99 {
|
|
100 hasRawTile_ = true;
|
|
101 }
|
|
102 else
|
|
103 {
|
|
104 hasRawTile_ = false;
|
|
105 decoded_.reset(that_.source_.DecodeTile(that_.level_, tileX, tileY));
|
|
106 if (decoded_.get() == NULL)
|
|
107 {
|
|
108 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
|
|
109 }
|
|
110
|
|
111 RepaintBackground();
|
|
112 }
|
|
113 }
|
|
114
|
|
115 bool HasRawTile() const
|
|
116 {
|
|
117 return hasRawTile_;
|
|
118 }
|
|
119
|
|
120 const std::string& GetRawTile() const
|
|
121 {
|
|
122 if (hasRawTile_)
|
|
123 {
|
|
124 return rawTile_;
|
|
125 }
|
|
126 else
|
|
127 {
|
|
128 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
|
|
129 }
|
|
130 }
|
|
131
|
|
132 const Orthanc::ImageAccessor& GetDecodedTile()
|
|
133 {
|
|
134 if (decoded_.get() == NULL)
|
|
135 {
|
|
136 if (!hasRawTile_)
|
|
137 {
|
|
138 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
|
|
139 }
|
|
140
|
|
141 decoded_.reset(ImageToolbox::DecodeTile(rawTile_, that_.source_.GetImageCompression()));
|
|
142 if (decoded_.get() == NULL)
|
|
143 {
|
|
144 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
|
|
145 }
|
|
146
|
|
147 RepaintBackground();
|
|
148 }
|
|
149
|
|
150 return *decoded_;
|
|
151 }
|
|
152 };
|
|
153
|
|
154
|
|
155 Orthanc::ImageAccessor& PyramidReader::GetOutsideTile()
|
|
156 {
|
|
157 if (outside_.get() == NULL)
|
|
158 {
|
|
159 outside_.reset(ImageToolbox::Allocate(source_.GetPixelFormat(), targetTileWidth_, targetTileHeight_));
|
|
160 ImageToolbox::Set(*outside_,
|
|
161 parameters_.GetBackgroundColorRed(),
|
|
162 parameters_.GetBackgroundColorGreen(),
|
|
163 parameters_.GetBackgroundColorBlue());
|
|
164 }
|
|
165
|
|
166 return *outside_;
|
|
167 }
|
|
168
|
|
169
|
|
170 void PyramidReader::CheckTileSize(const Orthanc::ImageAccessor& tile) const
|
|
171 {
|
|
172 if (tile.GetWidth() != sourceTileWidth_ ||
|
|
173 tile.GetHeight() != sourceTileHeight_)
|
|
174 {
|
|
175 LOG(ERROR) << "One tile in the input image has size " << tile.GetWidth() << "x" << tile.GetHeight()
|
|
176 << " instead of required " << source_.GetTileWidth() << "x" << source_.GetTileHeight();
|
|
177 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
|
|
178 }
|
|
179 }
|
|
180
|
|
181
|
|
182 void PyramidReader::CheckTileSize(const std::string& tile) const
|
|
183 {
|
|
184 if (parameters_.IsSafetyCheck())
|
|
185 {
|
|
186 std::auto_ptr<Orthanc::ImageAccessor> decoded(ImageToolbox::DecodeTile(tile, source_.GetImageCompression()));
|
|
187 CheckTileSize(*decoded);
|
|
188 }
|
|
189 }
|
|
190
|
|
191
|
|
192 PyramidReader::SourceTile& PyramidReader::AccessSourceTile(const Location& location)
|
|
193 {
|
|
194 Cache::iterator found = cache_.find(location);
|
|
195 if (found != cache_.end())
|
|
196 {
|
|
197 return *found->second;
|
|
198 }
|
|
199 else
|
|
200 {
|
|
201 SourceTile *tile = new SourceTile(*this, location.first, location.second);
|
|
202 cache_[location] = tile;
|
|
203 return *tile;
|
|
204 }
|
|
205 }
|
|
206
|
|
207
|
|
208 PyramidReader::Location PyramidReader::MapTargetToSourceLocation(unsigned int tileX,
|
|
209 unsigned int tileY)
|
|
210 {
|
|
211 return std::make_pair(tileX / (sourceTileWidth_ / targetTileWidth_),
|
|
212 tileY / (sourceTileHeight_ / targetTileHeight_));
|
|
213 }
|
|
214
|
|
215
|
|
216 PyramidReader::PyramidReader(ITiledPyramid& source,
|
|
217 unsigned int level,
|
|
218 unsigned int targetTileWidth,
|
|
219 unsigned int targetTileHeight,
|
|
220 const DicomizerParameters& parameters) :
|
|
221 source_(source),
|
|
222 level_(level),
|
|
223 levelWidth_(source.GetLevelWidth(level)),
|
|
224 levelHeight_(source.GetLevelHeight(level)),
|
|
225 sourceTileWidth_(source.GetTileWidth()),
|
|
226 sourceTileHeight_(source.GetTileHeight()),
|
|
227 targetTileWidth_(targetTileWidth),
|
|
228 targetTileHeight_(targetTileHeight),
|
|
229 parameters_(parameters)
|
|
230 {
|
|
231 if (sourceTileWidth_ % targetTileWidth_ != 0 ||
|
|
232 sourceTileHeight_ % targetTileHeight_ != 0)
|
|
233 {
|
|
234 LOG(ERROR) << "When resampling the tile size, it must be a integer divisor of the original tile size";
|
|
235 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
|
|
236 }
|
|
237 }
|
|
238
|
|
239
|
|
240 PyramidReader::~PyramidReader()
|
|
241 {
|
|
242 for (Cache::iterator it = cache_.begin(); it != cache_.end(); ++it)
|
|
243 {
|
|
244 assert(it->second != NULL);
|
|
245 delete it->second;
|
|
246 }
|
|
247 }
|
|
248
|
|
249
|
|
250 const std::string* PyramidReader::GetRawTile(unsigned int tileX,
|
|
251 unsigned int tileY)
|
|
252 {
|
|
253 if (sourceTileWidth_ != targetTileWidth_ ||
|
|
254 sourceTileHeight_ != targetTileHeight_)
|
|
255 {
|
|
256 return NULL;
|
|
257 }
|
|
258
|
|
259 SourceTile& source = AccessSourceTile(MapTargetToSourceLocation(tileX, tileY));
|
|
260 if (source.HasRawTile())
|
|
261 {
|
|
262 CheckTileSize(source.GetRawTile());
|
|
263 return &source.GetRawTile();
|
|
264 }
|
|
265 else
|
|
266 {
|
|
267 return NULL;
|
|
268 }
|
|
269 }
|
|
270
|
|
271
|
|
272 Orthanc::ImageAccessor PyramidReader::GetDecodedTile(unsigned int tileX,
|
|
273 unsigned int tileY)
|
|
274 {
|
|
275 if (tileX * targetTileWidth_ >= levelWidth_ ||
|
|
276 tileY * targetTileHeight_ >= levelHeight_)
|
|
277 {
|
|
278 // Accessing a tile out of the source image
|
|
279 return GetOutsideTile();
|
|
280 }
|
|
281
|
|
282 SourceTile& source = AccessSourceTile(MapTargetToSourceLocation(tileX, tileY));
|
|
283 const Orthanc::ImageAccessor& tile = source.GetDecodedTile();
|
|
284
|
|
285 CheckTileSize(tile);
|
|
286
|
|
287 assert(sourceTileWidth_ % targetTileWidth_ == 0 &&
|
|
288 sourceTileHeight_ % targetTileHeight_ == 0);
|
|
289
|
|
290 unsigned int xx = tileX % (sourceTileWidth_ / targetTileWidth_);
|
|
291 unsigned int yy = tileY % (sourceTileHeight_ / targetTileHeight_);
|
|
292
|
|
293 const uint8_t* bytes =
|
|
294 reinterpret_cast<const uint8_t*>(tile.GetConstRow(yy * targetTileHeight_)) +
|
|
295 GetBytesPerPixel(tile.GetFormat()) * xx * targetTileWidth_;
|
|
296
|
|
297 Orthanc::ImageAccessor region;
|
|
298 region.AssignReadOnly(tile.GetFormat(),
|
|
299 targetTileWidth_,
|
|
300 targetTileHeight_,
|
|
301 tile.GetPitch(), bytes);
|
|
302
|
|
303 return region;
|
|
304 }
|
|
305 }
|