1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4import os
5import random
6
7import numpy
8import PIL.Image
9import PIL.ImageDraw
10import PIL.ImageOps
11import pkg_resources
12import torch
13import torchvision.transforms.functional
14
15from ..data.transforms import (
16 CenterCrop,
17 ColorJitter,
18 Compose,
19 Crop,
20 GaussianBlur,
21 Pad,
22 RandomHorizontalFlip,
23 RandomRotation,
24 RandomVerticalFlip,
25 Resize,
26 ShrinkIntoSquare,
27 SingleAutoLevel16to8,
28 ToTensor,
29)
30
31
32def _create_img(size):
33 t = torch.randn(size)
34 pil = torchvision.transforms.functional.to_pil_image(t)
35 return pil
36
37
38def test_center_crop():
39
40 # parameters
41 im_size = (3, 22, 20) # (planes, height, width)
42 crop_size = (10, 12) # (height, width)
43
44 # test
45 bh = (im_size[1] - crop_size[0]) // 2
46 bw = (im_size[2] - crop_size[1]) // 2
47 idx = (slice(bh, -bh), slice(bw, -bw), slice(0, im_size[0]))
48 transforms = CenterCrop(crop_size)
49 img, gt, mask = [_create_img(im_size) for i in range(3)]
50 assert img.size == (im_size[2], im_size[1]) # confirms the above
51 img_t, gt_t, mask_t = transforms(img, gt, mask)
52 assert img_t.size == (crop_size[1], crop_size[0]) # confirms the above
53 # notice that PIL->array does array.transpose(1, 2, 0)
54 # so it creates an array that is (height, width, planes)
55 assert numpy.all(numpy.array(img_t) == numpy.array(img)[idx])
56 assert numpy.all(numpy.array(gt_t) == numpy.array(gt)[idx])
57 assert numpy.all(numpy.array(mask_t) == numpy.array(mask)[idx])
58
59
60def test_center_crop_uneven():
61
62 # parameters - WARNING: notice that modern implementations of centercrop in
63 # torchvision (noticed with version 0.10.x) may provide slightly different
64 # outputs depending on the multiplicity of pixels that need to be cropped
65 # from both sides. In the current configuration, the output is such that
66 # the centercrop is 0.5 pixel to the right/bottom from an actual center
67 # crop. With other configurations, it may be that the crop is 0.5 pixel to
68 # the left/top part of the image, which makes testing this function a bit
69 # hard. If you have issues with this function, try different
70 # configurations here, or to change the setting of "idx" a few lines down.
71 im_size = (3, 21, 20) # (planes, height, width)
72 crop_size = (10, 13) # (height, width)
73
74 # test
75 bh = (im_size[1] - crop_size[0]) // 2
76 bw = (im_size[2] - crop_size[1]) // 2
77 # when the crop size is uneven, this is what happens - notice here that the
78 # image height is uneven, and the crop width as well - the attributions of
79 # extra pixels will depend on what is uneven (original image or crop)
80 # crop is 0.5 pixel to the right/bottom:
81 idx = (slice(bh + 1, -bh), slice(bw + 1, -bw), slice(0, im_size[0]))
82 # crop is 0.5 pixel to the left/top:
83 # idx = (slice(bh, -(bh + 1)), slice(bw, -(bw + 1)), slice(0, im_size[0]))
84 transforms = CenterCrop(crop_size)
85 img, gt, mask = [_create_img(im_size) for i in range(3)]
86 assert img.size == (im_size[2], im_size[1]) # confirms the above
87 img_t, gt_t, mask_t = transforms(img, gt, mask)
88 assert img_t.size == (crop_size[1], crop_size[0]) # confirms the above
89 # notice that PIL->array does array.transpose(1, 2, 0)
90 # so it creates an array that is (height, width, planes)
91 assert numpy.all(numpy.array(img_t) == numpy.array(img)[idx])
92 assert numpy.all(numpy.array(gt_t) == numpy.array(gt)[idx])
93 assert numpy.all(numpy.array(mask_t) == numpy.array(mask)[idx])
94
95
96def test_pad_default():
97
98 # parameters
99 im_size = (3, 22, 20) # (planes, height, width)
100 pad_size = 2
101
102 # test
103 idx = (
104 slice(pad_size, -pad_size),
105 slice(pad_size, -pad_size),
106 slice(0, im_size[0]),
107 )
108 transforms = Pad(pad_size)
109 img, gt, mask = [_create_img(im_size) for i in range(3)]
110 assert img.size == (im_size[2], im_size[1]) # confirms the above
111 img_t, gt_t, mask_t = transforms(img, gt, mask)
112 # notice that PIL->array does array.transpose(1, 2, 0)
113 # so it creates an array that is (height, width, planes)
114 assert numpy.all(numpy.array(img_t)[idx] == numpy.array(img))
115 assert numpy.all(numpy.array(gt_t)[idx] == numpy.array(gt))
116 assert numpy.all(numpy.array(mask_t)[idx] == numpy.array(mask))
117
118 # checks that the border introduced with padding is all about "fill"
119 img_t = numpy.array(img_t)
120 img_t[idx] = 0
121 # border_size_plane = img_t[:, :, 0].size - numpy.array(img)[:, :, 0].size
122 assert img_t.sum() == 0
123
124 gt_t = numpy.array(gt_t)
125 gt_t[idx] = 0
126 assert gt_t.sum() == 0
127
128 mask_t = numpy.array(mask_t)
129 mask_t[idx] = 0
130 assert mask_t.sum() == 0
131
132
133def test_pad_2tuple():
134
135 # parameters
136 im_size = (3, 22, 20) # (planes, height, width)
137 pad_size = (1, 2) # left/right, top/bottom
138 fill = (3, 4, 5)
139
140 # test
141 idx = (
142 slice(pad_size[1], -pad_size[1]),
143 slice(pad_size[0], -pad_size[0]),
144 slice(0, im_size[0]),
145 )
146 transforms = Pad(pad_size, fill)
147 img, gt, mask = [_create_img(im_size) for i in range(3)]
148 assert img.size == (im_size[2], im_size[1]) # confirms the above
149 img_t, gt_t, mask_t = transforms(img, gt, mask)
150 # notice that PIL->array does array.transpose(1, 2, 0)
151 # so it creates an array that is (height, width, planes)
152 assert numpy.all(numpy.array(img_t)[idx] == numpy.array(img))
153 assert numpy.all(numpy.array(gt_t)[idx] == numpy.array(gt))
154 assert numpy.all(numpy.array(mask_t)[idx] == numpy.array(mask))
155
156 # checks that the border introduced with padding is all about "fill"
157 img_t = numpy.array(img_t)
158 img_t[idx] = 0
159 border_size_plane = img_t[:, :, 0].size - numpy.array(img)[:, :, 0].size
160 expected_sum = sum((fill[k] * border_size_plane) for k in range(3))
161 assert img_t.sum() == expected_sum
162
163 gt_t = numpy.array(gt_t)
164 gt_t[idx] = 0
165 assert gt_t.sum() == expected_sum
166
167 mask_t = numpy.array(mask_t)
168 mask_t[idx] = 0
169 assert mask_t.sum() == expected_sum
170
171
172def test_pad_4tuple():
173
174 # parameters
175 im_size = (3, 22, 20) # (planes, height, width)
176 pad_size = (1, 2, 3, 4) # left, top, right, bottom
177 fill = (3, 4, 5)
178
179 # test
180 idx = (
181 slice(pad_size[1], -pad_size[3]),
182 slice(pad_size[0], -pad_size[2]),
183 slice(0, im_size[0]),
184 )
185 transforms = Pad(pad_size, fill)
186 img, gt, mask = [_create_img(im_size) for i in range(3)]
187 assert img.size == (im_size[2], im_size[1]) # confirms the above
188 img_t, gt_t, mask_t = transforms(img, gt, mask)
189 # notice that PIL->array does array.transpose(1, 2, 0)
190 # so it creates an array that is (height, width, planes)
191 assert numpy.all(numpy.array(img_t)[idx] == numpy.array(img))
192 assert numpy.all(numpy.array(gt_t)[idx] == numpy.array(gt))
193 assert numpy.all(numpy.array(mask_t)[idx] == numpy.array(mask))
194
195 # checks that the border introduced with padding is all about "fill"
196 img_t = numpy.array(img_t)
197 img_t[idx] = 0
198 border_size_plane = img_t[:, :, 0].size - numpy.array(img)[:, :, 0].size
199 expected_sum = sum((fill[k] * border_size_plane) for k in range(3))
200 assert img_t.sum() == expected_sum
201
202 gt_t = numpy.array(gt_t)
203 gt_t[idx] = 0
204 assert gt_t.sum() == expected_sum
205
206 mask_t = numpy.array(mask_t)
207 mask_t[idx] = 0
208 assert mask_t.sum() == expected_sum
209
210
211def test_resize_downscale_w():
212
213 # parameters
214 im_size = (3, 22, 20) # (planes, height, width)
215 new_size = 10 # (smallest edge)
216
217 # test
218 transforms = Resize(new_size)
219 img, gt, mask = [_create_img(im_size) for i in range(3)]
220 assert img.size == (im_size[2], im_size[1]) # confirms the above
221 img_t, gt_t, mask_t = transforms(img, gt, mask)
222 new_size = (new_size, (new_size * im_size[1]) / im_size[2])
223 assert img_t.size == new_size
224 assert gt_t.size == new_size
225 assert mask_t.size == new_size
226
227
228def test_resize_downscale_hw():
229
230 # parameters
231 im_size = (3, 22, 20) # (planes, height, width)
232 new_size = (10, 12) # (height, width)
233
234 # test
235 transforms = Resize(new_size)
236 img, gt, mask = [_create_img(im_size) for i in range(3)]
237 assert img.size == (im_size[2], im_size[1]) # confirms the above
238 img_t, gt_t, mask_t = transforms(img, gt, mask)
239 assert img_t.size == (new_size[1], new_size[0])
240 assert gt_t.size == (new_size[1], new_size[0])
241 assert mask_t.size == (new_size[1], new_size[0])
242
243
244def test_crop():
245
246 # parameters
247 im_size = (3, 22, 20) # (planes, height, width)
248 crop_size = (3, 2, 10, 12) # (upper, left, height, width)
249
250 # test
251 idx = (
252 slice(crop_size[0], crop_size[0] + crop_size[2]),
253 slice(crop_size[1], crop_size[1] + crop_size[3]),
254 slice(0, im_size[0]),
255 )
256 transforms = Crop(*crop_size)
257 img, gt, mask = [_create_img(im_size) for i in range(3)]
258 assert img.size == (im_size[2], im_size[1]) # confirms the above
259 img_t, gt_t, mask_t = transforms(img, gt, mask)
260 # notice that PIL->array does array.transpose(1, 2, 0)
261 # so it creates an array that is (height, width, planes)
262 assert numpy.all(numpy.array(img_t) == numpy.array(img)[idx])
263 assert numpy.all(numpy.array(gt_t) == numpy.array(gt)[idx])
264 assert numpy.all(numpy.array(mask_t) == numpy.array(mask)[idx])
265
266
267def test_to_tensor():
268
269 transforms = ToTensor()
270 img, gt, mask = [_create_img((3, 5, 5)) for i in range(3)]
271 gt = gt.convert("1", dither=None)
272 mask = mask.convert("1", dither=None)
273 img_t, gt_t, mask_t = transforms(img, gt, mask)
274 assert img_t.dtype == torch.float32
275 assert gt_t.dtype == torch.float32
276 assert mask_t.dtype == torch.float32
277
278
279def test_horizontal_flip():
280
281 transforms = RandomHorizontalFlip(p=1)
282
283 im_size = (3, 24, 42) # (planes, height, width)
284 img, gt, mask = [_create_img(im_size) for i in range(3)]
285 img_t, gt_t, mask_t = transforms(img, gt, mask)
286
287 # notice that PIL->array does array.transpose(1, 2, 0)
288 # so it creates an array that is (height, width, planes)
289 assert numpy.all(numpy.flip(img_t, axis=1) == numpy.array(img))
290 assert numpy.all(numpy.flip(gt_t, axis=1) == numpy.array(gt))
291 assert numpy.all(numpy.flip(mask_t, axis=1) == numpy.array(mask))
292
293
294def test_vertical_flip():
295
296 transforms = RandomVerticalFlip(p=1)
297
298 im_size = (3, 24, 42) # (planes, height, width)
299 img, gt, mask = [_create_img(im_size) for i in range(3)]
300 img_t, gt_t, mask_t = transforms(img, gt, mask)
301
302 # notice that PIL->array does array.transpose(1, 2, 0)
303 # so it creates an array that is (height, width, planes)
304 assert numpy.all(numpy.flip(img_t, axis=0) == numpy.array(img))
305 assert numpy.all(numpy.flip(gt_t, axis=0) == numpy.array(gt))
306 assert numpy.all(numpy.flip(mask_t, axis=0) == numpy.array(mask))
307
308
309def test_rotation():
310
311 im_size = (3, 24, 42) # (planes, height, width)
312 transforms = RandomRotation(degrees=90, p=1)
313 img = _create_img(im_size)
314
315 # asserts all images are rotated the same
316 # and they are different from the original
317 random.seed(42)
318 img1_t, img2_t, img3_t = transforms(img, img, img)
319 assert img1_t.size == (im_size[2], im_size[1])
320 assert numpy.all(numpy.array(img1_t) == numpy.array(img2_t))
321 assert numpy.all(numpy.array(img1_t) == numpy.array(img3_t))
322 assert numpy.any(numpy.array(img1_t) != numpy.array(img))
323
324 # asserts two random transforms are not the same
325 (img_t2,) = transforms(img)
326 assert numpy.any(numpy.array(img_t2) != numpy.array(img1_t))
327
328
329def test_color_jitter():
330
331 im_size = (3, 24, 42) # (planes, height, width)
332 transforms = ColorJitter(p=1)
333 img = _create_img(im_size)
334
335 # asserts only the first image is jittered
336 # and it is different from the original
337 # all others match the input data
338 random.seed(42)
339 img1_t, img2_t, img3_t = transforms(img, img, img)
340 assert img1_t.size == (im_size[2], im_size[1])
341 assert numpy.any(numpy.array(img1_t) != numpy.array(img))
342 assert numpy.any(numpy.array(img1_t) != numpy.array(img2_t))
343 assert numpy.all(numpy.array(img2_t) == numpy.array(img3_t))
344 assert numpy.all(numpy.array(img2_t) == numpy.array(img))
345
346 # asserts two random transforms are not the same
347 img1_t2, img2_t2, img3_t2 = transforms(img, img, img)
348 assert numpy.any(numpy.array(img1_t2) != numpy.array(img1_t))
349 assert numpy.all(numpy.array(img2_t2) == numpy.array(img))
350 assert numpy.all(numpy.array(img3_t2) == numpy.array(img))
351
352
353def test_blur():
354
355 im_size = (1, 256, 256) # (planes, height, width)
356 transforms = GaussianBlur(p=1)
357 img = _create_img(im_size)
358
359 # asserts only the first image is blurred
360 # and it is different from the original
361 # all others match the input data
362 random.seed(42)
363 img1_t, img2_t, img3_t = transforms(img, img, img)
364 assert img1_t.size == (im_size[2], im_size[1])
365 assert numpy.any(numpy.array(img1_t) != numpy.array(img))
366 assert numpy.any(numpy.array(img1_t) != numpy.array(img2_t))
367 assert numpy.all(numpy.array(img2_t) == numpy.array(img3_t))
368 assert numpy.all(numpy.array(img2_t) == numpy.array(img))
369
370
371def test_compose():
372
373 transforms = Compose(
374 [
375 RandomVerticalFlip(p=1),
376 RandomHorizontalFlip(p=1),
377 RandomVerticalFlip(p=1),
378 RandomHorizontalFlip(p=1),
379 ]
380 )
381
382 img, gt, mask = [_create_img((3, 24, 42)) for i in range(3)]
383 img_t, gt_t, mask_t = transforms(img, gt, mask)
384 assert numpy.all(numpy.array(img_t) == numpy.array(img))
385 assert numpy.all(numpy.array(gt_t) == numpy.array(gt))
386 assert numpy.all(numpy.array(mask_t) == numpy.array(mask))
387
388
389def test_16bit_autolevel():
390
391 path = pkg_resources.resource_filename(
392 __name__, os.path.join("data", "img-16bit.png")
393 )
394 # the way to load a 16-bit PNG image correctly, according to:
395 # https://stackoverflow.com/questions/32622658/read-16-bit-png-image-file-using-python
396 # https://github.com/python-pillow/Pillow/issues/3011
397 img = PIL.Image.fromarray(
398 numpy.array(PIL.Image.open(path)).astype("uint16")
399 )
400 assert img.mode == "I;16"
401 assert img.getextrema() == (0, 65281)
402
403 timg = SingleAutoLevel16to8()(img)
404 assert timg.mode == "L"
405 assert timg.getextrema() == (0, 255)
406 # timg.show()
407 # import ipdb; ipdb.set_trace()
408
409
410def test_shrink_into_square():
411
412 # parameters
413 im_size = (3, 128, 140) # (planes, height, width)
414 mask_gt_size = (1, 128, 140) # (planes, height, width)
415 crop_size = (30, 30, 91, 91) # (left,up , right ,down)
416 size_after_crop = (61, 61)
417
418 idx = (slice(crop_size[0], crop_size[2]), slice(crop_size[1], crop_size[3]))
419
420 # Create random image and a mask with a circle inside
421 img, gt = _create_img(im_size), _create_img(mask_gt_size)
422 mask = PIL.Image.new("L", (140, 128), "black")
423 dr = PIL.ImageDraw.Draw(mask)
424 dr.ellipse((30, 30, 90, 90), "white")
425
426 # Test
427 transform = ShrinkIntoSquare(reference=2, threshold=0)
428 img_, gt_, mask_ = transform(img, gt, mask)
429
430 assert img_.size == size_after_crop
431 assert gt_.size == size_after_crop
432 assert mask_.size == size_after_crop
433
434 assert img_.mode == "RGB"
435 assert gt_.mode == "L"
436 assert mask_.mode == "L"
437
438 assert numpy.all(numpy.array(img_) == numpy.array(img)[idx])
439 assert numpy.all(numpy.array(gt_) == numpy.array(gt)[idx])
440 assert numpy.all(numpy.array(mask_) == numpy.array(mask)[idx])