Coverage for tests/objects/test_image_feature.py: 100%
275 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 math
3import numpy as np
4from qubalab.objects.image_feature import ImageFeature
5from qubalab.objects.classification import Classification
6from qubalab.objects.object_type import ObjectType
9def test_geometry():
10 expected_geometry = geojson.Point((-115.81, 37.24))
11 image_feature = ImageFeature(expected_geometry)
13 geometry = image_feature.geometry
15 assert geometry == expected_geometry
18def test_json_serializable_with_geometry():
19 geometry = geojson.Point((-115.81, 37.24))
21 image_feature = ImageFeature(geometry)
23 geojson.dumps(image_feature) # will throw an exception if not serializable
26def test_id():
27 expected_id = 23
28 image_feature = ImageFeature(None, id=expected_id)
30 id = image_feature.id
32 assert id == str(expected_id)
35def test_json_serializable_with_id():
36 id = 23
38 image_feature = ImageFeature(None, id=id)
40 geojson.dumps(image_feature) # will throw an exception if not serializable
43def test_classification():
44 expected_classification = Classification("name", (1, 1, 1))
45 image_feature = ImageFeature(None, classification=expected_classification)
47 classification = image_feature.classification
49 assert classification == expected_classification
52def test_json_serializable_with_classification():
53 classification = Classification("name", (1, 1, 1))
55 image_feature = ImageFeature(None, classification=classification)
57 geojson.dumps(image_feature) # will throw an exception if not serializable
60def test_name():
61 expected_name = "name"
62 image_feature = ImageFeature(None, name=expected_name)
64 name = image_feature.name
66 assert name == expected_name
69def test_json_serializable_with_name():
70 name = "name"
72 image_feature = ImageFeature(None, name=name)
74 geojson.dumps(image_feature) # will throw an exception if not serializable
77def test_measurements():
78 inital_measurements = {"some_value": 0.324, "nan_value": float("nan")}
79 expected_measurements = {
80 k: v for k, v in inital_measurements.items() if not math.isnan(v)
81 } # NaN values are skipped
82 image_feature = ImageFeature(None, measurements=inital_measurements)
84 measurements = image_feature.measurements
86 assert measurements == expected_measurements
89def test_json_serializable_with_measurements():
90 measurements = {"some_value": 0.324, "nan_value": float("nan")}
92 image_feature = ImageFeature(None, measurements=measurements)
94 geojson.dumps(image_feature) # will throw an exception if not serializable
97def test_object_type():
98 expected_object_type = ObjectType.DETECTION
99 image_feature = ImageFeature(None, object_type=expected_object_type)
101 object_type = image_feature.object_type
103 assert object_type == expected_object_type
106def test_json_serializable_with_object_type():
107 object_type = ObjectType.DETECTION
109 image_feature = ImageFeature(None, object_type=object_type)
111 geojson.dumps(image_feature) # will throw an exception if not serializable
114def test_is_detection():
115 expected_object_type = ObjectType.DETECTION
116 image_feature = ImageFeature(None, object_type=expected_object_type)
118 is_detection = image_feature.is_detection
120 assert is_detection
123def test_is_not_detection():
124 expected_object_type = ObjectType.ANNOTATION
125 image_feature = ImageFeature(None, object_type=expected_object_type)
127 is_detection = image_feature.is_detection
129 assert not (is_detection)
132def test_is_cell():
133 expected_object_type = ObjectType.CELL
134 image_feature = ImageFeature(None, object_type=expected_object_type)
136 is_cell = image_feature.is_cell
138 assert is_cell
141def test_is_not_cell():
142 expected_object_type = ObjectType.DETECTION
143 image_feature = ImageFeature(None, object_type=expected_object_type)
145 is_cell = image_feature.is_cell
147 assert not (is_cell)
150def test_is_tile():
151 expected_object_type = ObjectType.TILE
152 image_feature = ImageFeature(None, object_type=expected_object_type)
154 is_tile = image_feature.is_tile
156 assert is_tile
159def test_is_not_tile():
160 expected_object_type = ObjectType.DETECTION
161 image_feature = ImageFeature(None, object_type=expected_object_type)
163 is_tile = image_feature.is_tile
165 assert not (is_tile)
168def test_is_annotation():
169 expected_object_type = ObjectType.ANNOTATION
170 image_feature = ImageFeature(None, object_type=expected_object_type)
172 is_annotation = image_feature.is_annotation
174 assert is_annotation
177def test_is_not_annotation():
178 expected_object_type = ObjectType.DETECTION
179 image_feature = ImageFeature(None, object_type=expected_object_type)
181 is_annotation = image_feature.is_annotation
183 assert not (is_annotation)
186def test_color():
187 expected_color = (4, 5, 6)
188 image_feature = ImageFeature(None, color=expected_color)
190 color = image_feature.color
192 assert color == expected_color
195def test_json_serializable_with_color():
196 color = (4, 5, 6)
198 image_feature = ImageFeature(None, color=color)
200 geojson.dumps(image_feature) # will throw an exception if not serializable
203def test_nucleus_geometry():
204 expected_nucleus_geometry = geojson.Point((-115.81, 37.24))
205 image_feature = ImageFeature(
206 None, extra_geometries={"nucleus": expected_nucleus_geometry}
207 )
209 nucleus_geometry = image_feature.nucleus_geometry
211 assert nucleus_geometry == expected_nucleus_geometry
214def test_json_serializable_with_nucleus_geometry():
215 nucleus_geometry = geojson.Point((-115.81, 37.24))
217 image_feature = ImageFeature(None, extra_geometries={"nucleus": nucleus_geometry})
219 geojson.dumps(image_feature) # will throw an exception if not serializable
222def test_geometry_when_created_from_feature():
223 expected_geometry = geojson.Point((-115.81, 37.24))
224 feature = geojson.Feature(geometry=expected_geometry)
225 image_feature = ImageFeature.create_from_feature(feature)
227 geometry = image_feature.geometry
229 assert geometry == expected_geometry
232def test_id_when_created_from_feature():
233 expected_id = 23
234 feature = geojson.Feature(id=expected_id)
235 image_feature = ImageFeature.create_from_feature(feature)
237 id = image_feature.id
239 assert id == str(expected_id)
242def test_classification_when_created_from_feature():
243 expected_classification = Classification("name", (1, 1, 1))
244 feature = geojson.Feature(properties={"classification": expected_classification})
245 image_feature = ImageFeature.create_from_feature(feature)
247 classification = image_feature.classification
249 assert classification == expected_classification
252def test_name_when_created_from_feature():
253 expected_name = "name"
254 feature = geojson.Feature(properties={"name": expected_name})
255 image_feature = ImageFeature.create_from_feature(feature)
257 name = image_feature.name
259 assert name == expected_name
262def test_measurements_when_created_from_feature():
263 inital_measurements = {"some_value": 0.324, "nan_value": float("nan")}
264 expected_measurements = {
265 k: v for k, v in inital_measurements.items() if not math.isnan(v)
266 } # NaN values are skipped
267 feature = geojson.Feature(properties={"measurements": inital_measurements})
268 image_feature = ImageFeature.create_from_feature(feature)
270 measurements = image_feature.measurements
272 assert measurements == expected_measurements
275def test_object_type_when_created_from_feature():
276 expected_object_type = ObjectType.CELL
277 feature = geojson.Feature(properties={"object_type": expected_object_type.name})
278 image_feature = ImageFeature.create_from_feature(feature)
280 object_type = image_feature.object_type
282 assert object_type == expected_object_type
285def test_color_when_created_from_feature():
286 expected_color = (4, 5, 6)
287 feature = geojson.Feature(properties={"color": expected_color})
288 image_feature = ImageFeature.create_from_feature(feature)
290 color = image_feature.color
292 assert color == expected_color
295def test_nucleus_geometry_when_created_from_feature():
296 expected_nucleus_geometry = geojson.Point((-115.81, 37.24))
297 feature = geojson.Feature(properties={"nucleusGeometry": expected_nucleus_geometry})
298 image_feature = ImageFeature.create_from_feature(feature)
300 nucleus_geometry = image_feature.nucleus_geometry
302 assert nucleus_geometry == expected_nucleus_geometry
305def test_number_of_features_when_created_from_label_image_without_scale():
306 label_image = np.array(
307 [
308 [0, 1, 1, 0, 0],
309 [0, 1, 1, 0, 0],
310 [0, 0, 0, 0, 0],
311 [0, 0, 0, 1, 0],
312 [0, 0, 0, 1, 0],
313 [0, 0, 0, 0, 0],
314 [0, 0, 2, 2, 0],
315 [0, 0, 0, 0, 0],
316 [0, 0, 0, 0, 0],
317 [0, 3, 0, 0, 0],
318 ],
319 dtype=np.uint8,
320 )
321 expected_number_of_features = 3
323 features = ImageFeature.create_from_label_image(label_image)
325 assert len(features) == expected_number_of_features
328def test_number_of_features_when_created_from_label_image_with_scale():
329 scale = 2
330 label_image = np.array(
331 [
332 [0, 1, 1, 0, 0],
333 [0, 1, 1, 0, 0],
334 [0, 0, 0, 0, 0],
335 [0, 0, 0, 1, 0],
336 [0, 0, 0, 1, 0],
337 [0, 0, 0, 0, 0],
338 [0, 0, 2, 2, 0],
339 [0, 0, 0, 0, 0],
340 [0, 0, 0, 0, 0],
341 [0, 3, 0, 0, 0],
342 ],
343 dtype=np.uint8,
344 )
345 expected_number_of_features = 3
347 features = ImageFeature.create_from_label_image(label_image, scale=scale)
349 assert len(features) == expected_number_of_features
352def test_object_type_when_created_from_label_image():
353 expected_object_type = ObjectType.CELL
354 label_image = np.array(
355 [
356 [0, 1, 1, 0, 0],
357 [0, 1, 1, 0, 0],
358 [0, 0, 0, 0, 0],
359 [0, 0, 0, 1, 0],
360 [0, 0, 0, 1, 0],
361 [0, 0, 0, 0, 0],
362 [0, 0, 2, 2, 0],
363 [0, 0, 0, 0, 0],
364 [0, 0, 0, 0, 0],
365 [0, 3, 0, 0, 0],
366 ],
367 dtype=np.uint8,
368 )
370 features = ImageFeature.create_from_label_image(
371 label_image, object_type=expected_object_type
372 )
374 assert all(feature.object_type == expected_object_type for feature in features)
377def test_measurement_when_created_from_label_image():
378 expected_measurements = [{"Label": float(label)} for label in [1, 2, 3]]
379 label_image = np.array(
380 [
381 [0, 1, 1, 0, 0],
382 [0, 1, 1, 0, 0],
383 [0, 0, 0, 0, 0],
384 [0, 0, 0, 1, 0],
385 [0, 0, 0, 1, 0],
386 [0, 0, 0, 0, 0],
387 [0, 0, 2, 2, 0],
388 [0, 0, 0, 0, 0],
389 [0, 0, 0, 0, 0],
390 [0, 3, 0, 0, 0],
391 ],
392 dtype=np.uint8,
393 )
395 features = ImageFeature.create_from_label_image(label_image, include_labels=True)
397 assert all(feature.measurements in expected_measurements for feature in features)
400def test_classification_when_created_from_label_image_and_classification_name_provided():
401 expected_classification_name = "name"
402 label_image = np.array(
403 [
404 [0, 1, 1, 0, 0],
405 [0, 1, 1, 0, 0],
406 [0, 0, 0, 0, 0],
407 [0, 0, 0, 1, 0],
408 [0, 0, 0, 1, 0],
409 [0, 0, 0, 0, 0],
410 [0, 0, 2, 2, 0],
411 [0, 0, 0, 0, 0],
412 [0, 0, 0, 0, 0],
413 [0, 3, 0, 0, 0],
414 ],
415 dtype=np.uint8,
416 )
418 features = ImageFeature.create_from_label_image(
419 label_image, classification_names=expected_classification_name
420 )
422 assert all(
423 feature.classification.name == expected_classification_name
424 for feature in features
425 )
428def test_classification_when_created_from_label_image_and_classification_dict_provided():
429 classification_dict = {
430 # no classification for label 1
431 2: "name2",
432 3: "name3",
433 }
434 expected_classification_names = classification_dict.values()
435 label_image = np.array(
436 [
437 [0, 1, 1, 0, 0],
438 [0, 1, 1, 0, 0],
439 [0, 0, 0, 0, 0],
440 [0, 0, 0, 1, 0],
441 [0, 0, 0, 1, 0],
442 [0, 0, 0, 0, 0],
443 [0, 0, 2, 2, 0],
444 [0, 0, 0, 0, 0],
445 [0, 0, 0, 0, 0],
446 [0, 3, 0, 0, 0],
447 ],
448 dtype=np.uint8,
449 )
451 features = ImageFeature.create_from_label_image(
452 label_image, classification_names=classification_dict
453 )
455 assert all(
456 feature.classification is None
457 or feature.classification.name in expected_classification_names
458 for feature in features
459 )
462def test_number_of_features_when_created_from_binary_image_without_scale():
463 binary_image = np.array(
464 [
465 [False, True, True, False, False],
466 [False, True, True, False, False],
467 [False, False, False, False, False],
468 [False, False, False, True, False],
469 [False, False, False, True, False],
470 [False, False, False, False, False],
471 [False, False, True, True, False],
472 [False, False, False, False, False],
473 [False, False, False, False, False],
474 [False, True, False, False, False],
475 ],
476 dtype=bool,
477 )
478 expected_number_of_features = 1
480 features = ImageFeature.create_from_label_image(binary_image)
482 assert len(features) == expected_number_of_features
485def test_number_of_features_when_created_from_binary_image_with_scale():
486 scale = 2
487 binary_image = np.array(
488 [
489 [False, True, True, False, False],
490 [False, True, True, False, False],
491 [False, False, False, False, False],
492 [False, False, False, True, False],
493 [False, False, False, True, False],
494 [False, False, False, False, False],
495 [False, False, True, True, False],
496 [False, False, False, False, False],
497 [False, False, False, False, False],
498 [False, True, False, False, False],
499 ],
500 dtype=bool,
501 )
502 expected_number_of_features = 1
504 features = ImageFeature.create_from_label_image(binary_image, scale=scale)
506 assert len(features) == expected_number_of_features
509def test_object_type_when_created_from_binary_image():
510 expected_object_type = ObjectType.TILE
511 binary_image = np.array(
512 [
513 [False, True, True, False, False],
514 [False, True, True, False, False],
515 [False, False, False, False, False],
516 [False, False, False, True, False],
517 [False, False, False, True, False],
518 [False, False, False, False, False],
519 [False, False, True, True, False],
520 [False, False, False, False, False],
521 [False, False, False, False, False],
522 [False, True, False, False, False],
523 ],
524 dtype=bool,
525 )
527 features = ImageFeature.create_from_label_image(
528 binary_image, object_type=expected_object_type
529 )
531 assert all(feature.object_type == expected_object_type for feature in features)
534def test_measurement_when_created_from_binary_image():
535 expected_measurement = {"Label": 1.0}
536 binary_image = np.array(
537 [
538 [False, True, True, False, False],
539 [False, True, True, False, False],
540 [False, False, False, False, False],
541 [False, False, False, True, False],
542 [False, False, False, True, False],
543 [False, False, False, False, False],
544 [False, False, True, True, False],
545 [False, False, False, False, False],
546 [False, False, False, False, False],
547 [False, True, False, False, False],
548 ],
549 dtype=bool,
550 )
552 features = ImageFeature.create_from_label_image(binary_image, include_labels=True)
554 assert all(feature.measurements == expected_measurement for feature in features)
557def test_classification_when_created_from_binary_image_and_classification_name_provided():
558 expected_classification_name = "name"
559 binary_image = np.array(
560 [
561 [False, True, True, False, False],
562 [False, True, True, False, False],
563 [False, False, False, False, False],
564 [False, False, False, True, False],
565 [False, False, False, True, False],
566 [False, False, False, False, False],
567 [False, False, True, True, False],
568 [False, False, False, False, False],
569 [False, False, False, False, False],
570 [False, True, False, False, False],
571 ],
572 dtype=bool,
573 )
575 features = ImageFeature.create_from_label_image(
576 binary_image, classification_names=expected_classification_name
577 )
579 assert all(
580 feature.classification.name == expected_classification_name
581 for feature in features
582 )
585def test_classification_when_created_from_binary_image_and_classification_dict_provided():
586 classification_dict = {1: "name1"}
587 expected_classification_names = classification_dict.values()
588 binary_image = np.array(
589 [
590 [False, True, True, False, False],
591 [False, True, True, False, False],
592 [False, False, False, False, False],
593 [False, False, False, True, False],
594 [False, False, False, True, False],
595 [False, False, False, False, False],
596 [False, False, True, True, False],
597 [False, False, False, False, False],
598 [False, False, False, False, False],
599 [False, True, False, False, False],
600 ],
601 dtype=bool,
602 )
604 features = ImageFeature.create_from_label_image(
605 binary_image, classification_names=classification_dict
606 )
608 assert all(
609 feature.classification.name in expected_classification_names
610 for feature in features
611 )
614def test_classification_when_set_after_creation():
615 expected_classification = Classification("name", (1, 1, 1))
616 image_feature = ImageFeature(None)
617 image_feature.classification = {
618 "name": expected_classification.name,
619 "color": expected_classification.color,
620 }
622 classification = image_feature.classification
624 assert classification == expected_classification
627def test_name_when_set_after_creation():
628 expected_name = "name"
629 image_feature = ImageFeature(None)
630 image_feature.name = expected_name
632 name = image_feature.name
634 assert name == expected_name
637def test_measurements_when_set_after_creation():
638 inital_measurements = {"some_value": 0.324, "nan_value": float("nan")}
639 expected_measurements = {
640 k: v for k, v in inital_measurements.items() if not math.isnan(v)
641 } # NaN values are skipped
642 image_feature = ImageFeature(None)
643 image_feature.measurements = inital_measurements
645 measurements = image_feature.measurements
647 assert measurements == expected_measurements
650def test_object_type_when_set_after_creation():
651 expected_object_type = ObjectType.CELL
652 image_feature = ImageFeature(None)
653 image_feature.object_type = expected_object_type
655 object_type = image_feature.object_type
657 assert object_type == expected_object_type
660def test_object_type_name_when_set_after_creation():
661 expected_object_type = ObjectType.TILE
662 image_feature = ImageFeature(None)
663 image_feature.object_type = expected_object_type.name
665 object_type = image_feature.object_type
667 assert object_type == expected_object_type
670def test_color_when_set_after_creation():
671 expected_color = (4, 5, 6)
672 image_feature = ImageFeature(None)
673 image_feature.color = expected_color
675 color = image_feature.color
677 assert color == expected_color
680def test_nucleus_geometry_when_set_after_creation():
681 expected_nucleus_geometry = geojson.Point((-115.81, 37.24))
682 image_feature = ImageFeature(None)
683 image_feature.nucleus_geometry = expected_nucleus_geometry
685 nucleus_geometry = image_feature.nucleus_geometry
687 assert nucleus_geometry == expected_nucleus_geometry