Coverage for tests/images/test_qupath_server.py: 100%

306 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-31 11:24 +0000

1import numpy as np 

2import tifffile 

3import imageio.v3 as iio 

4import base64 

5import pytest 

6from unittest.mock import Mock 

7from qubalab.images.qupath_server import QuPathServer 

8from qubalab.images.region_2d import Region2D 

9from qubalab.images.metadata.image_metadata import ImageMetadata 

10from qubalab.images.metadata.image_shape import ImageShape 

11from qubalab.images.metadata.pixel_calibration import PixelCalibration, PixelLength 

12 

13 

14sample_RGB_metadata = ImageMetadata( 

15 "/path/to/img.tiff", 

16 "Image name", 

17 ( 

18 ImageShape(64, 50, c=3), 

19 ImageShape(32, 25, c=3), 

20 ImageShape(16, 12, c=3) 

21 ), 

22 PixelCalibration( 

23 PixelLength.create_microns(2.5), 

24 PixelLength.create_microns(2.5) 

25 ), 

26 True, 

27 np.uint8 

28) 

29sample_RGB_pixels = [[[[ 

30 x / shape.x * 255 if c == 0 else (y / shape.y * 255 if c == 1 else 0) 

31 for x in range(shape.x)] 

32 for y in range(shape.y)] 

33 for c in range(shape.c)] 

34 for shape in sample_RGB_metadata.shapes 

35] 

36 

37 

38sample_float32_metadata = ImageMetadata( 

39 "/path/to/img.tiff", 

40 "Image name", 

41 ( 

42 ImageShape(64, 50, t=5, z=2, c=3), 

43 ImageShape(32, 25, t=5, z=2, c=3), 

44 ImageShape(16, 12, t=5, z=2, c=3) 

45 ), 

46 PixelCalibration( 

47 PixelLength.create_microns(2.5), 

48 PixelLength.create_microns(2.5) 

49 ), 

50 False, 

51 np.float32 

52) 

53sample_float32_pixels = [[[[[[ 

54 x/shape.x + y/shape.y + z/shape.z + c/shape.c + t/shape.t 

55 for x in range(shape.x)] 

56 for y in range(shape.y)] 

57 for z in range(shape.z)] 

58 for c in range(shape.c)] 

59 for t in range(shape.t)] 

60 for shape in sample_float32_metadata.shapes 

61] 

62 

63 

64def _create_qupath_metadata(metadata: ImageMetadata): 

65 qupath_metadata = Mock() 

66 qupath_metadata.getName.return_value = metadata.name 

67 

68 qupath_levels = [] 

69 for shape in metadata.shapes: 

70 level = Mock() 

71 level.getWidth.return_value = shape.x 

72 level.getHeight.return_value = shape.y 

73 qupath_levels.append(level) 

74 qupath_metadata.getLevels.return_value = qupath_levels 

75 

76 qupath_channels = [] 

77 for channel in metadata.channels: 

78 qupath_channel = Mock() 

79 qupath_channel.getName.return_value = channel.name 

80 qupath_channel.getColor.return_value = ((255 & 0xff)<<24) + \ 

81 ((int(channel.color[0]) * 255 & 0xff)<<16) + \ 

82 ((int(channel.color[1] * 255) & 0xff)<<8) + \ 

83 (int(channel.color[2] * 255) & 0xff) 

84 qupath_channels.append(qupath_channel) 

85 qupath_metadata.getChannels.return_value = qupath_channels 

86 

87 return qupath_metadata 

88 

89 

90def _create_qupath_server(metadata: ImageMetadata): 

91 qupath_server = Mock() 

92 qupath_server.getMetadata.return_value = _create_qupath_metadata(metadata) 

93 qupath_server.getURIs.return_value = ("file://" + metadata.path,) 

94 qupath_server.nChannels.return_value = metadata.n_channels 

95 qupath_server.nZSlices.return_value = metadata.n_z_slices 

96 qupath_server.nTimepoints.return_value = metadata.n_timepoints 

97 qupath_server.isRGB.return_value = metadata.is_rgb 

98 qupath_server.getPreferredDownsamples.return_value = metadata.downsamples 

99 qupath_server.getDownsampleForResolution.side_effect = lambda level : metadata.downsamples[level] 

100 qupath_server.getPath.return_value = metadata.path 

101 

102 pixel_type_text = Mock() 

103 pixel_type_text.lower.return_value = metadata.dtype 

104 pixel_type = Mock() 

105 pixel_type.toString.return_value = pixel_type_text 

106 qupath_server.getPixelType.return_value = pixel_type 

107 

108 pixel_calibration = Mock() 

109 pixel_calibration.hasPixelSizeMicrons.return_value = metadata.pixel_calibration.length_x.unit == metadata.pixel_calibration.length_y.unit == "micrometer" 

110 pixel_calibration.hasZSpacingMicrons.return_value = metadata.pixel_calibration.length_z.unit == "micrometer" 

111 pixel_calibration.getPixelWidthMicrons.return_value = metadata.pixel_calibration.length_x.length 

112 pixel_calibration.getPixelHeightMicrons.return_value = metadata.pixel_calibration.length_y.length 

113 pixel_calibration.getZSpacingMicrons.return_value = metadata.pixel_calibration.length_z.length 

114 qupath_server.getPixelCalibration.return_value = pixel_calibration 

115 

116 return qupath_server 

117 

118 

119def _create_gateway(metadata: ImageMetadata, pixels: list): 

120 def _create_region_request(path, downsample, x, y, width, height, z, t): 

121 return { 

122 "downsample": downsample, 

123 "x": x, 

124 "y": y, 

125 "width": width, 

126 "height": height, 

127 "z": z, 

128 "t": t 

129 } 

130 

131 def _get_tile(request): 

132 level = metadata.downsamples.index(request["downsample"]) 

133 image = np.array(pixels[level], dtype=metadata.dtype) 

134 

135 if metadata.n_timepoints > 1: 

136 image = image[request["t"], ...] 

137 if metadata.n_z_slices > 1: 

138 image = image[..., request["z"], :, :] 

139 image = image[:, request["y"]:request["y"]+request["height"], request["x"]:request["x"]+request["width"]] 

140 

141 return image 

142 

143 def _write_image_region(server, request, path): 

144 with tifffile.TiffWriter(path, bigtiff=True) as tif: 

145 tif.write(_get_tile(request), photometric='rgb') 

146 

147 def _get_image_bytes(server, request, format): 

148 return iio.imwrite("<bytes>", _get_tile(request), extension=".tiff", photometric='rgb') 

149 

150 def _get_image_base64(server, request, format): 

151 return base64.b64encode(_get_image_bytes(server, request, format)) 

152 

153 gateway = Mock() 

154 

155 gateway.jvm.qupath.lib.regions.RegionRequest.createInstance.side_effect = _create_region_request 

156 gateway.entry_point.writeImageRegion.side_effect = _write_image_region 

157 gateway.entry_point.getImageBytes.side_effect = _get_image_bytes 

158 gateway.entry_point.getImageBase64.side_effect = _get_image_base64 

159 

160 return gateway 

161 

162 

163def test_invalid_pixel_access(): 

164 pixel_access='invalid_pixel_access' 

165 

166 with pytest.raises(ValueError): 

167 QuPathServer(pixel_access=pixel_access) 

168 

169 

170def test_RGB_metadata(): 

171 metadata = sample_RGB_metadata 

172 gateway = _create_gateway(metadata, sample_RGB_pixels) 

173 qupath_server = _create_qupath_server(metadata) 

174 server = QuPathServer(gateway, qupath_server) 

175 

176 metadata = server.metadata 

177 

178 assert metadata == metadata 

179 

180 server.close() 

181 

182 

183def test_RGB_full_resolution_with_temp_file(): 

184 level = 0 

185 expected_image = np.array(sample_RGB_pixels[level], dtype=sample_RGB_metadata.dtype) 

186 gateway = _create_gateway(sample_RGB_metadata, sample_RGB_pixels) 

187 qupath_server = _create_qupath_server(sample_RGB_metadata) 

188 server = QuPathServer(gateway, qupath_server, 'temp_files') 

189 

190 image = server.read_region( 

191 sample_RGB_metadata.downsamples[level], 

192 Region2D(width=sample_RGB_metadata.width, height=sample_RGB_metadata.height) 

193 ) 

194 

195 np.testing.assert_array_equal(image, expected_image) 

196 

197 server.close() 

198 

199 

200def test_RGB_lowest_resolution_with_temp_file(): 

201 level = sample_RGB_metadata.n_resolutions - 1 

202 expected_image = np.array(sample_RGB_pixels[level], dtype=sample_RGB_metadata.dtype) 

203 gateway = _create_gateway(sample_RGB_metadata, sample_RGB_pixels) 

204 qupath_server = _create_qupath_server(sample_RGB_metadata) 

205 server = QuPathServer(gateway, qupath_server, 'temp_files') 

206 

207 image = server.read_region( 

208 sample_RGB_metadata.downsamples[level], 

209 Region2D(width=sample_RGB_metadata.width, height=sample_RGB_metadata.height) 

210 ) 

211 

212 np.testing.assert_array_equal(image, expected_image) 

213 

214 server.close() 

215 

216 

217def test_RGB_tile_of_full_resolution_with_temp_file(): 

218 level = 0 

219 width = 5 

220 height = 6 

221 x = 10 

222 y = 20 

223 expected_image = np.array(sample_RGB_pixels[level], dtype=sample_RGB_metadata.dtype)[:, y:y+height, x:x+width] 

224 gateway = _create_gateway(sample_RGB_metadata, sample_RGB_pixels) 

225 qupath_server = _create_qupath_server(sample_RGB_metadata) 

226 server = QuPathServer(gateway, qupath_server, 'temp_files') 

227 

228 image = server.read_region( 

229 sample_RGB_metadata.downsamples[level], 

230 Region2D(x, y, width, height) 

231 ) 

232 

233 np.testing.assert_array_equal(image, expected_image) 

234 

235 server.close() 

236 

237 

238def test_RGB_full_resolution_with_bytes(): 

239 level = 0 

240 expected_image = np.array(sample_RGB_pixels[level], dtype=sample_RGB_metadata.dtype) 

241 gateway = _create_gateway(sample_RGB_metadata, sample_RGB_pixels) 

242 qupath_server = _create_qupath_server(sample_RGB_metadata) 

243 server = QuPathServer(gateway, qupath_server, 'bytes') 

244 

245 image = server.read_region( 

246 sample_RGB_metadata.downsamples[level], 

247 Region2D(width=sample_RGB_metadata.width, height=sample_RGB_metadata.height) 

248 ) 

249 

250 np.testing.assert_array_equal(image, expected_image) 

251 

252 server.close() 

253 

254 

255def test_RGB_lowest_resolution_with_bytes(): 

256 level = sample_RGB_metadata.n_resolutions - 1 

257 expected_image = np.array(sample_RGB_pixels[level], dtype=sample_RGB_metadata.dtype) 

258 gateway = _create_gateway(sample_RGB_metadata, sample_RGB_pixels) 

259 qupath_server = _create_qupath_server(sample_RGB_metadata) 

260 server = QuPathServer(gateway, qupath_server, 'bytes') 

261 

262 image = server.read_region( 

263 sample_RGB_metadata.downsamples[level], 

264 Region2D(width=sample_RGB_metadata.width, height=sample_RGB_metadata.height) 

265 ) 

266 

267 np.testing.assert_array_equal(image, expected_image) 

268 

269 server.close() 

270 

271 

272def test_RGB_tile_of_full_resolution_with_bytes(): 

273 level = 0 

274 width = 5 

275 height = 6 

276 x = 10 

277 y = 20 

278 expected_image = np.array(sample_RGB_pixels[level], dtype=sample_RGB_metadata.dtype)[:, y:y+height, x:x+width] 

279 gateway = _create_gateway(sample_RGB_metadata, sample_RGB_pixels) 

280 qupath_server = _create_qupath_server(sample_RGB_metadata) 

281 server = QuPathServer(gateway, qupath_server, 'bytes') 

282 

283 image = server.read_region( 

284 sample_RGB_metadata.downsamples[level], 

285 Region2D(x, y, width, height) 

286 ) 

287 

288 np.testing.assert_array_equal(image, expected_image) 

289 

290 server.close() 

291 

292 

293def test_RGB_full_resolution_with_base64(): 

294 level = 0 

295 expected_image = np.array(sample_RGB_pixels[level], dtype=sample_RGB_metadata.dtype) 

296 gateway = _create_gateway(sample_RGB_metadata, sample_RGB_pixels) 

297 qupath_server = _create_qupath_server(sample_RGB_metadata) 

298 server = QuPathServer(gateway, qupath_server, 'base_64') 

299 

300 image = server.read_region( 

301 sample_RGB_metadata.downsamples[level], 

302 Region2D(width=sample_RGB_metadata.width, height=sample_RGB_metadata.height) 

303 ) 

304 

305 np.testing.assert_array_equal(image, expected_image) 

306 

307 server.close() 

308 

309 

310def test_RGB_lowest_resolution_with_base64(): 

311 level = sample_RGB_metadata.n_resolutions - 1 

312 expected_image = np.array(sample_RGB_pixels[level], dtype=sample_RGB_metadata.dtype) 

313 gateway = _create_gateway(sample_RGB_metadata, sample_RGB_pixels) 

314 qupath_server = _create_qupath_server(sample_RGB_metadata) 

315 server = QuPathServer(gateway, qupath_server, 'base_64') 

316 

317 image = server.read_region( 

318 sample_RGB_metadata.downsamples[level], 

319 Region2D(width=sample_RGB_metadata.width, height=sample_RGB_metadata.height) 

320 ) 

321 

322 np.testing.assert_array_equal(image, expected_image) 

323 

324 server.close() 

325 

326 

327def test_RGB_tile_of_full_resolution_with_base64(): 

328 level = 0 

329 width = 5 

330 height = 6 

331 x = 10 

332 y = 20 

333 expected_image = np.array(sample_RGB_pixels[level], dtype=sample_RGB_metadata.dtype)[:, y:y+height, x:x+width] 

334 gateway = _create_gateway(sample_RGB_metadata, sample_RGB_pixels) 

335 qupath_server = _create_qupath_server(sample_RGB_metadata) 

336 server = QuPathServer(gateway, qupath_server, 'base_64') 

337 

338 image = server.read_region( 

339 sample_RGB_metadata.downsamples[level], 

340 Region2D(x, y, width, height) 

341 ) 

342 

343 np.testing.assert_array_equal(image, expected_image) 

344 

345 server.close() 

346 

347 

348def test_float32_metadata(): 

349 metadata = sample_float32_metadata 

350 gateway = _create_gateway(metadata, sample_float32_pixels) 

351 qupath_server = _create_qupath_server(metadata) 

352 server = QuPathServer(gateway, qupath_server) 

353 

354 metadata = server.metadata 

355 

356 assert metadata == metadata 

357 

358 server.close() 

359 

360 

361def test_float32_full_resolution_with_temp_file(): 

362 level = 0 

363 t = int(sample_float32_metadata.n_timepoints / 2) 

364 z = int(sample_float32_metadata.n_z_slices / 2) 

365 expected_image = np.array(sample_float32_pixels[level], dtype=sample_float32_metadata.dtype)[t, :, z, ...] 

366 gateway = _create_gateway(sample_float32_metadata, sample_float32_pixels) 

367 qupath_server = _create_qupath_server(sample_float32_metadata) 

368 server = QuPathServer(gateway, qupath_server, 'temp_files') 

369 

370 image = server.read_region( 

371 sample_float32_metadata.downsamples[level], 

372 Region2D(width=sample_float32_metadata.width, height=sample_float32_metadata.height, z=z, t=t) 

373 ) 

374 

375 np.testing.assert_array_equal(image, expected_image) 

376 

377 server.close() 

378 

379 

380def test_float32_lowest_resolution_with_temp_file(): 

381 level = sample_float32_metadata.n_resolutions - 1 

382 t = int(sample_float32_metadata.n_timepoints / 2) 

383 z = int(sample_float32_metadata.n_z_slices / 2) 

384 expected_image = np.array(sample_float32_pixels[level], dtype=sample_float32_metadata.dtype)[t, :, z, ...] 

385 gateway = _create_gateway(sample_float32_metadata, sample_float32_pixels) 

386 qupath_server = _create_qupath_server(sample_float32_metadata) 

387 server = QuPathServer(gateway, qupath_server, 'temp_files') 

388 

389 image = server.read_region( 

390 sample_float32_metadata.downsamples[level], 

391 Region2D(width=sample_float32_metadata.width, height=sample_float32_metadata.height, z=z, t=t) 

392 ) 

393 

394 np.testing.assert_array_equal(image, expected_image) 

395 

396 server.close() 

397 

398 

399def test_float32_tile_of_full_resolution_with_temp_file(): 

400 level = 0 

401 width = 5 

402 height = 6 

403 x = 10 

404 y = 20 

405 t = int(sample_float32_metadata.n_timepoints / 2) 

406 z = int(sample_float32_metadata.n_z_slices / 2) 

407 expected_image = np.array(sample_float32_pixels[level], dtype=sample_float32_metadata.dtype)[t, :, z, y:y+height, x:x+width] 

408 gateway = _create_gateway(sample_float32_metadata, sample_float32_pixels) 

409 qupath_server = _create_qupath_server(sample_float32_metadata) 

410 server = QuPathServer(gateway, qupath_server, 'temp_files') 

411 

412 image = server.read_region( 

413 sample_float32_metadata.downsamples[level], 

414 Region2D(x, y, width, height, z, t) 

415 ) 

416 

417 np.testing.assert_array_equal(image, expected_image) 

418 

419 server.close() 

420 

421 

422def test_float32_full_resolution_with_bytes(): 

423 level = 0 

424 t = int(sample_float32_metadata.n_timepoints / 2) 

425 z = int(sample_float32_metadata.n_z_slices / 2) 

426 expected_image = np.array(sample_float32_pixels[level], dtype=sample_float32_metadata.dtype)[t, :, z, ...] 

427 gateway = _create_gateway(sample_float32_metadata, sample_float32_pixels) 

428 qupath_server = _create_qupath_server(sample_float32_metadata) 

429 server = QuPathServer(gateway, qupath_server, 'bytes') 

430 

431 image = server.read_region( 

432 sample_float32_metadata.downsamples[level], 

433 Region2D(width=sample_float32_metadata.width, height=sample_float32_metadata.height, z=z, t=t) 

434 ) 

435 

436 np.testing.assert_array_equal(image, expected_image) 

437 

438 server.close() 

439 

440 

441def test_float32_lowest_resolution_with_bytes(): 

442 level = sample_float32_metadata.n_resolutions - 1 

443 t = int(sample_float32_metadata.n_timepoints / 2) 

444 z = int(sample_float32_metadata.n_z_slices / 2) 

445 expected_image = np.array(sample_float32_pixels[level], dtype=sample_float32_metadata.dtype)[t, :, z, ...] 

446 gateway = _create_gateway(sample_float32_metadata, sample_float32_pixels) 

447 qupath_server = _create_qupath_server(sample_float32_metadata) 

448 server = QuPathServer(gateway, qupath_server, 'bytes') 

449 

450 image = server.read_region( 

451 sample_float32_metadata.downsamples[level], 

452 Region2D(width=sample_float32_metadata.width, height=sample_float32_metadata.height, z=z, t=t) 

453 ) 

454 

455 np.testing.assert_array_equal(image, expected_image) 

456 

457 server.close() 

458 

459 

460def test_float32_tile_of_full_resolution_with_bytes(): 

461 level = 0 

462 width = 5 

463 height = 6 

464 x = 10 

465 y = 20 

466 t = int(sample_float32_metadata.n_timepoints / 2) 

467 z = int(sample_float32_metadata.n_z_slices / 2) 

468 expected_image = np.array(sample_float32_pixels[level], dtype=sample_float32_metadata.dtype)[t, :, z, y:y+height, x:x+width] 

469 gateway = _create_gateway(sample_float32_metadata, sample_float32_pixels) 

470 qupath_server = _create_qupath_server(sample_float32_metadata) 

471 server = QuPathServer(gateway, qupath_server, 'bytes') 

472 

473 image = server.read_region( 

474 sample_float32_metadata.downsamples[level], 

475 Region2D(x, y, width, height, z, t) 

476 ) 

477 

478 np.testing.assert_array_equal(image, expected_image) 

479 

480 server.close() 

481 

482 

483def test_float32_full_resolution_with_base64(): 

484 level = 0 

485 t = int(sample_float32_metadata.n_timepoints / 2) 

486 z = int(sample_float32_metadata.n_z_slices / 2) 

487 expected_image = np.array(sample_float32_pixels[level], dtype=sample_float32_metadata.dtype)[t, :, z, ...] 

488 gateway = _create_gateway(sample_float32_metadata, sample_float32_pixels) 

489 qupath_server = _create_qupath_server(sample_float32_metadata) 

490 server = QuPathServer(gateway, qupath_server, 'base_64') 

491 

492 image = server.read_region( 

493 sample_float32_metadata.downsamples[level], 

494 Region2D(width=sample_float32_metadata.width, height=sample_float32_metadata.height, z=z, t=t) 

495 ) 

496 

497 np.testing.assert_array_equal(image, expected_image) 

498 

499 server.close() 

500 

501 

502def test_float32_lowest_resolution_with_base64(): 

503 level = sample_float32_metadata.n_resolutions - 1 

504 t = int(sample_float32_metadata.n_timepoints / 2) 

505 z = int(sample_float32_metadata.n_z_slices / 2) 

506 expected_image = np.array(sample_float32_pixels[level], dtype=sample_float32_metadata.dtype)[t, :, z, ...] 

507 gateway = _create_gateway(sample_float32_metadata, sample_float32_pixels) 

508 qupath_server = _create_qupath_server(sample_float32_metadata) 

509 server = QuPathServer(gateway, qupath_server, 'base_64') 

510 

511 image = server.read_region( 

512 sample_float32_metadata.downsamples[level], 

513 Region2D(width=sample_float32_metadata.width, height=sample_float32_metadata.height, z=z, t=t) 

514 ) 

515 

516 np.testing.assert_array_equal(image, expected_image) 

517 

518 server.close() 

519 

520 

521def test_float32_tile_of_full_resolution_with_base64(): 

522 level = 0 

523 width = 5 

524 height = 6 

525 x = 10 

526 y = 20 

527 t = int(sample_float32_metadata.n_timepoints / 2) 

528 z = int(sample_float32_metadata.n_z_slices / 2) 

529 expected_image = np.array(sample_float32_pixels[level], dtype=sample_float32_metadata.dtype)[t, :, z, y:y+height, x:x+width] 

530 gateway = _create_gateway(sample_float32_metadata, sample_float32_pixels) 

531 qupath_server = _create_qupath_server(sample_float32_metadata) 

532 server = QuPathServer(gateway, qupath_server, 'base_64') 

533 

534 image = server.read_region( 

535 sample_float32_metadata.downsamples[level], 

536 Region2D(x, y, width, height, z, t) 

537 ) 

538 

539 np.testing.assert_array_equal(image, expected_image) 

540 

541 server.close()