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