1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4"""Image transformations for our pipelines
5
6Differences between methods here and those from
7:py:mod:`torchvision.transforms` is that these support multiple simultaneous
8image inputs, which are required to feed segmentation networks (e.g. image and
9labels or masks). We also take care of data augmentations, in which random
10flipping and rotation needs to be applied across all input images, but color
11jittering, for example, only on the input image.
12"""
13
14import random
15
16import numpy
17import PIL.Image
18from scipy.ndimage import gaussian_filter, map_coordinates
19
20class SingleAutoLevel16to8:
21 """Converts a 16-bit image to 8-bit representation using "auto-level"
22
23 This transform assumes that the input image is gray-scaled.
24
25 To auto-level, we calculate the maximum and the minimum of the image, and
26 consider such a range should be mapped to the [0,255] range of the
27 destination image.
28
29 """
30
31 def __call__(self, img):
32 imin, imax = img.getextrema()
33 irange = imax - imin
34 return PIL.Image.fromarray(
35 numpy.round(
36 255.0 * (numpy.array(img).astype(float) - imin) / irange
37 ).astype("uint8"),
38 ).convert("L")
39
40
41class RemoveBlackBorders:
42 """Remove black borders of CXR"""
43 def __init__(self, threshold=0):
44 self.threshold = threshold
45
46 def __call__(self, img):
47 img = numpy.asarray(img)
48 mask = numpy.asarray(img) > self.threshold
49 return PIL.Image.fromarray(
50 img[numpy.ix_(mask.any(1), mask.any(0))]
51 )
52
53class ElasticDeformation:
54 """Elastic deformation of 2D image slightly adapted from [SIMARD-2003]_.
55 .. [SIMARD-2003] Simard, Steinkraus and Platt, "Best Practices for
56 Convolutional Neural Networks applied to Visual Document Analysis", in
57 Proc. of the International Conference on Document Analysis and
58 Recognition, 2003.
59 Source: https://gist.github.com/oeway/2e3b989e0343f0884388ed7ed82eb3b0
60 """
61 def __init__(self, alpha=1000, sigma=30, spline_order=1, mode='nearest', random_state=numpy.random, p=1):
62 self.alpha = alpha
63 self.sigma = sigma
64 self.spline_order = spline_order
65 self.mode = mode
66 self.random_state = random_state
67 self.p = p
68
69 def __call__(self, img):
70
71 if random.random() < self.p:
72
73 img = numpy.asarray(img)
74
75 assert img.ndim == 2
76
77 shape = img.shape
78
79 dx = gaussian_filter((self.random_state.rand(*shape) * 2 - 1),
80 self.sigma, mode="constant", cval=0) * self.alpha
81 dy = gaussian_filter((self.random_state.rand(*shape) * 2 - 1),
82 self.sigma, mode="constant", cval=0) * self.alpha
83
84 x, y = numpy.meshgrid(numpy.arange(shape[0]), numpy.arange(shape[1]), indexing='ij')
85 indices = [numpy.reshape(x + dx, (-1, 1)), numpy.reshape(y + dy, (-1, 1))]
86 result = numpy.empty_like(img)
87 result[:, :] = map_coordinates(
88 img[:, :], indices, order=self.spline_order, mode=self.mode).reshape(shape)
89 return PIL.Image.fromarray(result)
90 else:
91 return img