Mercurial > hg > orthanc-wsi
annotate Framework/Inputs/HierarchicalTiff.cpp @ 57:91fc9583b2de
big refactoring to support sparse tiling
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 24 Nov 2016 17:48:24 +0100 |
parents | 7a88c614be04 |
children | 7a3853d51c45 |
rev | line source |
---|---|
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 | |
16
7a88c614be04
preparing for precompiled headers
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
21 #include "../PrecompiledHeadersWSI.h" |
0 | 22 #include "HierarchicalTiff.h" |
23 | |
24 #include "../Orthanc/Core/Logging.h" | |
25 #include "../Orthanc/Core/OrthancException.h" | |
26 | |
27 #include <iostream> | |
28 #include <algorithm> | |
29 #include <cassert> | |
30 #include <string.h> | |
31 | |
32 namespace OrthancWSI | |
33 { | |
34 HierarchicalTiff::Level::Level(TIFF* tiff, | |
35 tdir_t directory, | |
36 unsigned int width, | |
37 unsigned int height) : | |
38 directory_(directory), | |
39 width_(width), | |
40 height_(height) | |
41 { | |
42 // Read the JPEG headers shared at that level, if any | |
43 uint8_t *tables = NULL; | |
44 uint32_t size; | |
45 if (TIFFGetField(tiff, TIFFTAG_JPEGTABLES, &size, &tables) && | |
46 size > 0 && | |
47 tables != NULL) | |
48 { | |
49 // Look for the EOI (end-of-image) tag == FF D9 | |
50 // https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format | |
51 | |
52 bool found = false; | |
53 | |
54 for (size_t i = 0; i + 1 < size; i++) | |
55 { | |
56 if (tables[i] == 0xff && | |
57 tables[i + 1] == 0xd9) | |
58 { | |
59 headers_.assign(reinterpret_cast<const char*>(tables), i); | |
60 found = true; | |
61 } | |
62 } | |
63 | |
64 if (!found) | |
65 { | |
66 headers_.assign(reinterpret_cast<const char*>(tables), size); | |
67 } | |
68 } | |
69 } | |
70 | |
71 struct HierarchicalTiff::Comparator | |
72 { | |
73 bool operator() (const HierarchicalTiff::Level& a, | |
74 const HierarchicalTiff::Level& b) const | |
75 { | |
76 return a.width_ > b.width_; | |
77 } | |
78 }; | |
79 | |
80 | |
81 void HierarchicalTiff::Finalize() | |
82 { | |
83 if (tiff_) | |
84 { | |
85 TIFFClose(tiff_); | |
86 tiff_ = NULL; | |
87 } | |
88 } | |
89 | |
90 | |
91 void HierarchicalTiff::CheckLevel(unsigned int level) const | |
92 { | |
93 if (level >= levels_.size()) | |
94 { | |
95 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
96 } | |
97 } | |
98 | |
99 | |
100 bool HierarchicalTiff::GetCurrentCompression(ImageCompression& compression) | |
101 { | |
102 uint16_t c; | |
103 if (!TIFFGetField(tiff_, TIFFTAG_COMPRESSION, &c)) | |
104 { | |
105 return false; | |
106 } | |
107 | |
108 switch (c) | |
109 { | |
110 case COMPRESSION_NONE: | |
111 compression = ImageCompression_None; | |
112 return true; | |
113 | |
114 case COMPRESSION_JPEG: | |
115 compression = ImageCompression_Jpeg; | |
116 return true; | |
117 | |
118 default: | |
119 return false; | |
120 } | |
121 } | |
122 | |
123 | |
124 bool HierarchicalTiff::GetCurrentPixelFormat(Orthanc::PixelFormat& pixelFormat, | |
125 ImageCompression compression) | |
126 { | |
127 // http://www.awaresystems.be/imaging/tiff/tifftags/baseline.html | |
128 | |
129 uint16_t channels, photometric, bpp, planar; | |
130 if (!TIFFGetField(tiff_, TIFFTAG_SAMPLESPERPIXEL, &channels) || | |
131 channels == 0 || | |
132 !TIFFGetField(tiff_, TIFFTAG_PHOTOMETRIC, &photometric) || | |
133 !TIFFGetField(tiff_, TIFFTAG_BITSPERSAMPLE, &bpp) || | |
134 !TIFFGetField(tiff_, TIFFTAG_PLANARCONFIG, &planar)) | |
135 { | |
136 return false; | |
137 } | |
138 | |
139 if (compression == ImageCompression_Jpeg && | |
140 channels == 3 && // This is a color image | |
141 bpp == 8 && | |
142 photometric == PHOTOMETRIC_YCBCR && | |
143 planar == PLANARCONFIG_CONTIG) // This is interleaved RGB | |
144 { | |
145 pixelFormat = Orthanc::PixelFormat_RGB24; | |
146 } | |
147 else | |
148 { | |
149 return false; | |
150 } | |
151 | |
152 return true; | |
153 } | |
154 | |
155 | |
156 bool HierarchicalTiff::Initialize() | |
157 { | |
158 bool first = true; | |
159 tdir_t pos = 0; | |
160 | |
161 do | |
162 { | |
163 uint32_t w, h, tw, th; | |
164 ImageCompression compression; | |
165 Orthanc::PixelFormat pixelFormat; | |
166 | |
167 if (TIFFSetDirectory(tiff_, pos) && | |
168 TIFFGetField(tiff_, TIFFTAG_IMAGEWIDTH, &w) && | |
169 TIFFGetField(tiff_, TIFFTAG_IMAGELENGTH, &h) && | |
170 TIFFGetField(tiff_, TIFFTAG_TILEWIDTH, &tw) && | |
171 TIFFGetField(tiff_, TIFFTAG_TILELENGTH, &th) && | |
172 w > 0 && | |
173 h > 0 && | |
174 tw > 0 && | |
175 th > 0 && | |
176 GetCurrentCompression(compression) && | |
177 GetCurrentPixelFormat(pixelFormat, compression)) | |
178 { | |
179 if (first) | |
180 { | |
181 tileWidth_ = tw; | |
182 tileHeight_ = th; | |
183 compression_ = compression; | |
184 pixelFormat_ = pixelFormat; | |
185 first = false; | |
186 } | |
187 else if (tw != tileWidth_ || | |
188 th != tileHeight_ || | |
189 compression_ != compression || | |
190 pixelFormat_ != pixelFormat) | |
191 { | |
192 LOG(ERROR) << "The tile size or compression of the TIFF file varies along levels, this is not supported"; | |
193 return false; | |
194 } | |
195 | |
196 levels_.push_back(Level(tiff_, pos, w, h)); | |
197 } | |
198 | |
199 pos++; | |
200 } | |
201 while (TIFFReadDirectory(tiff_)); | |
202 | |
203 if (levels_.size() == 0) | |
204 { | |
205 LOG(ERROR) << "This is not a tiled TIFF image"; | |
206 return false; | |
207 } | |
208 | |
209 std::sort(levels_.begin(), levels_.end(), Comparator()); | |
210 return true; | |
211 } | |
212 | |
213 | |
214 HierarchicalTiff::HierarchicalTiff(const std::string& path) : | |
215 tileWidth_(0), | |
216 tileHeight_(0) | |
217 { | |
218 tiff_ = TIFFOpen(path.c_str(), "r"); | |
219 if (tiff_ == NULL) | |
220 { | |
221 LOG(ERROR) << "libtiff cannot open: " << path; | |
222 throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile); | |
223 } | |
224 | |
225 if (!Initialize()) | |
226 { | |
227 Finalize(); | |
228 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
229 } | |
230 } | |
231 | |
232 | |
233 unsigned int HierarchicalTiff::GetLevelWidth(unsigned int level) const | |
234 { | |
235 CheckLevel(level); | |
236 return levels_[level].width_; | |
237 } | |
238 | |
239 | |
240 unsigned int HierarchicalTiff::GetLevelHeight(unsigned int level) const | |
241 { | |
242 CheckLevel(level); | |
243 return levels_[level].height_; | |
244 } | |
245 | |
246 | |
247 bool HierarchicalTiff::ReadRawTile(std::string& tile, | |
57
91fc9583b2de
big refactoring to support sparse tiling
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
16
diff
changeset
|
248 ImageCompression& compression, |
0 | 249 unsigned int level, |
250 unsigned int tileX, | |
251 unsigned int tileY) | |
252 { | |
253 boost::mutex::scoped_lock lock(mutex_); | |
254 | |
255 CheckLevel(level); | |
256 | |
57
91fc9583b2de
big refactoring to support sparse tiling
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
16
diff
changeset
|
257 compression = compression_; |
91fc9583b2de
big refactoring to support sparse tiling
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
16
diff
changeset
|
258 |
0 | 259 // Make the TIFF context point to the level of interest |
260 if (!TIFFSetDirectory(tiff_, levels_[level].directory_)) | |
261 { | |
262 throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile); | |
263 } | |
264 | |
265 // Get the index of the tile | |
266 ttile_t index = TIFFComputeTile(tiff_, tileX * tileWidth_, tileY * tileHeight_, 0 /*z*/, 0 /*sample*/); | |
267 | |
268 // Read the raw tile | |
269 toff_t *sizes; | |
270 if (!TIFFGetField(tiff_, TIFFTAG_TILEBYTECOUNTS, &sizes)) | |
271 { | |
272 throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile); | |
273 } | |
274 | |
275 std::string raw; | |
276 raw.resize(sizes[index]); | |
277 | |
278 tsize_t read = TIFFReadRawTile(tiff_, index, &raw[0], raw.size()); | |
279 if (read != static_cast<tsize_t>(sizes[index])) | |
280 { | |
281 throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile); | |
282 } | |
283 | |
284 const std::string& headers = levels_[level].headers_; | |
285 | |
286 // Possibly prepend the raw tile with the shared JPEG headers | |
287 if (headers.empty() || | |
288 compression_ != ImageCompression_Jpeg) | |
289 { | |
290 tile.swap(raw); // Same as "tile.assign(raw)", but optimizes memory | |
291 } | |
292 else | |
293 { | |
294 assert(compression_ == ImageCompression_Jpeg); | |
295 | |
296 // Check that the raw JPEG tile starts with the SOI (start-of-image) tag == FF D8 | |
297 if (raw.size() < 2 || | |
298 static_cast<uint8_t>(raw[0]) != 0xff || | |
299 static_cast<uint8_t>(raw[1]) != 0xd8) | |
300 { | |
301 throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile); | |
302 } | |
303 | |
304 tile.resize(headers.size() + raw.size() - 2); | |
305 memcpy(&tile[0], &headers[0], headers.size()); | |
306 memcpy(&tile[0] + headers.size(), &raw[2], raw.size() - 2); | |
307 } | |
308 | |
309 return true; | |
310 } | |
311 } |