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 "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 }
|