comparison Plugins/WSI/Run.py @ 573:31ab8bb2ac5a

merge
author Alain Mazy <am@osimis.io>
date Thu, 20 Jul 2023 10:57:39 +0200
parents e6cee85fe421
children 8aa101e126d0
comparison
equal deleted inserted replaced
572:3a5260cc6d55 573:31ab8bb2ac5a
54 help = 'Username to the REST API') 54 help = 'Username to the REST API')
55 parser.add_argument('--password', 55 parser.add_argument('--password',
56 default = 'orthanctest', 56 default = 'orthanctest',
57 help = 'Password to the REST API') 57 help = 'Password to the REST API')
58 parser.add_argument('--dicomizer', 58 parser.add_argument('--dicomizer',
59 default = '/home/jodogne/Subversion/orthanc-wsi/Applications/i/OrthancWSIDicomizer', 59 default = os.path.join(os.environ['HOME'], 'Subversion/orthanc-wsi/Applications/i/OrthancWSIDicomizer'),
60 help = 'Password to the REST API') 60 help = 'Password to the REST API')
61 parser.add_argument('--to-tiff', 61 parser.add_argument('--to-tiff',
62 default = '/home/jodogne/Subversion/orthanc-wsi/Applications/i/OrthancWSIDicomToTiff', 62 default = os.path.join(os.environ['HOME'], 'Subversion/orthanc-wsi/Applications/i/OrthancWSIDicomToTiff'),
63 help = 'Password to the REST API') 63 help = 'Password to the REST API')
64 parser.add_argument('--valgrind', help = 'Use valgrind while running the DICOM-izer', 64 parser.add_argument('--valgrind', help = 'Use valgrind while running the DICOM-izer',
65 action = 'store_true') 65 action = 'store_true')
66 parser.add_argument('--force', help = 'Do not warn the user', 66 parser.add_argument('--force', help = 'Do not warn the user',
67 action = 'store_true') 67 action = 'store_true')
98 if args.valgrind: 98 if args.valgrind:
99 prefix = [ 'valgrind' ] 99 prefix = [ 'valgrind' ]
100 100
101 log = subprocess.check_output(prefix + command, 101 log = subprocess.check_output(prefix + command,
102 stderr=subprocess.STDOUT) 102 stderr=subprocess.STDOUT)
103 103
104 if sys.version_info >= (3, 0):
105 log = log.decode('ascii')
106
104 # If using valgrind, only print the lines from the log starting 107 # If using valgrind, only print the lines from the log starting
105 # with '==' (they contain the report from valgrind) 108 # with '==' (they contain the report from valgrind)
106 if args.valgrind: 109 if args.valgrind:
107 print('\n'.join(filter(lambda x: x.startswith('=='), log.splitlines()))) 110 print('\n'.join(filter(lambda x: x.startswith('=='), log.splitlines())))
108 111
129 try: 132 try:
130 tiff = subprocess.check_output([ 'tiffinfo', temp.name ]) 133 tiff = subprocess.check_output([ 'tiffinfo', temp.name ])
131 except: 134 except:
132 print('\ntiffinfo is probably not installed => sudo apt-get install libtiff-tools\n') 135 print('\ntiffinfo is probably not installed => sudo apt-get install libtiff-tools\n')
133 tiff = None 136 tiff = None
137
138 if (tiff != None and sys.version_info >= (3, 0)):
139 tiff = tiff.decode('ascii')
134 140
135 os.unlink(temp.name) 141 os.unlink(temp.name)
136 142
137 return tiff 143 return tiff
138 144
178 self.assertEqual(512, pyramid['TotalHeight']) 184 self.assertEqual(512, pyramid['TotalHeight'])
179 self.assertEqual(1, pyramid['TilesCount'][0][0]) 185 self.assertEqual(1, pyramid['TilesCount'][0][0])
180 self.assertEqual(1, pyramid['TilesCount'][0][1]) 186 self.assertEqual(1, pyramid['TilesCount'][0][1])
181 187
182 tiff = CallTiffInfoOnSeries(s[0]) 188 tiff = CallTiffInfoOnSeries(s[0])
183 p = filter(lambda x: 'Photometric Interpretation' in x, tiff.splitlines()) 189 p = list(filter(lambda x: 'Photometric Interpretation' in x, tiff.splitlines()))
184 self.assertEqual(1, len(p)) 190 self.assertEqual(1, len(p))
185 self.assertTrue('YCbCr' in p[0]) 191 self.assertTrue('YCbCr' in p[0])
186 192
187 193
188 def test_grayscale_pyramid(self): 194 def test_grayscale_pyramid(self):
233 self.assertEqual(2, pyramid['TilesCount'][2][1]) 239 self.assertEqual(2, pyramid['TilesCount'][2][1])
234 self.assertEqual(1, pyramid['TilesCount'][3][0]) 240 self.assertEqual(1, pyramid['TilesCount'][3][0])
235 self.assertEqual(1, pyramid['TilesCount'][3][1]) 241 self.assertEqual(1, pyramid['TilesCount'][3][1])
236 242
237 tiff = CallTiffInfoOnSeries(s[0]) 243 tiff = CallTiffInfoOnSeries(s[0])
238 p = filter(lambda x: 'Photometric Interpretation' in x, tiff.splitlines()) 244 p = list(filter(lambda x: 'Photometric Interpretation' in x, tiff.splitlines()))
239 self.assertEqual(4, len(p)) 245 self.assertEqual(4, len(p))
240 for j in range(4): 246 for j in range(4):
241 self.assertTrue('min-is-black' in p[j]) 247 self.assertTrue('min-is-black' in p[j])
242 248
243 249
249 255
250 pyramid = DoGet(ORTHANC, '/wsi/pyramids/%s' % s[0]) 256 pyramid = DoGet(ORTHANC, '/wsi/pyramids/%s' % s[0])
251 self.assertEqual(4, len(pyramid['Resolutions'])) 257 self.assertEqual(4, len(pyramid['Resolutions']))
252 258
253 tiff = CallTiffInfoOnSeries(s[0]) 259 tiff = CallTiffInfoOnSeries(s[0])
254 p = filter(lambda x: 'Photometric Interpretation' in x, tiff.splitlines()) 260 p = list(filter(lambda x: 'Photometric Interpretation' in x, tiff.splitlines()))
255 self.assertEqual(4, len(p)) 261 self.assertEqual(4, len(p))
256 for j in range(4): 262 for j in range(4):
257 self.assertTrue('min-is-black' in p[j]) 263 self.assertTrue('min-is-black' in p[j])
258 264
259 265
265 271
266 pyramid = DoGet(ORTHANC, '/wsi/pyramids/%s' % s[0]) 272 pyramid = DoGet(ORTHANC, '/wsi/pyramids/%s' % s[0])
267 self.assertEqual(4, len(pyramid['Resolutions'])) 273 self.assertEqual(4, len(pyramid['Resolutions']))
268 274
269 tiff = CallTiffInfoOnSeries(s[0]) 275 tiff = CallTiffInfoOnSeries(s[0])
270 p = filter(lambda x: 'Photometric Interpretation' in x, tiff.splitlines()) 276 p = list(filter(lambda x: 'Photometric Interpretation' in x, tiff.splitlines()))
271 self.assertEqual(4, len(p)) 277 self.assertEqual(4, len(p))
272 for j in range(4): 278 for j in range(4):
273 self.assertTrue('YCbCr' in p[j]) 279 self.assertTrue('YCbCr' in p[j])
274 280
275 281
281 287
282 pyramid = DoGet(ORTHANC, '/wsi/pyramids/%s' % s[0]) 288 pyramid = DoGet(ORTHANC, '/wsi/pyramids/%s' % s[0])
283 self.assertEqual(4, len(pyramid['Resolutions'])) 289 self.assertEqual(4, len(pyramid['Resolutions']))
284 290
285 tiff = CallTiffInfoOnSeries(s[0]) 291 tiff = CallTiffInfoOnSeries(s[0])
286 p = filter(lambda x: 'Photometric Interpretation' in x, tiff.splitlines()) 292 p = list(filter(lambda x: 'Photometric Interpretation' in x, tiff.splitlines()))
287 self.assertEqual(4, len(p)) 293 self.assertEqual(4, len(p))
288 for j in range(4): 294 for j in range(4):
289 self.assertTrue('RGB' in p[j]) 295 self.assertTrue('RGB' in p[j])
290 296
291 297
359 s = spacings[str(4 ** i)].split('\\') 365 s = spacings[str(4 ** i)].split('\\')
360 self.assertEqual(2, len(s)) 366 self.assertEqual(2, len(s))
361 self.assertEqual(20.0 / 512.0 * (2.0 ** (3 - i)), float(s[0])) 367 self.assertEqual(20.0 / 512.0 * (2.0 ** (3 - i)), float(s[0]))
362 self.assertEqual(10.0 / 512.0 * (2.0 ** (3 - i)), float(s[1])) 368 self.assertEqual(10.0 / 512.0 * (2.0 ** (3 - i)), float(s[1]))
363 369
370
371 def test_http_accept(self):
372 # https://discourse.orthanc-server.org/t/orthanc-wsi-image-quality-issue/3331
373
374 def TestTransferSyntax(s, expected):
375 instance = DoGet(ORTHANC, '/series/%s' % s[0]) ['Instances'][0]
376 self.assertEqual(expected, DoGet(ORTHANC, '/instances/%s/metadata/TransferSyntax' % instance))
364 377
378 def TestDefaultAccept(s, mime):
379 tile = GetImage(ORTHANC, '/wsi/tiles/%s/0/0/0' % s[0])
380 self.assertEqual(mime, tile.format)
381
382 tile = GetImage(ORTHANC, '/wsi/tiles/%s/0/0/0' % s[0], {
383 'Accept' : 'text/html,*/*'
384 })
385 self.assertEqual(mime, tile.format)
386
387 tile = GetImage(ORTHANC, '/wsi/tiles/%s/0/0/0' % s[0], {
388 'Accept' : 'image/*,text/html'
389 })
390 self.assertEqual(mime, tile.format)
391
392 tile = DoGetRaw(ORTHANC, '/wsi/tiles/%s/0/0/0' % s[0], headers = {
393 'Accept' : 'text/html'
394 })
395 self.assertEqual(406, int(tile[0]['status']))
396
397 def TestForceAccept(s):
398 tile = GetImage(ORTHANC, '/wsi/tiles/%s/0/0/0' % s[0], {
399 'Accept' : 'image/jpeg'
400 })
401 self.assertEqual('JPEG', tile.format)
402
403 tile = GetImage(ORTHANC, '/wsi/tiles/%s/0/0/0' % s[0], {
404 'Accept' : 'image/png'
405 })
406 self.assertEqual('PNG', tile.format)
407
408 tile = GetImage(ORTHANC, '/wsi/tiles/%s/0/0/0' % s[0], {
409 'Accept' : 'image/jp2'
410 })
411 self.assertEqual('JPEG2000', tile.format)
412
413
414 CallDicomizer([ GetDatabasePath('Lena.jpg') ])
415
416 s = DoGet(ORTHANC, '/series')
417 self.assertEqual(1, len(s))
418 TestTransferSyntax(s, '1.2.840.10008.1.2.4.50')
419 TestDefaultAccept(s, 'JPEG')
420 TestForceAccept(s)
421
422 DoDelete(ORTHANC, '/series/%s' % s[0])
423
424 CallDicomizer([ GetDatabasePath('Lena.jpg'), '--compression', 'none' ])
425 s = DoGet(ORTHANC, '/series')
426 self.assertEqual(1, len(s))
427
428 TestTransferSyntax(s, '1.2.840.10008.1.2')
429 TestDefaultAccept(s, 'PNG')
430 TestForceAccept(s)
431
432 DoDelete(ORTHANC, '/series/%s' % s[0])
433
434 CallDicomizer([ GetDatabasePath('Lena.jpg'), '--compression', 'jpeg2000' ])
435 s = DoGet(ORTHANC, '/series')
436 self.assertEqual(1, len(s))
437
438 TestTransferSyntax(s, '1.2.840.10008.1.2.4.90')
439 TestDefaultAccept(s, 'PNG')
440 TestForceAccept(s)
441
442 def test_iiif(self):
443 CallDicomizer([ GetDatabasePath('LenaGrayscale.png'), # Image is 512x512
444 '--levels=3', '--tile-width=128', '--tile-height=128' ])
445
446 self.assertEqual(3, len(DoGet(ORTHANC, '/instances')))
447
448 s = DoGet(ORTHANC, '/series')
449 self.assertEqual(1, len(s))
450
451 uri = '/wsi/iiif/tiles/%s' % s[0]
452 info = DoGet(ORTHANC, '%s/info.json' % uri)
453 self.assertEqual('http://iiif.io/api/image/3/context.json', info['@context'])
454 self.assertEqual('http://iiif.io/api/image', info['protocol'])
455 self.assertEqual('http://localhost:8042%s' % uri, info['id'])
456 self.assertEqual('level0', info['profile'])
457 self.assertEqual('ImageService3', info['type'])
458 self.assertEqual(512, info['width'])
459 self.assertEqual(512, info['height'])
460
461 self.assertEqual(3, len(info['sizes']))
462 self.assertEqual(512, info['sizes'][0]['width'])
463 self.assertEqual(512, info['sizes'][0]['height'])
464 self.assertEqual(256, info['sizes'][1]['width'])
465 self.assertEqual(256, info['sizes'][1]['height'])
466 self.assertEqual(128, info['sizes'][2]['width'])
467 self.assertEqual(128, info['sizes'][2]['height'])
468
469 self.assertEqual(1, len(info['tiles']))
470 self.assertEqual(128, info['tiles'][0]['width'])
471 self.assertEqual(128, info['tiles'][0]['height'])
472 self.assertEqual([ 1, 2, 4 ], info['tiles'][0]['scaleFactors'])
473
474 # The list of URIs below was generated by "orthanc-wsi/Resources/TestIIIFTiles.py"
475
476 # Level 0
477 GetImage(ORTHANC, '/%s/0,0,128,128/128,128/0/default.jpg' % uri)
478 GetImage(ORTHANC, '/%s/128,0,128,128/128,128/0/default.jpg' % uri)
479 GetImage(ORTHANC, '/%s/256,0,128,128/128,128/0/default.jpg' % uri)
480 GetImage(ORTHANC, '/%s/384,0,128,128/128,128/0/default.jpg' % uri)
481 GetImage(ORTHANC, '/%s/0,128,128,128/128,128/0/default.jpg' % uri)
482 GetImage(ORTHANC, '/%s/128,128,128,128/128,128/0/default.jpg' % uri)
483 GetImage(ORTHANC, '/%s/256,128,128,128/128,128/0/default.jpg' % uri)
484 GetImage(ORTHANC, '/%s/384,128,128,128/128,128/0/default.jpg' % uri)
485 GetImage(ORTHANC, '/%s/0,256,128,128/128,128/0/default.jpg' % uri)
486 GetImage(ORTHANC, '/%s/128,256,128,128/128,128/0/default.jpg' % uri)
487 GetImage(ORTHANC, '/%s/256,256,128,128/128,128/0/default.jpg' % uri)
488 GetImage(ORTHANC, '/%s/384,256,128,128/128,128/0/default.jpg' % uri)
489 GetImage(ORTHANC, '/%s/0,384,128,128/128,128/0/default.jpg' % uri)
490 GetImage(ORTHANC, '/%s/128,384,128,128/128,128/0/default.jpg' % uri)
491 GetImage(ORTHANC, '/%s/256,384,128,128/128,128/0/default.jpg' % uri)
492 GetImage(ORTHANC, '/%s/384,384,128,128/128,128/0/default.jpg' % uri)
493
494 # Level 1
495 GetImage(ORTHANC, '/%s/0,0,256,256/128,128/0/default.jpg' % uri)
496 GetImage(ORTHANC, '/%s/256,0,256,256/128,128/0/default.jpg' % uri)
497 GetImage(ORTHANC, '/%s/0,256,256,256/128,128/0/default.jpg' % uri)
498 GetImage(ORTHANC, '/%s/256,256,256,256/128,128/0/default.jpg' % uri)
499
500 # Level 2
501 i = GetImage(ORTHANC, '/%s/0,0,512,512/128,128/0/default.jpg' % uri)
502 self.assertEqual(128, i.width)
503 self.assertEqual(128, i.height)
504
505 uri2 = '/wsi/iiif/series/%s/manifest.json' % s[0]
506 manifest = DoGet(ORTHANC, uri2)
507 self.assertEqual('http://iiif.io/api/presentation/3/context.json', manifest['@context'])
508 self.assertEqual('http://localhost:8042%s' % uri2, manifest['id'])
509
510 self.assertEqual(1, len(manifest['items']))
511 self.assertEqual(1, len(manifest['items'][0]['items']))
512 self.assertEqual(1, len(manifest['items'][0]['items'][0]['items']))
513
514 self.assertEqual('Manifest', manifest['type'])
515 self.assertEqual('Canvas', manifest['items'][0]['type'])
516 self.assertEqual('AnnotationPage', manifest['items'][0]['items'][0]['type'])
517 self.assertEqual('Annotation', manifest['items'][0]['items'][0]['items'][0]['type'])
518
519 self.assertEqual(512, manifest['items'][0]['width'])
520 self.assertEqual(512, manifest['items'][0]['height'])
521
522 body = manifest['items'][0]['items'][0]['items'][0]['body']
523 self.assertEqual(1, len(body['service']))
524 self.assertEqual('image/jpeg', body['format'])
525 self.assertEqual('Image', body['type'])
526 self.assertEqual(512, body['width'])
527 self.assertEqual(512, body['height'])
528 self.assertEqual('level0', body['service'][0]['profile'])
529 self.assertEqual('ImageService3', body['service'][0]['type'])
530 self.assertEqual('http://localhost:8042%s' % uri, body['service'][0]['id'])
531
532 def test_iiif_radiology(self):
533 a = UploadInstance(ORTHANC, 'ColorTestMalaterre.dcm') ['ID']
534 b = UploadInstance(ORTHANC, 'Multiframe.dcm') ['ID']
535 c = UploadInstance(ORTHANC, 'Brainix/Epi/IM-0001-0001.dcm') ['ID']
536 d = UploadInstance(ORTHANC, 'Brainix/Epi/IM-0001-0002.dcm') ['ID']
537
538 s1 = DoGet(ORTHANC, '/instances/%s/series' % a) ['ID']
539 s2 = DoGet(ORTHANC, '/instances/%s/series' % b) ['ID']
540 s3 = DoGet(ORTHANC, '/instances/%s/series' % c) ['ID']
541
542 manifest = DoGet(ORTHANC, '/wsi/iiif/series/%s/manifest.json' % s1)
543 self.assertEqual(1, len(manifest['items']))
544
545 manifest = DoGet(ORTHANC, '/wsi/iiif/series/%s/manifest.json' % s2)
546 self.assertEqual(76, len(manifest['items']))
547
548 manifest = DoGet(ORTHANC, '/wsi/iiif/series/%s/manifest.json' % s3)
549 self.assertEqual(2, len(manifest['items']))
550
551 for (i, width, height) in [ (a, 41, 41),
552 (b, 512, 512),
553 (c, 256, 256),
554 (d, 256, 256) ]:
555 uri = '/wsi/iiif/frames/%s/0' % i
556 info = DoGet(ORTHANC, uri + '/info.json')
557 self.assertEqual(8, len(info))
558 self.assertEqual('http://iiif.io/api/image/3/context.json', info['@context'])
559 self.assertEqual('http://iiif.io/api/image', info['protocol'])
560 self.assertEqual('http://localhost:8042%s' % uri, info['id'])
561 self.assertEqual('level0', info['profile'])
562 self.assertEqual('ImageService3', info['type'])
563 self.assertEqual(width, info['width'])
564 self.assertEqual(height, info['height'])
565 self.assertEqual(1, len(info['tiles']))
566 self.assertEqual(3, len(info['tiles'][0]))
567 self.assertEqual(width, info['tiles'][0]['width'])
568 self.assertEqual(height, info['tiles'][0]['height'])
569 self.assertEqual([ 1 ], info['tiles'][0]['scaleFactors'])
570
365 try: 571 try:
366 print('\nStarting the tests...') 572 print('\nStarting the tests...')
367 unittest.main(argv = [ sys.argv[0] ] + args.options) 573 unittest.main(argv = [ sys.argv[0] ] + args.options)
368 574
369 finally: 575 finally: