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

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 

7 

8 

9def test_geometry(): 

10 expected_geometry = geojson.Point((-115.81, 37.24)) 

11 image_feature = ImageFeature(expected_geometry) 

12 

13 geometry = image_feature.geometry 

14 

15 assert geometry == expected_geometry 

16 

17 

18def test_json_serializable_with_geometry(): 

19 geometry = geojson.Point((-115.81, 37.24)) 

20 

21 image_feature = ImageFeature(geometry) 

22 

23 geojson.dumps(image_feature) # will throw an exception if not serializable 

24 

25 

26def test_id(): 

27 expected_id = 23 

28 image_feature = ImageFeature(None, id=expected_id) 

29 

30 id = image_feature.id 

31 

32 assert id == str(expected_id) 

33 

34 

35def test_json_serializable_with_id(): 

36 id = 23 

37 

38 image_feature = ImageFeature(None, id=id) 

39 

40 geojson.dumps(image_feature) # will throw an exception if not serializable 

41 

42 

43def test_classification(): 

44 expected_classification = Classification("name", (1, 1, 1)) 

45 image_feature = ImageFeature(None, classification=expected_classification) 

46 

47 classification = image_feature.classification 

48 

49 assert classification == expected_classification 

50 

51 

52def test_json_serializable_with_classification(): 

53 classification = Classification("name", (1, 1, 1)) 

54 

55 image_feature = ImageFeature(None, classification=classification) 

56 

57 geojson.dumps(image_feature) # will throw an exception if not serializable 

58 

59 

60def test_name(): 

61 expected_name = "name" 

62 image_feature = ImageFeature(None, name=expected_name) 

63 

64 name = image_feature.name 

65 

66 assert name == expected_name 

67 

68 

69def test_json_serializable_with_name(): 

70 name = "name" 

71 

72 image_feature = ImageFeature(None, name=name) 

73 

74 geojson.dumps(image_feature) # will throw an exception if not serializable 

75 

76 

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) 

83 

84 measurements = image_feature.measurements 

85 

86 assert measurements == expected_measurements 

87 

88 

89def test_json_serializable_with_measurements(): 

90 measurements = {"some_value": 0.324, "nan_value": float("nan")} 

91 

92 image_feature = ImageFeature(None, measurements=measurements) 

93 

94 geojson.dumps(image_feature) # will throw an exception if not serializable 

95 

96 

97def test_object_type(): 

98 expected_object_type = ObjectType.DETECTION 

99 image_feature = ImageFeature(None, object_type=expected_object_type) 

100 

101 object_type = image_feature.object_type 

102 

103 assert object_type == expected_object_type 

104 

105 

106def test_json_serializable_with_object_type(): 

107 object_type = ObjectType.DETECTION 

108 

109 image_feature = ImageFeature(None, object_type=object_type) 

110 

111 geojson.dumps(image_feature) # will throw an exception if not serializable 

112 

113 

114def test_is_detection(): 

115 expected_object_type = ObjectType.DETECTION 

116 image_feature = ImageFeature(None, object_type=expected_object_type) 

117 

118 is_detection = image_feature.is_detection 

119 

120 assert is_detection 

121 

122 

123def test_is_not_detection(): 

124 expected_object_type = ObjectType.ANNOTATION 

125 image_feature = ImageFeature(None, object_type=expected_object_type) 

126 

127 is_detection = image_feature.is_detection 

128 

129 assert not (is_detection) 

130 

131 

132def test_is_cell(): 

133 expected_object_type = ObjectType.CELL 

134 image_feature = ImageFeature(None, object_type=expected_object_type) 

135 

136 is_cell = image_feature.is_cell 

137 

138 assert is_cell 

139 

140 

141def test_is_not_cell(): 

142 expected_object_type = ObjectType.DETECTION 

143 image_feature = ImageFeature(None, object_type=expected_object_type) 

144 

145 is_cell = image_feature.is_cell 

146 

147 assert not (is_cell) 

148 

149 

150def test_is_tile(): 

151 expected_object_type = ObjectType.TILE 

152 image_feature = ImageFeature(None, object_type=expected_object_type) 

153 

154 is_tile = image_feature.is_tile 

155 

156 assert is_tile 

157 

158 

159def test_is_not_tile(): 

160 expected_object_type = ObjectType.DETECTION 

161 image_feature = ImageFeature(None, object_type=expected_object_type) 

162 

163 is_tile = image_feature.is_tile 

164 

165 assert not (is_tile) 

166 

167 

168def test_is_annotation(): 

169 expected_object_type = ObjectType.ANNOTATION 

170 image_feature = ImageFeature(None, object_type=expected_object_type) 

171 

172 is_annotation = image_feature.is_annotation 

173 

174 assert is_annotation 

175 

176 

177def test_is_not_annotation(): 

178 expected_object_type = ObjectType.DETECTION 

179 image_feature = ImageFeature(None, object_type=expected_object_type) 

180 

181 is_annotation = image_feature.is_annotation 

182 

183 assert not (is_annotation) 

184 

185 

186def test_color(): 

187 expected_color = (4, 5, 6) 

188 image_feature = ImageFeature(None, color=expected_color) 

189 

190 color = image_feature.color 

191 

192 assert color == expected_color 

193 

194 

195def test_json_serializable_with_color(): 

196 color = (4, 5, 6) 

197 

198 image_feature = ImageFeature(None, color=color) 

199 

200 geojson.dumps(image_feature) # will throw an exception if not serializable 

201 

202 

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 ) 

208 

209 nucleus_geometry = image_feature.nucleus_geometry 

210 

211 assert nucleus_geometry == expected_nucleus_geometry 

212 

213 

214def test_json_serializable_with_nucleus_geometry(): 

215 nucleus_geometry = geojson.Point((-115.81, 37.24)) 

216 

217 image_feature = ImageFeature(None, extra_geometries={"nucleus": nucleus_geometry}) 

218 

219 geojson.dumps(image_feature) # will throw an exception if not serializable 

220 

221 

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) 

226 

227 geometry = image_feature.geometry 

228 

229 assert geometry == expected_geometry 

230 

231 

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) 

236 

237 id = image_feature.id 

238 

239 assert id == str(expected_id) 

240 

241 

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) 

246 

247 classification = image_feature.classification 

248 

249 assert classification == expected_classification 

250 

251 

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) 

256 

257 name = image_feature.name 

258 

259 assert name == expected_name 

260 

261 

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) 

269 

270 measurements = image_feature.measurements 

271 

272 assert measurements == expected_measurements 

273 

274 

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) 

279 

280 object_type = image_feature.object_type 

281 

282 assert object_type == expected_object_type 

283 

284 

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) 

289 

290 color = image_feature.color 

291 

292 assert color == expected_color 

293 

294 

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) 

299 

300 nucleus_geometry = image_feature.nucleus_geometry 

301 

302 assert nucleus_geometry == expected_nucleus_geometry 

303 

304 

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 

322 

323 features = ImageFeature.create_from_label_image(label_image) 

324 

325 assert len(features) == expected_number_of_features 

326 

327 

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 

346 

347 features = ImageFeature.create_from_label_image(label_image, scale=scale) 

348 

349 assert len(features) == expected_number_of_features 

350 

351 

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 ) 

369 

370 features = ImageFeature.create_from_label_image( 

371 label_image, object_type=expected_object_type 

372 ) 

373 

374 assert all(feature.object_type == expected_object_type for feature in features) 

375 

376 

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 ) 

394 

395 features = ImageFeature.create_from_label_image(label_image, include_labels=True) 

396 

397 assert all(feature.measurements in expected_measurements for feature in features) 

398 

399 

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 ) 

417 

418 features = ImageFeature.create_from_label_image( 

419 label_image, classification_names=expected_classification_name 

420 ) 

421 

422 assert all( 

423 feature.classification.name == expected_classification_name 

424 for feature in features 

425 ) 

426 

427 

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 ) 

450 

451 features = ImageFeature.create_from_label_image( 

452 label_image, classification_names=classification_dict 

453 ) 

454 

455 assert all( 

456 feature.classification is None 

457 or feature.classification.name in expected_classification_names 

458 for feature in features 

459 ) 

460 

461 

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 

479 

480 features = ImageFeature.create_from_label_image(binary_image) 

481 

482 assert len(features) == expected_number_of_features 

483 

484 

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 

503 

504 features = ImageFeature.create_from_label_image(binary_image, scale=scale) 

505 

506 assert len(features) == expected_number_of_features 

507 

508 

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 ) 

526 

527 features = ImageFeature.create_from_label_image( 

528 binary_image, object_type=expected_object_type 

529 ) 

530 

531 assert all(feature.object_type == expected_object_type for feature in features) 

532 

533 

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 ) 

551 

552 features = ImageFeature.create_from_label_image(binary_image, include_labels=True) 

553 

554 assert all(feature.measurements == expected_measurement for feature in features) 

555 

556 

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 ) 

574 

575 features = ImageFeature.create_from_label_image( 

576 binary_image, classification_names=expected_classification_name 

577 ) 

578 

579 assert all( 

580 feature.classification.name == expected_classification_name 

581 for feature in features 

582 ) 

583 

584 

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 ) 

603 

604 features = ImageFeature.create_from_label_image( 

605 binary_image, classification_names=classification_dict 

606 ) 

607 

608 assert all( 

609 feature.classification.name in expected_classification_names 

610 for feature in features 

611 ) 

612 

613 

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 } 

621 

622 classification = image_feature.classification 

623 

624 assert classification == expected_classification 

625 

626 

627def test_name_when_set_after_creation(): 

628 expected_name = "name" 

629 image_feature = ImageFeature(None) 

630 image_feature.name = expected_name 

631 

632 name = image_feature.name 

633 

634 assert name == expected_name 

635 

636 

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 

644 

645 measurements = image_feature.measurements 

646 

647 assert measurements == expected_measurements 

648 

649 

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 

654 

655 object_type = image_feature.object_type 

656 

657 assert object_type == expected_object_type 

658 

659 

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 

664 

665 object_type = image_feature.object_type 

666 

667 assert object_type == expected_object_type 

668 

669 

670def test_color_when_set_after_creation(): 

671 expected_color = (4, 5, 6) 

672 image_feature = ImageFeature(None) 

673 image_feature.color = expected_color 

674 

675 color = image_feature.color 

676 

677 assert color == expected_color 

678 

679 

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 

684 

685 nucleus_geometry = image_feature.nucleus_geometry 

686 

687 assert nucleus_geometry == expected_nucleus_geometry