Coverage for tests/objects/test_image_feature.py: 100%

275 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-31 11:24 +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 == 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 = { 

79 "some_value": 0.324, 

80 "nan_value": float('nan') 

81 } 

82 expected_measurements = {k: v for k, v in inital_measurements.items() if not math.isnan(v)} # NaN values are skipped 

83 image_feature = ImageFeature(None, measurements=inital_measurements) 

84 

85 measurements = image_feature.measurements 

86 

87 assert measurements == expected_measurements 

88 

89 

90def test_json_serializable_with_measurements(): 

91 measurements = { 

92 "some_value": 0.324, 

93 "nan_value": float('nan') 

94 } 

95 

96 image_feature = ImageFeature(None, measurements=measurements) 

97 

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

99 

100 

101def test_object_type(): 

102 expected_object_type = ObjectType.DETECTION 

103 image_feature = ImageFeature(None, object_type=expected_object_type) 

104 

105 object_type = image_feature.object_type 

106 

107 assert object_type == expected_object_type 

108 

109 

110def test_json_serializable_with_object_type(): 

111 object_type = ObjectType.DETECTION 

112 

113 image_feature = ImageFeature(None, object_type=object_type) 

114 

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

116 

117 

118def test_is_detection(): 

119 expected_object_type = ObjectType.DETECTION 

120 image_feature = ImageFeature(None, object_type=expected_object_type) 

121 

122 is_detection = image_feature.is_detection 

123 

124 assert is_detection 

125 

126 

127def test_is_not_detection(): 

128 expected_object_type = ObjectType.ANNOTATION 

129 image_feature = ImageFeature(None, object_type=expected_object_type) 

130 

131 is_detection = image_feature.is_detection 

132 

133 assert not(is_detection) 

134 

135 

136def test_is_cell(): 

137 expected_object_type = ObjectType.CELL 

138 image_feature = ImageFeature(None, object_type=expected_object_type) 

139 

140 is_cell = image_feature.is_cell 

141 

142 assert is_cell 

143 

144 

145def test_is_not_cell(): 

146 expected_object_type = ObjectType.DETECTION 

147 image_feature = ImageFeature(None, object_type=expected_object_type) 

148 

149 is_cell = image_feature.is_cell 

150 

151 assert not(is_cell) 

152 

153 

154def test_is_tile(): 

155 expected_object_type = ObjectType.TILE 

156 image_feature = ImageFeature(None, object_type=expected_object_type) 

157 

158 is_tile = image_feature.is_tile 

159 

160 assert is_tile 

161 

162 

163def test_is_not_tile(): 

164 expected_object_type = ObjectType.DETECTION 

165 image_feature = ImageFeature(None, object_type=expected_object_type) 

166 

167 is_tile = image_feature.is_tile 

168 

169 assert not(is_tile) 

170 

171 

172def test_is_annotation(): 

173 expected_object_type = ObjectType.ANNOTATION 

174 image_feature = ImageFeature(None, object_type=expected_object_type) 

175 

176 is_annotation = image_feature.is_annotation 

177 

178 assert is_annotation 

179 

180 

181def test_is_not_annotation(): 

182 expected_object_type = ObjectType.DETECTION 

183 image_feature = ImageFeature(None, object_type=expected_object_type) 

184 

185 is_annotation = image_feature.is_annotation 

186 

187 assert not(is_annotation) 

188 

189 

190def test_color(): 

191 expected_color = (4, 5, 6) 

192 image_feature = ImageFeature(None, color=expected_color) 

193 

194 color = image_feature.color 

195 

196 assert color == expected_color 

197 

198 

199def test_json_serializable_with_color(): 

200 color = (4, 5, 6) 

201 

202 image_feature = ImageFeature(None, color=color) 

203 

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

205 

206 

207def test_nucleus_geometry(): 

208 expected_nucleus_geometry = geojson.Point((-115.81, 37.24)) 

209 image_feature = ImageFeature(None, extra_geometries={ 

210 "nucleus": expected_nucleus_geometry 

211 }) 

212 

213 nucleus_geometry = image_feature.nucleus_geometry 

214 

215 assert nucleus_geometry == expected_nucleus_geometry 

216 

217 

218def test_json_serializable_with_nucleus_geometry(): 

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

220 

221 image_feature = ImageFeature(None, extra_geometries={ 

222 "nucleus": nucleus_geometry 

223 }) 

224 

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

226 

227 

228def test_geometry_when_created_from_feature(): 

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

230 feature = geojson.Feature(geometry=expected_geometry) 

231 image_feature = ImageFeature.create_from_feature(feature) 

232 

233 geometry = image_feature.geometry 

234 

235 assert geometry == expected_geometry 

236 

237 

238def test_id_when_created_from_feature(): 

239 expected_id = 23 

240 feature = geojson.Feature(id=expected_id) 

241 image_feature = ImageFeature.create_from_feature(feature) 

242 

243 id = image_feature.id 

244 

245 assert id == expected_id 

246 

247 

248def test_classification_when_created_from_feature(): 

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

250 feature = geojson.Feature(properties={ 

251 "classification": expected_classification 

252 }) 

253 image_feature = ImageFeature.create_from_feature(feature) 

254 

255 classification = image_feature.classification 

256 

257 assert classification == expected_classification 

258 

259 

260def test_name_when_created_from_feature(): 

261 expected_name = "name" 

262 feature = geojson.Feature(properties={ 

263 "name": expected_name 

264 }) 

265 image_feature = ImageFeature.create_from_feature(feature) 

266 

267 name = image_feature.name 

268 

269 assert name == expected_name 

270 

271 

272def test_measurements_when_created_from_feature(): 

273 inital_measurements = { 

274 "some_value": 0.324, 

275 "nan_value": float('nan') 

276 } 

277 expected_measurements = {k: v for k, v in inital_measurements.items() if not math.isnan(v)} # NaN values are skipped 

278 feature = geojson.Feature(properties={ 

279 "measurements": inital_measurements 

280 }) 

281 image_feature = ImageFeature.create_from_feature(feature) 

282 

283 measurements = image_feature.measurements 

284 

285 assert measurements == expected_measurements 

286 

287 

288def test_object_type_when_created_from_feature(): 

289 expected_object_type = ObjectType.CELL 

290 feature = geojson.Feature(properties={ 

291 "object_type": expected_object_type.name 

292 }) 

293 image_feature = ImageFeature.create_from_feature(feature) 

294 

295 object_type = image_feature.object_type 

296 

297 assert object_type == expected_object_type 

298 

299 

300def test_color_when_created_from_feature(): 

301 expected_color = (4, 5, 6) 

302 feature = geojson.Feature(properties={ 

303 "color": expected_color 

304 }) 

305 image_feature = ImageFeature.create_from_feature(feature) 

306 

307 color = image_feature.color 

308 

309 assert color == expected_color 

310 

311 

312def test_nucleus_geometry_when_created_from_feature(): 

313 expected_nucleus_geometry = geojson.Point((-115.81, 37.24)) 

314 feature = geojson.Feature(properties={ 

315 "nucleusGeometry": expected_nucleus_geometry 

316 }) 

317 image_feature = ImageFeature.create_from_feature(feature) 

318 

319 nucleus_geometry = image_feature.nucleus_geometry 

320 

321 assert nucleus_geometry == expected_nucleus_geometry 

322 

323 

324def test_number_of_features_when_created_from_label_image_without_scale(): 

325 label_image = np.array( 

326 [[0, 1, 1, 0, 0], 

327 [0, 1, 1, 0, 0], 

328 [0, 0, 0, 0, 0], 

329 [0, 0, 0, 1, 0], 

330 [0, 0, 0, 1, 0], 

331 [0, 0, 0, 0, 0], 

332 [0, 0, 2, 2, 0], 

333 [0, 0, 0, 0, 0], 

334 [0, 0, 0, 0, 0], 

335 [0, 3, 0, 0, 0],], 

336 dtype=np.uint8 

337 ) 

338 expected_number_of_features = 3 

339 

340 features = ImageFeature.create_from_label_image(label_image) 

341 

342 assert len(features) == expected_number_of_features 

343 

344 

345def test_number_of_features_when_created_from_label_image_with_scale(): 

346 scale = 2 

347 label_image = np.array( 

348 [[0, 1, 1, 0, 0], 

349 [0, 1, 1, 0, 0], 

350 [0, 0, 0, 0, 0], 

351 [0, 0, 0, 1, 0], 

352 [0, 0, 0, 1, 0], 

353 [0, 0, 0, 0, 0], 

354 [0, 0, 2, 2, 0], 

355 [0, 0, 0, 0, 0], 

356 [0, 0, 0, 0, 0], 

357 [0, 3, 0, 0, 0],], 

358 dtype=np.uint8 

359 ) 

360 expected_number_of_features = 3 

361 

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

363 

364 assert len(features) == expected_number_of_features 

365 

366 

367def test_object_type_when_created_from_label_image(): 

368 expected_object_type = ObjectType.CELL 

369 label_image = np.array( 

370 [[0, 1, 1, 0, 0], 

371 [0, 1, 1, 0, 0], 

372 [0, 0, 0, 0, 0], 

373 [0, 0, 0, 1, 0], 

374 [0, 0, 0, 1, 0], 

375 [0, 0, 0, 0, 0], 

376 [0, 0, 2, 2, 0], 

377 [0, 0, 0, 0, 0], 

378 [0, 0, 0, 0, 0], 

379 [0, 3, 0, 0, 0],], 

380 dtype=np.uint8 

381 ) 

382 

383 features = ImageFeature.create_from_label_image(label_image, object_type=expected_object_type) 

384 

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

386 

387 

388def test_measurement_when_created_from_label_image(): 

389 expected_measurements = [{'Label': float(label)} for label in [1, 2, 3]] 

390 label_image = np.array( 

391 [[0, 1, 1, 0, 0], 

392 [0, 1, 1, 0, 0], 

393 [0, 0, 0, 0, 0], 

394 [0, 0, 0, 1, 0], 

395 [0, 0, 0, 1, 0], 

396 [0, 0, 0, 0, 0], 

397 [0, 0, 2, 2, 0], 

398 [0, 0, 0, 0, 0], 

399 [0, 0, 0, 0, 0], 

400 [0, 3, 0, 0, 0],], 

401 dtype=np.uint8 

402 ) 

403 

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

405 

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

407 

408 

409def test_classification_when_created_from_label_image_and_classification_name_provided(): 

410 expected_classification_name = "name" 

411 label_image = np.array( 

412 [[0, 1, 1, 0, 0], 

413 [0, 1, 1, 0, 0], 

414 [0, 0, 0, 0, 0], 

415 [0, 0, 0, 1, 0], 

416 [0, 0, 0, 1, 0], 

417 [0, 0, 0, 0, 0], 

418 [0, 0, 2, 2, 0], 

419 [0, 0, 0, 0, 0], 

420 [0, 0, 0, 0, 0], 

421 [0, 3, 0, 0, 0],], 

422 dtype=np.uint8 

423 ) 

424 

425 features = ImageFeature.create_from_label_image(label_image, classification_names=expected_classification_name) 

426 

427 assert all(feature.classification.name == expected_classification_name for feature in features) 

428 

429 

430def test_classification_when_created_from_label_image_and_classification_dict_provided(): 

431 classification_dict = { 

432 # no classification for label 1 

433 2: "name2", 

434 3: "name3" 

435 } 

436 expected_classification_names = classification_dict.values() 

437 label_image = np.array( 

438 [[0, 1, 1, 0, 0], 

439 [0, 1, 1, 0, 0], 

440 [0, 0, 0, 0, 0], 

441 [0, 0, 0, 1, 0], 

442 [0, 0, 0, 1, 0], 

443 [0, 0, 0, 0, 0], 

444 [0, 0, 2, 2, 0], 

445 [0, 0, 0, 0, 0], 

446 [0, 0, 0, 0, 0], 

447 [0, 3, 0, 0, 0],], 

448 dtype=np.uint8 

449 ) 

450 

451 features = ImageFeature.create_from_label_image(label_image, classification_names=classification_dict) 

452 

453 assert all(feature.classification is None or feature.classification.name in expected_classification_names for feature in features) 

454 

455 

456def test_number_of_features_when_created_from_binary_image_without_scale(): 

457 binary_image = np.array( 

458 [[False, True, True, False, False], 

459 [False, True, True, False, False], 

460 [False, False, False, False, False], 

461 [False, False, False, True, False], 

462 [False, False, False, True, False], 

463 [False, False, False, False, False], 

464 [False, False, True, True, False], 

465 [False, False, False, False, False], 

466 [False, False, False, False, False], 

467 [False, True, False, False, False],], 

468 dtype=bool 

469 ) 

470 expected_number_of_features = 1 

471 

472 features = ImageFeature.create_from_label_image(binary_image) 

473 

474 assert len(features) == expected_number_of_features 

475 

476 

477def test_number_of_features_when_created_from_binary_image_with_scale(): 

478 scale = 2 

479 binary_image = np.array( 

480 [[False, True, True, False, False], 

481 [False, True, True, False, False], 

482 [False, False, False, False, False], 

483 [False, False, False, True, False], 

484 [False, False, False, True, False], 

485 [False, False, False, False, False], 

486 [False, False, True, True, False], 

487 [False, False, False, False, False], 

488 [False, False, False, False, False], 

489 [False, True, False, False, False],], 

490 dtype=bool 

491 ) 

492 expected_number_of_features = 1 

493 

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

495 

496 assert len(features) == expected_number_of_features 

497 

498 

499def test_object_type_when_created_from_binary_image(): 

500 expected_object_type = ObjectType.TILE 

501 binary_image = np.array( 

502 [[False, True, True, False, False], 

503 [False, True, True, False, False], 

504 [False, False, False, False, False], 

505 [False, False, False, True, False], 

506 [False, False, False, True, False], 

507 [False, False, False, False, False], 

508 [False, False, True, True, False], 

509 [False, False, False, False, False], 

510 [False, False, False, False, False], 

511 [False, True, False, False, False],], 

512 dtype=bool 

513 ) 

514 

515 features = ImageFeature.create_from_label_image(binary_image, object_type=expected_object_type) 

516 

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

518 

519 

520def test_measurement_when_created_from_binary_image(): 

521 expected_measurement = {'Label': 1.0} 

522 binary_image = np.array( 

523 [[False, True, True, False, False], 

524 [False, True, True, False, False], 

525 [False, False, False, False, False], 

526 [False, False, False, True, False], 

527 [False, False, False, True, False], 

528 [False, False, False, False, False], 

529 [False, False, True, True, False], 

530 [False, False, False, False, False], 

531 [False, False, False, False, False], 

532 [False, True, False, False, False],], 

533 dtype=bool 

534 ) 

535 

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

537 

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

539 

540 

541def test_classification_when_created_from_binary_image_and_classification_name_provided(): 

542 expected_classification_name = "name" 

543 binary_image = np.array( 

544 [[False, True, True, False, False], 

545 [False, True, True, False, False], 

546 [False, False, False, False, False], 

547 [False, False, False, True, False], 

548 [False, False, False, True, False], 

549 [False, False, False, False, False], 

550 [False, False, True, True, False], 

551 [False, False, False, False, False], 

552 [False, False, False, False, False], 

553 [False, True, False, False, False],], 

554 dtype=bool 

555 ) 

556 

557 features = ImageFeature.create_from_label_image(binary_image, classification_names=expected_classification_name) 

558 

559 assert all(feature.classification.name == expected_classification_name for feature in features) 

560 

561 

562def test_classification_when_created_from_binary_image_and_classification_dict_provided(): 

563 classification_dict = { 

564 1: "name1" 

565 } 

566 expected_classification_names = classification_dict.values() 

567 binary_image = np.array( 

568 [[False, True, True, False, False], 

569 [False, True, True, False, False], 

570 [False, False, False, False, False], 

571 [False, False, False, True, False], 

572 [False, False, False, True, False], 

573 [False, False, False, False, False], 

574 [False, False, True, True, False], 

575 [False, False, False, False, False], 

576 [False, False, False, False, False], 

577 [False, True, False, False, False],], 

578 dtype=bool 

579 ) 

580 

581 features = ImageFeature.create_from_label_image(binary_image, classification_names=classification_dict) 

582 

583 assert all(feature.classification.name in expected_classification_names for feature in features) 

584 

585 

586def test_classification_when_set_after_creation(): 

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

588 image_feature = ImageFeature(None) 

589 image_feature.classification = { 

590 "name": expected_classification.name, 

591 "color": expected_classification.color 

592 } 

593 

594 classification = image_feature.classification 

595 

596 assert classification == expected_classification 

597 

598 

599def test_name_when_set_after_creation(): 

600 expected_name = "name" 

601 image_feature = ImageFeature(None) 

602 image_feature.name = expected_name 

603 

604 name = image_feature.name 

605 

606 assert name == expected_name 

607 

608 

609def test_measurements_when_set_after_creation(): 

610 inital_measurements = { 

611 "some_value": 0.324, 

612 "nan_value": float('nan') 

613 } 

614 expected_measurements = {k: v for k, v in inital_measurements.items() if not math.isnan(v)} # NaN values are skipped 

615 image_feature = ImageFeature(None) 

616 image_feature.measurements = inital_measurements 

617 

618 measurements = image_feature.measurements 

619 

620 assert measurements == expected_measurements 

621 

622 

623def test_object_type_when_set_after_creation(): 

624 expected_object_type = ObjectType.CELL 

625 image_feature = ImageFeature(None) 

626 image_feature.object_type = expected_object_type 

627 

628 object_type = image_feature.object_type 

629 

630 assert object_type == expected_object_type 

631 

632 

633def test_object_type_name_when_set_after_creation(): 

634 expected_object_type = ObjectType.TILE 

635 image_feature = ImageFeature(None) 

636 image_feature.object_type = expected_object_type.name 

637 

638 object_type = image_feature.object_type 

639 

640 assert object_type == expected_object_type 

641 

642 

643def test_color_when_set_after_creation(): 

644 expected_color = (4, 5, 6) 

645 image_feature = ImageFeature(None) 

646 image_feature.color = expected_color 

647 

648 color = image_feature.color 

649 

650 assert color == expected_color 

651 

652 

653def test_nucleus_geometry_when_set_after_creation(): 

654 expected_nucleus_geometry = geojson.Point((-115.81, 37.24)) 

655 image_feature = ImageFeature(None) 

656 image_feature.nucleus_geometry = expected_nucleus_geometry 

657 

658 nucleus_geometry = image_feature.nucleus_geometry 

659 

660 assert nucleus_geometry == expected_nucleus_geometry