comparison Framework/Inputs/CytomineImage.cpp @ 243:7d189530d648

added class CytomineImage
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 06 Dec 2021 16:15:10 +0100
parents
children 4273518c2009
comparison
equal deleted inserted replaced
242:49f647ed1b4c 243:7d189530d648
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 * Copyright (C) 2017-2021 Osimis S.A., Belgium
6 * Copyright (C) 2021-2021 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
7 *
8 * This program is free software: you can redistribute it and/or
9 * modify it under the terms of the GNU Affero General Public License
10 * as published by the Free Software Foundation, either version 3 of
11 * the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Affero General Public License for more details.
17 *
18 * You should have received a copy of the GNU Affero General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 **/
21
22
23 #include "CytomineImage.h"
24
25 #include <Images/ImageProcessing.h>
26 #include <Images/JpegWriter.h>
27 #include <Images/PngReader.h>
28 #include <Logging.h>
29 #include <OrthancException.h>
30 #include <Toolbox.h>
31
32 #include <boost/date_time/posix_time/posix_time.hpp>
33 #include <boost/lexical_cast.hpp>
34 #include <openssl/hmac.h>
35
36
37 #if (OPENSSL_VERSION_NUMBER < 0x10100000L)
38 // OpenSSL < 1.1.0
39
40 namespace
41 {
42 class HmacContext : public boost::noncopyable
43 {
44 private:
45 HMAC_CTX hmac_;
46
47 public:
48 HmacContext()
49 {
50 HMAC_CTX_init(&hmac_);
51 }
52
53 ~HmacContext()
54 {
55 HMAC_CTX_cleanup(&hmac_);
56 }
57
58 HMAC_CTX* GetObject()
59 {
60 return &hmac_;
61 }
62 };
63 }
64
65 #else
66 // OpenSSL >= 1.1.0
67
68 namespace
69 {
70 class HmacContext : public boost::noncopyable
71 {
72 private:
73 HMAC_CTX* hmac_;
74
75 public:
76 HmacContext()
77 {
78 hmac_ = HMAC_CTX_new();
79 if (hmac_ == NULL)
80 {
81 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
82 }
83 }
84
85 ~HmacContext()
86 {
87 HMAC_CTX_free(hmac_);
88 }
89
90 HMAC_CTX* GetObject()
91 {
92 return hmac_;
93 }
94 };
95 }
96
97 #endif
98
99
100 namespace OrthancWSI
101 {
102 bool CytomineImage::GetCytomine(std::string& target,
103 const std::string& uri,
104 Orthanc::MimeType contentType) const
105 {
106 if (uri.empty() ||
107 uri[0] == '/')
108 {
109 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
110 }
111
112 std::string t = Orthanc::EnumerationToString(contentType);
113
114 std::string date;
115
116 {
117 const boost::posix_time::ptime now = boost::posix_time::second_clock::universal_time();
118
119 std::stringstream stream;
120 stream.imbue(std::locale(std::locale::classic(),
121 new boost::posix_time::time_facet("%a, %d %b %Y %H:%M:%S +0000")));
122 stream << now;
123 date = stream.str();
124 }
125
126 std::string auth;
127
128 {
129 const std::string token = "GET\n\n" + t + "\n" + date + "\n/" + uri;
130
131 HmacContext hmac;
132
133 unsigned char md[64];
134 unsigned int length = 0;
135
136 if (HMAC_Init_ex(hmac.GetObject(), privateKey_.c_str(), privateKey_.length(), EVP_sha1(), NULL) != 1 ||
137 HMAC_Update(hmac.GetObject(), reinterpret_cast<const unsigned char*>(token.c_str()), token.size()) != 1 ||
138 HMAC_Final(hmac.GetObject(), md, &length) != 1)
139 {
140 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
141 }
142
143 Orthanc::Toolbox::EncodeBase64(auth, std::string(reinterpret_cast<const char*>(md), length));
144 }
145
146 Orthanc::HttpClient c(parameters_, uri);
147 c.AddHeader("content-type", t);
148 c.AddHeader("authorization", "CYTOMINE " + publicKey_ + ":" + auth);
149 c.AddHeader("date", date);
150
151 return c.Apply(target);
152 }
153
154
155 void CytomineImage::ReadRegion(Orthanc::ImageAccessor& target,
156 unsigned int level,
157 unsigned int x,
158 unsigned int y)
159 {
160 if (level != 0 ||
161 x >= fullWidth_ ||
162 y >= fullHeight_)
163 {
164 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
165 }
166
167 unsigned int w = std::min(tileWidth_, fullWidth_ - x);
168 unsigned int h = std::min(tileHeight_, fullHeight_ - y);
169
170 const std::string uri = ("api/imageinstance/" + boost::lexical_cast<std::string>(imageId_) + "/window-" +
171 boost::lexical_cast<std::string>(x) + "-" +
172 boost::lexical_cast<std::string>(y) + "-" +
173 boost::lexical_cast<std::string>(w) + "-" +
174 boost::lexical_cast<std::string>(h) + ".png");
175
176 std::string png;
177 if (!GetCytomine(png, uri, Orthanc::MimeType_Png))
178 {
179 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, "Cannot read a tile from Cytomine");
180 }
181
182 Orthanc::PngReader reader;
183 reader.ReadFromMemory(png);
184
185 if (reader.GetWidth() != w ||
186 reader.GetHeight() != h)
187 {
188 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, "Cytomine returned a tile of bad size");
189 }
190
191 Orthanc::ImageProcessing::Set(target, 255, 255, 255, 255);
192
193 Orthanc::ImageAccessor region;
194 target.GetRegion(region, 0, 0, w, h);
195
196 Orthanc::ImageProcessing::Copy(target, reader);
197 }
198
199
200 CytomineImage::CytomineImage(const Orthanc::WebServiceParameters& parameters,
201 const std::string& publicKey,
202 const std::string& privateKey,
203 int imageId,
204 unsigned int tileWidth,
205 unsigned int tileHeight) :
206 parameters_(parameters),
207 publicKey_(publicKey),
208 privateKey_(privateKey),
209 imageId_(imageId),
210 tileWidth_(tileWidth),
211 tileHeight_(tileHeight)
212 {
213 if (tileWidth_ < 16 ||
214 tileHeight_ < 16)
215 {
216 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
217 }
218
219 std::string info;
220 if (!GetCytomine(info, "api/imageinstance/" + boost::lexical_cast<std::string>(imageId_) + ".json", Orthanc::MimeType_Json))
221 {
222 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource, "Inexistent image in Cytomine: " +
223 boost::lexical_cast<std::string>(imageId));
224 }
225
226 const char* const WIDTH = "width";
227 const char* const HEIGHT = "height";
228
229 Json::Value json;
230 if (!Orthanc::Toolbox::ReadJson(json, info) ||
231 !json.isMember(WIDTH) ||
232 !json.isMember(HEIGHT) ||
233 json[WIDTH].type() != Json::intValue ||
234 json[HEIGHT].type() != Json::intValue ||
235 json[WIDTH].asInt() < 0 ||
236 json[HEIGHT].asInt() < 0)
237 {
238 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, "Unsupported version of the Cytomine REST API");
239 }
240
241 fullWidth_ = json[WIDTH].asUInt();
242 fullHeight_ = json[HEIGHT].asUInt();
243 LOG(INFO) << "Reading an image of size " << fullWidth_ << "x" << fullHeight_ << " from Cytomine";
244 }
245
246
247 unsigned int CytomineImage::GetLevelWidth(unsigned int level) const
248 {
249 if (level == 0)
250 {
251 return fullWidth_;
252 }
253 else
254 {
255 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
256 }
257 }
258
259
260 unsigned int CytomineImage::GetLevelHeight(unsigned int level) const
261 {
262 if (level == 0)
263 {
264 return fullHeight_;
265 }
266 else
267 {
268 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
269 }
270 }
271 }