Coverage for tests/images/test_labeled_server.py: 100%
233 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-10-07 15:29 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-10-07 15:29 +0000
1import geojson
2import numpy as np
3import random
4from qubalab.objects.image_feature import ImageFeature
5from qubalab.objects.classification import Classification
6from qubalab.images.labeled_server import LabeledImageServer
7from qubalab.images.region_2d import Region2D
8from qubalab.images.metadata.image_metadata import ImageMetadata
9from qubalab.images.metadata.image_shape import ImageShape
10from qubalab.images.metadata.pixel_calibration import PixelCalibration, PixelLength
13sample_metadata = ImageMetadata(
14 "/path/to/img.tiff",
15 "Image name",
16 (ImageShape(10, 6),),
17 PixelCalibration(PixelLength.create_microns(2.5), PixelLength.create_microns(2.5)),
18 True,
19 np.uint8,
20)
21large_metadata = ImageMetadata(
22 "/path/to/img.tiff",
23 "Image name",
24 (ImageShape(500, 250),),
25 PixelCalibration(PixelLength.create_microns(2.5), PixelLength.create_microns(2.5)),
26 True,
27 np.uint8,
28)
31def test_image_width_with_downsample():
32 downsample = 1.5
33 expected_width = sample_metadata.shape.x
34 labeled_server = LabeledImageServer(sample_metadata, [], downsample=downsample)
36 width = labeled_server.metadata.width
38 assert width == expected_width
40 labeled_server.close()
43def test_image_height_with_downsample():
44 downsample = 1.5
45 expected_height = sample_metadata.shape.y
46 labeled_server = LabeledImageServer(sample_metadata, [], downsample=downsample)
48 height = labeled_server.metadata.height
50 assert height == expected_height
52 labeled_server.close()
55def test_image_width_with_no_downsample():
56 expected_width = sample_metadata.shape.x
57 labeled_server = LabeledImageServer(sample_metadata, [])
59 width = labeled_server.metadata.width
61 assert width == expected_width
63 labeled_server.close()
66def test_image_height_with_no_downsample():
67 expected_height = sample_metadata.shape.y
68 labeled_server = LabeledImageServer(sample_metadata, [])
70 height = labeled_server.metadata.height
72 assert height == expected_height
74 labeled_server.close()
77def test_number_of_channels_when_not_multichannel():
78 some_classification = Classification("Some classification")
79 some_other_classification = Classification("Some other classification")
80 features = [
81 ImageFeature(geojson.Point((2, 5)), some_classification),
82 ImageFeature(geojson.Point((5, 7)), some_classification),
83 ImageFeature(geojson.Point((17, 7)), some_other_classification),
84 ]
85 expected_number_of_channels = 1
86 labeled_server = LabeledImageServer(sample_metadata, features, multichannel=False)
88 n_channels = labeled_server.metadata.n_channels
90 assert n_channels == expected_number_of_channels
92 labeled_server.close()
95def test_number_of_channels_when_multi_channel_and_no_label_map_given():
96 some_classification = Classification("Some classification")
97 some_other_classification = Classification("Some other classification")
98 features = [
99 ImageFeature(geojson.Point((2, 5)), some_classification),
100 ImageFeature(geojson.Point((5, 7)), some_classification),
101 ImageFeature(geojson.Point((17, 7)), some_other_classification),
102 ]
103 expected_number_of_channels = 4
104 labeled_server = LabeledImageServer(sample_metadata, features, multichannel=True)
106 n_channels = labeled_server.metadata.n_channels
108 assert n_channels == expected_number_of_channels
110 labeled_server.close()
113def test_number_of_channels_when_multi_channel_and_label_map_given():
114 some_classification = Classification("Some classification")
115 some_other_classification = Classification("Some other classification")
116 features = [
117 ImageFeature(geojson.Point((2, 5)), some_classification),
118 ImageFeature(geojson.Point((5, 7)), some_classification),
119 ImageFeature(geojson.Point((17, 7)), some_other_classification),
120 ]
121 label_map = {
122 some_classification: 1,
123 some_other_classification: 2,
124 }
125 expected_number_of_channels = 3
126 labeled_server = LabeledImageServer(
127 sample_metadata, features, label_map=label_map, multichannel=True
128 )
130 n_channels = labeled_server.metadata.n_channels
132 assert n_channels == expected_number_of_channels
134 labeled_server.close()
137def test_image_n_timepoints():
138 expected_n_timepoints = 1
139 labeled_server = LabeledImageServer(sample_metadata, [])
141 n_timepoints = labeled_server.metadata.n_timepoints
143 assert n_timepoints == expected_n_timepoints
145 labeled_server.close()
148def test_image_n_z_slices():
149 expected_n_z_slices = 1
150 labeled_server = LabeledImageServer(sample_metadata, [])
152 n_z_slices = labeled_server.metadata.n_z_slices
154 assert n_z_slices == expected_n_z_slices
156 labeled_server.close()
159def test_image_n_resolutions():
160 expected_n_resolutions = 1
161 labeled_server = LabeledImageServer(sample_metadata, [])
163 n_resolutions = labeled_server.metadata.n_resolutions
165 assert n_resolutions == expected_n_resolutions
167 labeled_server.close()
170def test_x_pixel_length_with_downsample():
171 downsample = 1.5
172 expected_length_x = sample_metadata.pixel_calibration.length_x.length
173 labeled_server = LabeledImageServer(sample_metadata, [], downsample=downsample)
175 length_x = labeled_server.metadata.pixel_calibration.length_x.length
177 assert length_x == expected_length_x
179 labeled_server.close()
182def test_x_pixel_length_with_no_downsample():
183 expected_length_x = sample_metadata.pixel_calibration.length_x.length
184 labeled_server = LabeledImageServer(sample_metadata, [])
186 length_x = labeled_server.metadata.pixel_calibration.length_x.length
188 assert length_x == expected_length_x
190 labeled_server.close()
193def test_dtype_when_not_multi_channel():
194 expected_dtype = np.uint32
195 labeled_server = LabeledImageServer(sample_metadata, [], multichannel=False)
197 dtype = labeled_server.metadata.dtype
199 assert dtype == expected_dtype
201 labeled_server.close()
204def test_dtype_when_multi_channel():
205 expected_dtype = np.bool
206 labeled_server = LabeledImageServer(sample_metadata, [], multichannel=True)
208 dtype = labeled_server.metadata.dtype
210 assert dtype == expected_dtype
212 labeled_server.close()
215def test_read_points_in_single_channel_image_without_label_map_without_downsample():
216 some_classification = Classification("Some classification")
217 some_other_classification = Classification("Some other classification")
218 features = [
219 ImageFeature(geojson.Point((5, 2)), some_classification),
220 ImageFeature(geojson.Point((7, 4)), some_classification),
221 ImageFeature(geojson.Point((1, 0)), some_other_classification),
222 ]
223 expected_image = np.array(
224 [
225 [
226 [0, 3, 0, 0, 0, 0, 0, 0, 0, 0],
227 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
228 [0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
229 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
230 [0, 0, 0, 0, 0, 0, 0, 2, 0, 0],
231 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
232 ]
233 ]
234 )
235 labeled_server = LabeledImageServer(sample_metadata, features, multichannel=False)
237 image = labeled_server.read_region(
238 1, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height)
239 )
241 np.testing.assert_array_equal(image, expected_image)
244def test_read_points_in_multi_channel_image_without_label_map_without_downsample():
245 some_classification = Classification("Some classification")
246 some_other_classification = Classification("Some other classification")
247 features = [
248 ImageFeature(geojson.Point((5, 2)), some_classification),
249 ImageFeature(geojson.Point((7, 4)), some_classification),
250 ImageFeature(geojson.Point((1, 0)), some_other_classification),
251 ]
252 expected_image = np.array(
253 [
254 [
255 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
256 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
257 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
258 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
259 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
260 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
261 ],
262 [
263 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
264 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
265 [0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
266 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
267 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
268 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
269 ],
270 [
271 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
272 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
273 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
274 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
275 [0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
276 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
277 ],
278 [
279 [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
280 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
281 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
282 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
283 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
284 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
285 ],
286 ]
287 )
288 labeled_server = LabeledImageServer(sample_metadata, features, multichannel=True)
290 image = labeled_server.read_region(
291 1, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height)
292 )
294 np.testing.assert_array_equal(image, expected_image)
297def test_read_points_in_single_channel_image_with_label_map_without_downsample():
298 some_classification = Classification("Some classification")
299 some_other_classification = Classification("Some other classification")
300 features = [
301 ImageFeature(geojson.Point((5, 2)), some_classification),
302 ImageFeature(geojson.Point((7, 4)), some_classification),
303 ImageFeature(geojson.Point((1, 0)), some_other_classification),
304 ]
305 label_map = {
306 some_classification: 1,
307 some_other_classification: 2,
308 }
309 expected_image = np.array(
310 [
311 [
312 [0, 2, 0, 0, 0, 0, 0, 0, 0, 0],
313 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
314 [0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
315 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
316 [0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
317 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
318 ]
319 ]
320 )
321 labeled_server = LabeledImageServer(
322 sample_metadata, features, label_map=label_map, multichannel=False
323 )
325 image = labeled_server.read_region(
326 1, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height)
327 )
329 np.testing.assert_array_equal(image, expected_image)
332def test_read_points_in_single_channel_image_without_label_map_with_downsample():
333 downsample = 2
334 some_classification = Classification("Some classification")
335 some_other_classification = Classification("Some other classification")
336 features = [
337 ImageFeature(geojson.Point((6, 2)), some_classification),
338 ImageFeature(geojson.Point((8, 4)), some_classification),
339 ImageFeature(geojson.Point((2, 0)), some_other_classification),
340 ]
341 expected_image = np.array([[[0, 3, 0, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 2]]])
342 labeled_server = LabeledImageServer(
343 sample_metadata, features, multichannel=False, downsample=downsample
344 )
346 image = labeled_server.read_region(
347 downsample,
348 Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height),
349 )
351 np.testing.assert_array_equal(image, expected_image)
354def test_read_line_in_single_channel_image_without_label_map_without_downsample():
355 features = [
356 ImageFeature(
357 geojson.LineString([(6, 2), (8, 2)]), Classification("Some classification")
358 )
359 ]
360 expected_image = np.array(
361 [
362 [
363 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
364 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
365 [0, 0, 0, 0, 0, 0, 1, 1, 1, 0],
366 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
367 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
368 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
369 ]
370 ]
371 )
372 labeled_server = LabeledImageServer(sample_metadata, features, multichannel=False)
374 image = labeled_server.read_region(
375 1, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height)
376 )
378 np.testing.assert_array_equal(image, expected_image)
381def test_read_line_in_single_channel_image_without_label_map_with_downsample():
382 downsample = 2
383 features = [
384 ImageFeature(
385 geojson.LineString([(6, 2), (8, 2)]), Classification("Some classification")
386 )
387 ]
388 expected_image = np.array([[[0, 0, 0, 0, 0], [0, 0, 0, 1, 1], [0, 0, 0, 0, 0]]])
389 labeled_server = LabeledImageServer(
390 sample_metadata, features, multichannel=False, downsample=downsample
391 )
393 image = labeled_server.read_region(
394 downsample,
395 Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height),
396 )
398 np.testing.assert_array_equal(image, expected_image)
401def test_read_polygon_in_single_channel_image_without_label_map_without_downsample():
402 features = [
403 ImageFeature(
404 geojson.Polygon([[(6, 2), (8, 2), (8, 4), (4, 4)]]),
405 Classification("Some classification"),
406 )
407 ]
408 expected_image = np.array(
409 [
410 [
411 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
412 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
413 [0, 0, 0, 0, 0, 0, 1, 1, 1, 0],
414 [0, 0, 0, 0, 0, 1, 1, 1, 1, 0],
415 [0, 0, 0, 0, 1, 1, 1, 1, 1, 0],
416 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
417 ]
418 ]
419 )
420 labeled_server = LabeledImageServer(sample_metadata, features, multichannel=False)
422 image = labeled_server.read_region(
423 1, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height)
424 )
426 np.testing.assert_array_equal(image, expected_image)
429def test_read_polygon_in_single_channel_image_without_label_map_with_downsample():
430 downsample = 2
431 features = [
432 ImageFeature(
433 geojson.Polygon([[(6, 2), (8, 2), (8, 4), (4, 4)]]),
434 Classification("Some classification"),
435 )
436 ]
437 expected_image = np.array([[[0, 0, 0, 0, 0], [0, 0, 0, 1, 1], [0, 0, 1, 1, 1]]])
438 labeled_server = LabeledImageServer(
439 sample_metadata, features, multichannel=False, downsample=downsample
440 )
442 image = labeled_server.read_region(
443 downsample,
444 Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height),
445 )
447 np.testing.assert_array_equal(image, expected_image)
450def test_label_can_hold_many_values():
451 downsample = 1
452 max_objects = 1000
453 random.seed(1)
455 def rands():
456 x = random.randint(0, int(large_metadata.shape.x / downsample))
457 y = random.randint(0, int(large_metadata.shape.x / downsample))
458 return ((x, y), (x + 1, y), (x + 1, y + 1), (x, y + 1))
460 features = [ImageFeature(geojson.Polygon([rands()])) for i in range(max_objects)]
461 labeled_server = LabeledImageServer(
462 large_metadata, features, multichannel=False, downsample=downsample
463 )
465 image = labeled_server.read_region(
466 1, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height)
467 )
469 assert np.max(image) == max_objects
472def test_single_channel_labeled_image_with_region_request():
473 downsample = 1
474 features = [ImageFeature(geojson.LineString([(7, 5), (9, 5)]))]
475 expected_image = np.array([[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 1, 1, 1]]])
476 labeled_server = LabeledImageServer(
477 sample_metadata, features, multichannel=False, downsample=downsample
478 )
479 region = Region2D(
480 5, 3, labeled_server.metadata.width - 5, labeled_server.metadata.height - 3
481 )
482 image = labeled_server.read_region(1, region)
484 np.testing.assert_array_equal(image, expected_image)
487def test_single_channel_labeled_image_with_starting_downsample():
488 features = [ImageFeature(geojson.LineString([(6, 5), (9, 5)]))]
489 # when resizing, we lose the labels with bicubic
490 expected_image = np.array([[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 1, 1]]])
491 labeled_server = LabeledImageServer(
492 sample_metadata, features, multichannel=False, downsample=1
493 )
494 downsample = 2
495 region = Region2D(
496 0, 0, labeled_server.metadata.width, labeled_server.metadata.height
497 )
498 image = labeled_server.read_region(downsample, region)
500 np.testing.assert_array_equal(image, expected_image)
503def test_single_channel_labeled_image_with_request_downsample():
504 # we downsample
505 features = [ImageFeature(geojson.LineString([(6, 5), (9, 5)]))]
506 expected_image = np.array([[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 1, 1]]])
507 labeled_server = LabeledImageServer(
508 sample_metadata, features, multichannel=False, downsample=1
509 )
510 region = Region2D(
511 0, 0, labeled_server.metadata.width, labeled_server.metadata.height
512 )
513 image = labeled_server.read_region(2, region)
515 np.testing.assert_array_equal(image, expected_image)
518def test_multi_channel_labeled_image_with_region_request():
519 features = [ImageFeature(geojson.LineString([(7, 5), (9, 5)]))]
520 expected_image = np.array(
521 [
522 [
523 [False, False, False, False, False],
524 [False, False, False, False, False],
525 [False, False, False, False, False],
526 ],
527 [
528 [False, False, False, False, False],
529 [False, False, False, False, False],
530 [False, False, True, True, True],
531 ],
532 ]
533 )
534 labeled_server = LabeledImageServer(
535 sample_metadata, features, multichannel=True, downsample=1
536 )
537 region = Region2D(
538 5, 3, labeled_server.metadata.width - 5, labeled_server.metadata.height - 3
539 )
540 image = labeled_server.read_region(1, region)
542 np.testing.assert_array_equal(image, expected_image)
545def test_multi_channel_labeled_image_with_starting_downsample():
546 # we downsample the feature, then request at the same downsample
547 features = [ImageFeature(geojson.LineString([(6, 5), (9, 5)]))]
548 expected_image = np.array(
549 [
550 [
551 [False, False, False, False, False],
552 [False, False, False, False, False],
553 [False, False, False, False, False],
554 ],
555 [
556 [False, False, False, False, False],
557 [False, False, False, False, False],
558 [False, False, False, True, True],
559 ],
560 ]
561 )
562 downsample = 2
563 labeled_server = LabeledImageServer(
564 sample_metadata, features, multichannel=True, downsample=downsample
565 )
566 region = Region2D(0, 0, sample_metadata.width, sample_metadata.height)
567 image = labeled_server.read_region(2, region)
569 np.testing.assert_array_equal(image, expected_image)
572def test_multi_channel_labeled_image_with_request_downsample():
573 features = [ImageFeature(geojson.LineString([(6, 5), (9, 5)]))]
574 ## because we resize the image after reading, we lose the small region
575 expected_image = np.array(
576 [
577 [
578 [False, False, False, False, False],
579 [False, False, False, False, False],
580 [False, False, False, False, False],
581 ],
582 [
583 [False, False, False, False, False],
584 [False, False, False, False, False],
585 [False, False, False, False, False],
586 ],
587 ]
588 )
589 labeled_server = LabeledImageServer(
590 sample_metadata, features, multichannel=True, downsample=1
591 )
592 downsample = 2
593 region = Region2D(
594 0, 0, labeled_server.metadata.width, labeled_server.metadata.height
595 )
596 image = labeled_server.read_region(downsample, region)
598 np.testing.assert_array_equal(image, expected_image)
601def test_multi_channel_labeled_image_with_starting_downsample_upsampled():
602 # we downsample the feature, then request at a downsample of 1, so upsampled!
603 # therefore the feature gets much bigger
604 features = [ImageFeature(geojson.LineString([(5, 5), (9, 5)]))]
605 expected_image = np.array(
606 [
607 [
608 [False, False, False, False, False],
609 [False, False, False, False, False],
610 [False, False, False, False, False],
611 ],
612 [
613 [False, False, False, False, False],
614 [False, False, False, False, False],
615 [False, False, True, True, True],
616 ],
617 ]
618 )
619 labeled_server = LabeledImageServer(
620 sample_metadata, features, multichannel=True, downsample=2
621 )
622 image = labeled_server.read_region(2)
624 np.testing.assert_array_equal(image, expected_image)