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

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 

11 

12 

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) 

29 

30 

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) 

35 

36 width = labeled_server.metadata.width 

37 

38 assert width == expected_width 

39 

40 labeled_server.close() 

41 

42 

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) 

47 

48 height = labeled_server.metadata.height 

49 

50 assert height == expected_height 

51 

52 labeled_server.close() 

53 

54 

55def test_image_width_with_no_downsample(): 

56 expected_width = sample_metadata.shape.x 

57 labeled_server = LabeledImageServer(sample_metadata, []) 

58 

59 width = labeled_server.metadata.width 

60 

61 assert width == expected_width 

62 

63 labeled_server.close() 

64 

65 

66def test_image_height_with_no_downsample(): 

67 expected_height = sample_metadata.shape.y 

68 labeled_server = LabeledImageServer(sample_metadata, []) 

69 

70 height = labeled_server.metadata.height 

71 

72 assert height == expected_height 

73 

74 labeled_server.close() 

75 

76 

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) 

87 

88 n_channels = labeled_server.metadata.n_channels 

89 

90 assert n_channels == expected_number_of_channels 

91 

92 labeled_server.close() 

93 

94 

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) 

105 

106 n_channels = labeled_server.metadata.n_channels 

107 

108 assert n_channels == expected_number_of_channels 

109 

110 labeled_server.close() 

111 

112 

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 ) 

129 

130 n_channels = labeled_server.metadata.n_channels 

131 

132 assert n_channels == expected_number_of_channels 

133 

134 labeled_server.close() 

135 

136 

137def test_image_n_timepoints(): 

138 expected_n_timepoints = 1 

139 labeled_server = LabeledImageServer(sample_metadata, []) 

140 

141 n_timepoints = labeled_server.metadata.n_timepoints 

142 

143 assert n_timepoints == expected_n_timepoints 

144 

145 labeled_server.close() 

146 

147 

148def test_image_n_z_slices(): 

149 expected_n_z_slices = 1 

150 labeled_server = LabeledImageServer(sample_metadata, []) 

151 

152 n_z_slices = labeled_server.metadata.n_z_slices 

153 

154 assert n_z_slices == expected_n_z_slices 

155 

156 labeled_server.close() 

157 

158 

159def test_image_n_resolutions(): 

160 expected_n_resolutions = 1 

161 labeled_server = LabeledImageServer(sample_metadata, []) 

162 

163 n_resolutions = labeled_server.metadata.n_resolutions 

164 

165 assert n_resolutions == expected_n_resolutions 

166 

167 labeled_server.close() 

168 

169 

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) 

174 

175 length_x = labeled_server.metadata.pixel_calibration.length_x.length 

176 

177 assert length_x == expected_length_x 

178 

179 labeled_server.close() 

180 

181 

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, []) 

185 

186 length_x = labeled_server.metadata.pixel_calibration.length_x.length 

187 

188 assert length_x == expected_length_x 

189 

190 labeled_server.close() 

191 

192 

193def test_dtype_when_not_multi_channel(): 

194 expected_dtype = np.uint32 

195 labeled_server = LabeledImageServer(sample_metadata, [], multichannel=False) 

196 

197 dtype = labeled_server.metadata.dtype 

198 

199 assert dtype == expected_dtype 

200 

201 labeled_server.close() 

202 

203 

204def test_dtype_when_multi_channel(): 

205 expected_dtype = np.bool 

206 labeled_server = LabeledImageServer(sample_metadata, [], multichannel=True) 

207 

208 dtype = labeled_server.metadata.dtype 

209 

210 assert dtype == expected_dtype 

211 

212 labeled_server.close() 

213 

214 

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) 

236 

237 image = labeled_server.read_region( 

238 1, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height) 

239 ) 

240 

241 np.testing.assert_array_equal(image, expected_image) 

242 

243 

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) 

289 

290 image = labeled_server.read_region( 

291 1, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height) 

292 ) 

293 

294 np.testing.assert_array_equal(image, expected_image) 

295 

296 

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 ) 

324 

325 image = labeled_server.read_region( 

326 1, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height) 

327 ) 

328 

329 np.testing.assert_array_equal(image, expected_image) 

330 

331 

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 ) 

345 

346 image = labeled_server.read_region( 

347 downsample, 

348 Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height), 

349 ) 

350 

351 np.testing.assert_array_equal(image, expected_image) 

352 

353 

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) 

373 

374 image = labeled_server.read_region( 

375 1, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height) 

376 ) 

377 

378 np.testing.assert_array_equal(image, expected_image) 

379 

380 

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 ) 

392 

393 image = labeled_server.read_region( 

394 downsample, 

395 Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height), 

396 ) 

397 

398 np.testing.assert_array_equal(image, expected_image) 

399 

400 

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) 

421 

422 image = labeled_server.read_region( 

423 1, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height) 

424 ) 

425 

426 np.testing.assert_array_equal(image, expected_image) 

427 

428 

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 ) 

441 

442 image = labeled_server.read_region( 

443 downsample, 

444 Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height), 

445 ) 

446 

447 np.testing.assert_array_equal(image, expected_image) 

448 

449 

450def test_label_can_hold_many_values(): 

451 downsample = 1 

452 max_objects = 1000 

453 random.seed(1) 

454 

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)) 

459 

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 ) 

464 

465 image = labeled_server.read_region( 

466 1, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height) 

467 ) 

468 

469 assert np.max(image) == max_objects 

470 

471 

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) 

483 

484 np.testing.assert_array_equal(image, expected_image) 

485 

486 

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) 

499 

500 np.testing.assert_array_equal(image, expected_image) 

501 

502 

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) 

514 

515 np.testing.assert_array_equal(image, expected_image) 

516 

517 

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) 

541 

542 np.testing.assert_array_equal(image, expected_image) 

543 

544 

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) 

568 

569 np.testing.assert_array_equal(image, expected_image) 

570 

571 

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) 

597 

598 np.testing.assert_array_equal(image, expected_image) 

599 

600 

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) 

623 

624 np.testing.assert_array_equal(image, expected_image)