Mercurial > hg > orthanc-stone
comparison RenderingPlugin/Sources/Plugin.cpp @ 1887:aa4ed1cf4e8d
refactoring using DataAugmentationParameters
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 17 Jan 2022 12:38:04 +0100 |
parents | ca89fec8c48f |
children | 9bdce2c91620 |
comparison
equal
deleted
inserted
replaced
1886:ca89fec8c48f | 1887:aa4ed1cf4e8d |
---|---|
181 "Bad value for " + key + ": " + value); | 181 "Bad value for " + key + ": " + value); |
182 } | 182 } |
183 } | 183 } |
184 | 184 |
185 | 185 |
186 static unsigned int ParseUnsignedInteger(const std::string& key, | |
187 const std::string& value) | |
188 { | |
189 uint32_t result; | |
190 | |
191 if (Orthanc::SerializationToolbox::ParseUnsignedInteger32(result, value)) | |
192 { | |
193 return result; | |
194 } | |
195 else | |
196 { | |
197 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, | |
198 "Bad value for " + key + ": " + value); | |
199 } | |
200 } | |
201 | |
202 | |
203 | |
204 class DataAugmentationParameters : public boost::noncopyable | |
205 { | |
206 private: | |
207 double angleRadians_; | |
208 double scaling_; | |
209 double offsetX_; | |
210 double offsetY_; | |
211 bool flipX_; | |
212 bool flipY_; | |
213 bool hasResize_; | |
214 unsigned int targetWidth_; | |
215 unsigned int targetHeight_; | |
216 | |
217 void ApplyInternal(Orthanc::ImageAccessor& target, | |
218 const Orthanc::ImageAccessor& source) | |
219 { | |
220 if (source.GetWidth() == 0 || | |
221 source.GetHeight() == 0) | |
222 { | |
223 Orthanc::ImageProcessing::Set(target, 0); // Clear the image | |
224 } | |
225 else if (target.GetWidth() == 0 || | |
226 target.GetHeight() == 0) | |
227 { | |
228 // Nothing to do | |
229 } | |
230 else | |
231 { | |
232 OrthancStone::AffineTransform2D transform = ComputeTransform(source.GetWidth(), source.GetHeight()); | |
233 | |
234 OrthancStone::ImageInterpolation interpolation; | |
235 | |
236 if (source.GetFormat() == Orthanc::PixelFormat_RGB24) | |
237 { | |
238 LOG(WARNING) << "Bilinear interpolation for color images is not implemented yet"; | |
239 interpolation = OrthancStone::ImageInterpolation_Nearest; | |
240 } | |
241 else | |
242 { | |
243 interpolation = OrthancStone::ImageInterpolation_Bilinear; | |
244 } | |
245 | |
246 transform.Apply(target, source, interpolation, true /* clear */); | |
247 } | |
248 } | |
249 | |
250 | |
251 Orthanc::ImageAccessor* ApplyUnchecked(const Orthanc::ImageAccessor& source) | |
252 { | |
253 std::unique_ptr<Orthanc::ImageAccessor> target; | |
254 | |
255 if (hasResize_) | |
256 { | |
257 target.reset(new Orthanc::Image(source.GetFormat(), targetWidth_, targetHeight_, false)); | |
258 } | |
259 else | |
260 { | |
261 target.reset(new Orthanc::Image(source.GetFormat(), source.GetWidth(), source.GetHeight(), false)); | |
262 } | |
263 | |
264 ApplyInternal(*target, source); | |
265 return target.release(); | |
266 } | |
267 | |
268 | |
269 public: | |
270 DataAugmentationParameters() | |
271 { | |
272 Clear(); | |
273 } | |
274 | |
275 | |
276 void Clear() | |
277 { | |
278 angleRadians_ = 0; | |
279 scaling_ = 1; | |
280 offsetX_ = 0; | |
281 offsetY_ = 0; | |
282 flipX_ = false; | |
283 flipY_ = false; | |
284 hasResize_ = false; | |
285 targetWidth_ = 0; | |
286 targetHeight_ = 0; | |
287 } | |
288 | |
289 | |
290 OrthancStone::AffineTransform2D ComputeTransform(unsigned int sourceWidth, | |
291 unsigned int sourceHeight) const | |
292 { | |
293 unsigned int w = (hasResize_ ? targetWidth_ : sourceWidth); | |
294 unsigned int h = (hasResize_ ? targetHeight_ : sourceHeight); | |
295 | |
296 if (w == 0 || | |
297 h == 0 || | |
298 sourceWidth == 0 || | |
299 sourceHeight == 0) | |
300 { | |
301 // Division by zero | |
302 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
303 } | |
304 | |
305 double r = std::min(static_cast<double>(w) / static_cast<double>(sourceWidth), | |
306 static_cast<double>(h) / static_cast<double>(sourceHeight)); | |
307 | |
308 OrthancStone::AffineTransform2D resize = OrthancStone::AffineTransform2D::Combine( | |
309 OrthancStone::AffineTransform2D::CreateOffset(static_cast<double>(w) / 2.0, | |
310 static_cast<double>(h) / 2.0), | |
311 OrthancStone::AffineTransform2D::CreateScaling(r, r)); | |
312 | |
313 OrthancStone::AffineTransform2D dataAugmentation = OrthancStone::AffineTransform2D::Combine( | |
314 OrthancStone::AffineTransform2D::CreateScaling(scaling_, scaling_), | |
315 OrthancStone::AffineTransform2D::CreateOffset(offsetX_, offsetY_), | |
316 OrthancStone::AffineTransform2D::CreateRotation(angleRadians_), | |
317 OrthancStone::AffineTransform2D::CreateOffset(-static_cast<double>(sourceWidth) / 2.0, | |
318 -static_cast<double>(sourceHeight) / 2.0), | |
319 OrthancStone::AffineTransform2D::CreateFlip(flipX_, flipY_, sourceWidth, sourceHeight)); | |
320 | |
321 return OrthancStone::AffineTransform2D::Combine(resize, dataAugmentation); | |
322 } | |
323 | |
324 | |
325 bool ParseParameter(const std::string& key, | |
326 const std::string& value) | |
327 { | |
328 if (key == "angle") | |
329 { | |
330 double angle = ParseDouble(key, value); | |
331 angleRadians_ = angle / 180.0 * boost::math::constants::pi<double>(); | |
332 return true; | |
333 } | |
334 else if (key == "scaling") | |
335 { | |
336 scaling_ = ParseDouble(key, value); | |
337 return true; | |
338 } | |
339 else if (key == "offset-x") | |
340 { | |
341 offsetX_ = ParseDouble(key, value); | |
342 return true; | |
343 } | |
344 else if (key == "offset-y") | |
345 { | |
346 offsetY_ = ParseDouble(key, value); | |
347 return true; | |
348 } | |
349 else if (key == "flip-x") | |
350 { | |
351 flipX_ = ParseBoolean(key, value); | |
352 return true; | |
353 } | |
354 else if (key == "flip-y") | |
355 { | |
356 flipY_ = ParseBoolean(key, value); | |
357 return true; | |
358 } | |
359 else if (key == "resize") | |
360 { | |
361 std::vector<std::string> tokens; | |
362 Orthanc::Toolbox::TokenizeString(tokens, value, ','); | |
363 if (tokens.size() != 2) | |
364 { | |
365 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, | |
366 "Must provide two integers separated by commas in " + key + ": " + value); | |
367 } | |
368 else | |
369 { | |
370 targetWidth_ = ParseUnsignedInteger(key, tokens[0]); | |
371 targetHeight_ = ParseUnsignedInteger(key, tokens[1]); | |
372 hasResize_ = true; | |
373 return true; | |
374 } | |
375 } | |
376 else | |
377 { | |
378 return false; | |
379 } | |
380 } | |
381 | |
382 | |
383 Orthanc::ImageAccessor* Apply(const Orthanc::ImageAccessor& source) | |
384 { | |
385 if (source.GetFormat() != Orthanc::PixelFormat_RGB24 && | |
386 source.GetFormat() != Orthanc::PixelFormat_Float32) | |
387 { | |
388 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); | |
389 } | |
390 else | |
391 { | |
392 return ApplyUnchecked(source); | |
393 } | |
394 } | |
395 | |
396 | |
397 Orthanc::ImageAccessor* ApplyBinaryMask(const Orthanc::ImageAccessor& source) | |
398 { | |
399 if (source.GetFormat() != Orthanc::PixelFormat_Grayscale8) | |
400 { | |
401 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat, | |
402 "A segmentation mask should be a grayscale image"); | |
403 } | |
404 else | |
405 { | |
406 std::unique_ptr<Orthanc::ImageAccessor> target(ApplyUnchecked(source)); | |
407 | |
408 const unsigned int h = target->GetHeight(); | |
409 const unsigned int w = target->GetWidth(); | |
410 | |
411 for (unsigned int y = 0; y < h; y++) | |
412 { | |
413 uint8_t* p = reinterpret_cast<uint8_t*>(target->GetRow(y)); | |
414 for (unsigned int x = 0; x < w; x++, p++) | |
415 { | |
416 if (*p < 128) | |
417 { | |
418 *p = 0; | |
419 } | |
420 else | |
421 { | |
422 *p = 255; | |
423 } | |
424 } | |
425 } | |
426 | |
427 return target.release(); | |
428 } | |
429 } | |
430 }; | |
431 | |
432 | |
186 static void RenderNumpyFrame(OrthancPluginRestOutput* output, | 433 static void RenderNumpyFrame(OrthancPluginRestOutput* output, |
187 const char* url, | 434 const char* url, |
188 const OrthancPluginHttpRequest* request) | 435 const OrthancPluginHttpRequest* request) |
189 { | 436 { |
190 double angleRadians = 0; | 437 DataAugmentationParameters dataAugmentation; |
191 double scaling = 1; | |
192 double offsetX = 0; | |
193 double offsetY = 0; | |
194 bool flipX = false; | |
195 bool flipY = false; | |
196 bool compress = false; | 438 bool compress = false; |
197 | 439 |
198 for (uint32_t i = 0; i < request->getCount; i++) | 440 for (uint32_t i = 0; i < request->getCount; i++) |
199 { | 441 { |
200 std::string key(request->getKeys[i]); | 442 std::string key(request->getKeys[i]); |
201 std::string value(request->getValues[i]); | 443 std::string value(request->getValues[i]); |
202 | 444 |
203 if (key == "angle") | 445 if (!dataAugmentation.ParseParameter(key, value)) |
204 { | 446 { |
205 double angle = ParseDouble(key, value); | 447 if (key == "compress") |
206 angleRadians = angle / 180.0 * boost::math::constants::pi<double>(); | 448 { |
207 } | 449 compress = ParseBoolean(key, value); |
208 else if (key == "scaling") | 450 } |
209 { | 451 else |
210 scaling = ParseDouble(key, value); | 452 { |
211 } | 453 LOG(WARNING) << "Unsupported option for data augmentation: " << key; |
212 else if (key == "offset-x") | 454 } |
213 { | 455 } |
214 offsetX = ParseDouble(key, value); | 456 } |
215 } | 457 |
216 else if (key == "offset-y") | |
217 { | |
218 offsetY = ParseDouble(key, value); | |
219 } | |
220 else if (key == "flip-x") | |
221 { | |
222 flipX = ParseBoolean(key, value); | |
223 } | |
224 else if (key == "flip-y") | |
225 { | |
226 flipY = ParseBoolean(key, value); | |
227 } | |
228 else if (key == "compress") | |
229 { | |
230 compress = ParseBoolean(key, value); | |
231 } | |
232 else | |
233 { | |
234 LOG(WARNING) << "Unsupported option: " << key; | |
235 } | |
236 } | |
237 | |
238 OrthancPlugins::MemoryBuffer tags; | 458 OrthancPlugins::MemoryBuffer tags; |
239 if (!tags.RestApiGet("/instances/" + std::string(request->groups[0]) + "/tags", false)) | 459 if (!tags.RestApiGet("/instances/" + std::string(request->groups[0]) + "/tags", false)) |
240 { | 460 { |
241 throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem); | 461 throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem); |
242 } | 462 } |
259 | 479 |
260 Orthanc::ImageAccessor source; | 480 Orthanc::ImageAccessor source; |
261 source.AssignReadOnly(Convert(image.GetPixelFormat()), image.GetWidth(), image.GetHeight(), | 481 source.AssignReadOnly(Convert(image.GetPixelFormat()), image.GetWidth(), image.GetHeight(), |
262 image.GetPitch(), image.GetBuffer()); | 482 image.GetPitch(), image.GetBuffer()); |
263 | 483 |
264 OrthancStone::AffineTransform2D t; | |
265 t = OrthancStone::AffineTransform2D::Combine( | |
266 OrthancStone::AffineTransform2D::CreateOffset(static_cast<double>(image.GetWidth()) / 2.0 + offsetX, | |
267 static_cast<double>(image.GetHeight()) / 2.0 + offsetY), | |
268 OrthancStone::AffineTransform2D::CreateScaling(scaling, scaling), | |
269 OrthancStone::AffineTransform2D::CreateRotation(angleRadians), | |
270 OrthancStone::AffineTransform2D::CreateOffset(-static_cast<double>(image.GetWidth()) / 2.0, | |
271 -static_cast<double>(image.GetHeight()) / 2.0), | |
272 OrthancStone::AffineTransform2D::CreateFlip(flipX, flipY, image.GetWidth(), image.GetHeight())); | |
273 | |
274 std::unique_ptr<Orthanc::ImageAccessor> modified; | 484 std::unique_ptr<Orthanc::ImageAccessor> modified; |
275 | 485 |
276 if (source.GetFormat() == Orthanc::PixelFormat_RGB24) | 486 if (parameters.GetSopClassUid() == OrthancStone::SopClassUid_DicomSeg) |
277 { | 487 { |
278 LOG(WARNING) << "Bilinear interpolation for color images is not implemented yet"; | 488 modified.reset(dataAugmentation.ApplyBinaryMask(source)); |
279 | 489 } |
280 modified.reset(new Orthanc::Image(source.GetFormat(), source.GetWidth(), source.GetHeight(), false)); | 490 else if (source.GetFormat() == Orthanc::PixelFormat_RGB24) |
281 t.Apply(*modified, source, OrthancStone::ImageInterpolation_Nearest, true); | 491 { |
492 modified.reset(dataAugmentation.Apply(source)); | |
282 } | 493 } |
283 else | 494 else |
284 { | 495 { |
285 std::unique_ptr<Orthanc::ImageAccessor> converted(parameters.ConvertToFloat(source)); | 496 std::unique_ptr<Orthanc::ImageAccessor> converted(parameters.ConvertToFloat(source)); |
286 | |
287 assert(converted.get() != NULL); | 497 assert(converted.get() != NULL); |
288 modified.reset(new Orthanc::Image(converted->GetFormat(), converted->GetWidth(), converted->GetHeight(), false)); | 498 |
289 t.Apply(*modified, *converted, OrthancStone::ImageInterpolation_Bilinear, true); | 499 modified.reset(dataAugmentation.Apply(*converted)); |
290 } | 500 } |
291 | 501 |
292 assert(modified.get() != NULL); | 502 assert(modified.get() != NULL); |
293 | 503 |
294 std::string answer; | 504 std::string answer; |