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;