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