# HG changeset patch # User Sebastien Jodogne # Date 1434975277 -7200 # Node ID 2a29bcff60a721fb72cc8afefefc6a67a78a4be9 # Parent f3d08a75a63613498eeaea9f83ff153e795e9bb6 tests of image decoding diff -r f3d08a75a636 -r 2a29bcff60a7 Database/Formats/Brainix.png Binary file Database/Formats/Brainix.png has changed diff -r f3d08a75a636 -r 2a29bcff60a7 Database/Formats/Generate.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Database/Formats/Generate.sh Mon Jun 22 14:14:37 2015 +0200 @@ -0,0 +1,37 @@ +#!/bin/bash + +set -e + +# http://gdcm.sourceforge.net/html/gdcmconv.html + +gdcmconv -i ../Brainix/Epi/IM-0001-0001.dcm -o JpegLossless.dcm -L +gdcmconv -i ../Brainix/Epi/IM-0001-0001.dcm -o Jpeg.dcm -J +gdcmconv -i ../Brainix/Epi/IM-0001-0001.dcm -o Rle.dcm -R + +# Generate study/series/sop instance UID++++ +dcmodify -e '(0008,0005)' -m '(0010,0020)=FromGDCM' -gin -gst -gse JpegLossless.dcm +dcmodify -e '(0008,0005)' -m '(0010,0020)=FromGDCM' -gin -gst -gse Jpeg.dcm +dcmodify -e '(0008,0005)' -m '(0010,0020)=FromGDCM' -gin -gst -gse Rle.dcm + +rm -f JpegLossless.dcm.bak Jpeg.dcm.bak Rle.dcm.bak + +gdcmraw -t PixelData ../Brainix/Epi/IM-0001-0001.dcm PixelData.raw +convert -define png:include-chunks=none -define png:compression-level=9 -size 256x256 -depth 16 gray:PixelData.raw Brainix.png + +gdcmraw -t PixelData ../KarstenHilbertRF.dcm PixelData.raw +convert -define png:include-chunks=none -define png:compression-level=9 -size 512x464 -depth 8 gray:PixelData.raw KarstenHilbertRF.png + +# Decompress the multiframe image +gdcmconv -w ../Multiframe.dcm tmp.dcm +gdcmraw -t PixelData ./tmp.dcm PixelData.raw +SIZE=$((512*512)) +dd if=PixelData.raw of=PixelData2.raw bs=$SIZE count=1 skip=0 &> /dev/null +convert -define png:include-chunks=none -define png:compression-level=9 -size 512x512 -depth 8 gray:PixelData2.raw Multiframe0.png +dd if=PixelData.raw of=PixelData2.raw bs=$SIZE count=1 skip=75 &> /dev/null +convert -define png:include-chunks=none -define png:compression-level=9 -size 512x512 -depth 8 gray:PixelData2.raw Multiframe75.png + +# Decompress the signed CT image, ignoring the fact that the data is signed +gdcmraw -t PixelData ../SignedCT.dcm PixelData.raw +convert -define png:include-chunks=none -define png:compression-level=9 -size 512x512 -depth 16 gray:PixelData.raw SignedCT.png + +rm -f PixelData.raw PixelData2.raw tmp.dcm diff -r f3d08a75a636 -r 2a29bcff60a7 Database/Formats/Jpeg.dcm Binary file Database/Formats/Jpeg.dcm has changed diff -r f3d08a75a636 -r 2a29bcff60a7 Database/Formats/JpegLossless.dcm Binary file Database/Formats/JpegLossless.dcm has changed diff -r f3d08a75a636 -r 2a29bcff60a7 Database/Formats/KarstenHilbertRF.png Binary file Database/Formats/KarstenHilbertRF.png has changed diff -r f3d08a75a636 -r 2a29bcff60a7 Database/Formats/Multiframe0.png Binary file Database/Formats/Multiframe0.png has changed diff -r f3d08a75a636 -r 2a29bcff60a7 Database/Formats/Multiframe75.png Binary file Database/Formats/Multiframe75.png has changed diff -r f3d08a75a636 -r 2a29bcff60a7 Database/Formats/Rle.dcm Binary file Database/Formats/Rle.dcm has changed diff -r f3d08a75a636 -r 2a29bcff60a7 Database/Formats/SignedCT.png Binary file Database/Formats/SignedCT.png has changed diff -r f3d08a75a636 -r 2a29bcff60a7 Database/Lua/TransferSyntaxDisable.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Database/Lua/TransferSyntaxDisable.lua Mon Jun 22 14:14:37 2015 +0200 @@ -0,0 +1,29 @@ +function IsDeflatedTransferSyntaxAccepted(aet, ip) + return false +end + +function IsJpegTransferSyntaxAccepted(aet, ip) + return false +end + +function IsJpeg2000TransferSyntaxAccepted(aet, ip) + return false +end + +function IsJpegLosslessTransferSyntaxAccepted(aet, ip) + return false +end + +function IsJpipTransferSyntaxAccepted(aet, ip) + return false +end + +function IsMpeg2TransferSyntaxAccepted(aet, ip) + return false +end + +function IsRleTransferSyntaxAccepted(aet, ip) + return false +end + +print('All special transfer syntaxes are now disallowed') diff -r f3d08a75a636 -r 2a29bcff60a7 Database/Lua/TransferSyntaxEnable.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Database/Lua/TransferSyntaxEnable.lua Mon Jun 22 14:14:37 2015 +0200 @@ -0,0 +1,29 @@ +function IsDeflatedTransferSyntaxAccepted(aet, ip) + return true +end + +function IsJpegTransferSyntaxAccepted(aet, ip) + return true +end + +function IsJpeg2000TransferSyntaxAccepted(aet, ip) + return true +end + +function IsJpegLosslessTransferSyntaxAccepted(aet, ip) + return true +end + +function IsJpipTransferSyntaxAccepted(aet, ip) + return true +end + +function IsMpeg2TransferSyntaxAccepted(aet, ip) + return true +end + +function IsRleTransferSyntaxAccepted(aet, ip) + return true +end + +print('All special transfer syntaxes are now accepted') diff -r f3d08a75a636 -r 2a29bcff60a7 Database/README.txt --- a/Database/README.txt Mon Jun 22 09:47:25 2015 +0200 +++ b/Database/README.txt Mon Jun 22 14:14:37 2015 +0200 @@ -56,3 +56,4 @@ - PilatesArgenturGEUltrasoundOsiriX.dcm: From https://groups.google.com/d/msg/orthanc-users/m3zQLyl_jNc/TUrR462UKSMJ - PrivateMDNTags.dcm : From University Hospital of Liege - PrivateTags.dcm : From GDCM, "images_of_interest/494APG9K.dcm" +- SignedCT.dcm : From Sébastien Jodogne. diff -r f3d08a75a636 -r 2a29bcff60a7 Database/SignedCT.dcm Binary file Database/SignedCT.dcm has changed diff -r f3d08a75a636 -r 2a29bcff60a7 GenerateConfigurationForTests.py --- a/GenerateConfigurationForTests.py Mon Jun 22 09:47:25 2015 +0200 +++ b/GenerateConfigurationForTests.py Mon Jun 22 14:14:37 2015 +0200 @@ -74,6 +74,7 @@ config = re.sub(r'("DicomAet"\s*:)\s*".*?"', r'\1 "ORTHANC"', config) config = re.sub(r'("RemoteAccessAllowed"\s*:)\s*false', r'\1 true', config) config = re.sub(r'("AuthenticationEnabled"\s*:)\s*false', r'\1 true', config) +config = re.sub(r'("DicomAssociationCloseDelay"\s*:)\s*[0-9]*', r'\1 0', config) config = re.sub(r'("RegisteredUsers"\s*:)\s*{', r'\1 { "alice" : "orthanctest"', config) config = re.sub(r'("DicomModalities"\s*:)\s*{', r'\1 { "orthanctest" : [ "%s", "%s", %d ]' % ('ORTHANCTEST', ip, 5001), config) diff -r f3d08a75a636 -r 2a29bcff60a7 Tests/Run.py --- a/Tests/Run.py Mon Jun 22 09:47:25 2015 +0200 +++ b/Tests/Run.py Mon Jun 22 14:14:37 2015 +0200 @@ -84,7 +84,7 @@ if args.docker: args.server = GetDockerHostAddress() -CONFIG = '/tmp/Configuration.json' +CONFIG = '/tmp/IntegrationTestsConfiguration.json' subprocess.check_call([ 'Orthanc', '--config=%s' % CONFIG ]) with open(CONFIG, 'rt') as f: @@ -98,6 +98,7 @@ config = re.sub(r'("RemoteAccessAllowed"\s*:)\s*false', r'\1 true', config) config = re.sub(r'("AuthenticationEnabled"\s*:)\s*false', r'\1 true', config) config = re.sub(r'("RegisteredUsers"\s*:)\s*{', r'\1 { "alice" : "orthanctest"', config) +config = re.sub(r'("DicomAssociationCloseDelay"\s*:)\s*[0-9]*', r'\1 0', config) config = re.sub(r'("DicomModalities"\s*:)\s*{', r'\1 { "orthanc" : [ "%s", "%s", "%s" ]' % (args.aet, args.server, args.dicom), config) diff -r f3d08a75a636 -r 2a29bcff60a7 Tests/Tests.py --- a/Tests/Tests.py Mon Jun 22 09:47:25 2015 +0200 +++ b/Tests/Tests.py Mon Jun 22 14:14:37 2015 +0200 @@ -59,11 +59,31 @@ DoPost(_REMOTE, '/tools/execute-script', 'function OnStoredInstance() end', 'application/lua') +def CompareLists(a, b): + if len(a) != len(b): + return False + + for i in range(len(a)): + d = a[i] - b[i] + if abs(d) >= 0.51: # Add some tolerance for rounding errors + return False + + return True + + + + class Orthanc(unittest.TestCase): def setUp(self): DropOrthanc(_LOCAL) DropOrthanc(_REMOTE) + #print "In test", self._testMethodName + + def AssertSameImages(self, truth, url): + im = GetImage(_REMOTE, url) + self.assertTrue(CompareLists(truth, im.getdata())) + def test_system(self): self.assertTrue('Version' in DoGet(_REMOTE, '/system')) @@ -243,7 +263,7 @@ self.assertEqual('0', DoGet(_REMOTE, '/statistics')['TotalUncompressedSize']) - def test_multi_frame(self): + def test_multiframe(self): i = UploadInstance(_REMOTE, 'Multiframe.dcm')['ID'] self.assertEqual(76, len(DoGet(_REMOTE, '/instances/%s/frames' % i))) @@ -1322,14 +1342,11 @@ def test_issue_19(self): - # This is an image with "YBR_FULL" photometric interpretation + # This is an image with "YBR_FULL" photometric interpretation, it is not supported by Orthanc # gdcmconv -i /home/jodogne/DICOM/GdcmDatabase/US_DataSet/HDI5000_US/3EAF5E01 -w -o Issue19.dcm a = UploadInstance(_REMOTE, 'Issue19.dcm')['ID'] - i = DoGet(_REMOTE, '/instances/941ad3c8-05d05b88-560459f9-0eae0e20-6cddd533/preview') - - # eb5d156c3594497f589158a6c6f3ca51 <=> Unsupported.png - self.assertEqual('eb5d156c3594497f589158a6c6f3ca51', ComputeMD5(i)) + self.assertRaises(Exception, lambda: DoGet(_REMOTE, '/instances/941ad3c8-05d05b88-560459f9-0eae0e20-6cddd533/preview')) def test_issue_37(self): @@ -1800,3 +1817,151 @@ self.assertEqual('hello', DoGet(_REMOTE, '/instances/%s/content/StudyDescription' % b).strip()) self.assertEqual('world', DoGet(_REMOTE, '/instances/%s/content/Modality' % b).strip()) + + def test_incoming_jpeg(self): + def storescu(): + with open(os.devnull, 'w') as FNULL: + subprocess.check_call([ 'storescu', '-xs', + _REMOTE['Server'], str(_REMOTE['DicomPort']), + GetDatabasePath('Knix/Loc/IM-0001-0001.dcm') ], + stderr = FNULL) + + self.assertEqual(0, len(DoGet(_REMOTE, '/patients'))) + InstallLuaScript('Lua/TransferSyntaxDisable.lua') + self.assertRaises(Exception, storescu) + self.assertEqual(0, len(DoGet(_REMOTE, '/patients'))) + InstallLuaScript('Lua/TransferSyntaxEnable.lua') + storescu() + self.assertEqual(1, len(DoGet(_REMOTE, '/patients'))) + + + def test_storescu_jpeg(self): + self.assertEqual(0, len(DoGet(_REMOTE, '/exports')['Exports'])) + + knixStudy = 'b9c08539-26f93bde-c81ab0d7-bffaf2cb-a4d0bdd0' + UploadInstance(_REMOTE, 'Knix/Loc/IM-0001-0001.dcm') + UploadInstance(_REMOTE, 'Knix/Loc/IM-0001-0002.dcm') + UploadInstance(_REMOTE, 'Knix/Loc/IM-0001-0003.dcm') + + a = UploadInstance(_REMOTE, 'Brainix/Flair/IM-0001-0001.dcm')['ID'] + b = UploadInstance(_REMOTE, 'ColorTestImageJ.dcm')['ID'] + self.assertEqual(0, len(DoGet(_LOCAL, '/instances'))) + DoPost(_REMOTE, '/modalities/orthanctest/store', [ knixStudy, a, b ]) + self.assertEqual(5, len(DoGet(_LOCAL, '/instances'))) + + self.assertEqual(3, len(DoGet(_REMOTE, '/exports')['Exports'])) + + DropOrthanc(_REMOTE) + self.assertEqual(0, len(DoGet(_REMOTE, '/exports')['Exports'])) + + + def test_pixel_data(self): + jpeg = UploadInstance(_REMOTE, 'Knix/Loc/IM-0001-0001.dcm')['ID'] + color = UploadInstance(_REMOTE, 'ColorTestImageJ.dcm')['ID'] + phenix = UploadInstance(_REMOTE, 'Phenix/IM-0001-0001.dcm')['ID'] + + phenixSize = 358 * 512 * 2 + colorSize = 1000 * 1000 * 3 + jpegSize = 51918 + + self.assertEqual(1, len(DoGet(_REMOTE, '/instances/%s/content/7fe0-0010' % phenix))) + self.assertEqual(1, len(DoGet(_REMOTE, '/instances/%s/content/7fe0-0010' % color))) + self.assertEqual(2, len(DoGet(_REMOTE, '/instances/%s/content/7fe0-0010' % jpeg))) + + self.assertEqual(0, len(DoGet(_REMOTE, '/instances/%s/content/7fe0-0010/0' % jpeg))) + self.assertEqual(jpegSize, len(DoGet(_REMOTE, '/instances/%s/content/7fe0-0010/1' % jpeg))) + + self.assertEqual(phenixSize, len(DoGet(_REMOTE, '/instances/%s/content/7fe0-0010/0' % phenix))) + self.assertEqual(colorSize, len(DoGet(_REMOTE, '/instances/%s/content/7fe0-0010/0' % color))) + + + def test_decode_brainix(self): + brainix = [ + UploadInstance(_REMOTE, 'Brainix/Epi/IM-0001-0001.dcm')['ID'], # (*) + UploadInstance(_REMOTE, 'Formats/JpegLossless.dcm')['ID'], # JPEG-LS, same as (*) (since Orthanc 0.7.6) + UploadInstance(_REMOTE, 'Formats/Jpeg.dcm')['ID'], # JPEG, same as (*) (since Orthanc 0.7.6) + ] + h = '6fb11b932d535c2be04beabd99793ff8' + maxValue = 426.0 + + truth = Image.open(GetDatabasePath('Formats/Brainix.png')) + for i in brainix: + self.AssertSameImages(truth.getdata(), '/instances/%s/image-int16' % i) + self.AssertSameImages(truth.getdata(), '/instances/%s/image-uint16' % i) + + truth2 = map(lambda x: min(255, x), truth.getdata()) + for i in brainix: + self.AssertSameImages(truth2, '/instances/%s/image-uint8' % i) + + truth2 = map(lambda x: x * 255.0 / maxValue, truth.getdata()) + for i in brainix: + self.AssertSameImages(truth2, '/instances/%s/preview' % i) + + for i in brainix: + self.assertEqual(h, ComputeMD5(DoGet(_REMOTE, '/instances/%s/matlab' % i))) + + + def test_decode_color(self): + imagej = UploadInstance(_REMOTE, 'ColorTestImageJ.dcm')['ID'] + color = UploadInstance(_REMOTE, 'ColorTestMalaterre.dcm')['ID'] + + for i in [ imagej, color ]: + for j in [ 'image-uint8', 'image-uint16', 'image-int16' ]: + self.assertRaises(Exception, lambda: DoGet(_REMOTE, '/instances/%s/%s' % (i, j))) + + self.assertEqual('c14c687f7a1ea9fe022479fc87c67274', ComputeMD5(DoGet(_REMOTE, '/instances/%s/preview' % imagej))) + self.assertEqual('a87d122918a56f803bcfe9d2586b9125', ComputeMD5(DoGet(_REMOTE, '/instances/%s/preview' % color))) + + self.assertEqual('30cc46bfa7aba77a40e4178f6184c25a', ComputeMD5(DoGet(_REMOTE, '/instances/%s/matlab' % imagej))) + self.assertEqual('ff195005cef06b59666fd220a9b4cd9a', ComputeMD5(DoGet(_REMOTE, '/instances/%s/matlab' % color))) + + + def test_decode_rf(self): + rf = UploadInstance(_REMOTE, 'KarstenHilbertRF.dcm')['ID'] + truth = Image.open(GetDatabasePath('Formats/KarstenHilbertRF.png')) + + self.AssertSameImages(truth.getdata(), '/instances/%s/image-uint8' % rf) + self.AssertSameImages(truth.getdata(), '/instances/%s/image-uint16' % rf) + self.AssertSameImages(truth.getdata(), '/instances/%s/image-int16' % rf) + self.AssertSameImages(truth.getdata(), '/instances/%s/preview' % rf) + + self.assertEqual('42254d70efd2f4a1b8f3455909689f0e', ComputeMD5(DoGet(_REMOTE, '/instances/%s/matlab' % rf))) + + + def test_decode_multiframe(self): + mf = UploadInstance(_REMOTE, 'Multiframe.dcm')['ID'] + + # Test the first frame + truth = Image.open(GetDatabasePath('Formats/Multiframe0.png')) + self.AssertSameImages(truth.getdata(), '/instances/%s/image-uint8' % mf) + self.AssertSameImages(truth.getdata(), '/instances/%s/image-uint16' % mf) + self.AssertSameImages(truth.getdata(), '/instances/%s/image-int16' % mf) + self.AssertSameImages(truth.getdata(), '/instances/%s/preview' % mf) + self.assertEqual('9812b99d93bbcd4e7684ded089b5dfb3', ComputeMD5(DoGet(_REMOTE, '/instances/%s/matlab' % mf))) + + self.AssertSameImages(truth.getdata(), '/instances/%s/frames/0/image-uint16' % mf) + + # Test the last frame + truth = Image.open(GetDatabasePath('Formats/Multiframe75.png')) + self.AssertSameImages(truth.getdata(), '/instances/%s/frames/75/image-uint16' % mf) + + + def test_decode_signed(self): + signed = UploadInstance(_REMOTE, 'SignedCT.dcm')['ID'] + minValue = -2000 + maxValue = 4042 + + truth = Image.open(GetDatabasePath('Formats/SignedCT.png')) + self.AssertSameImages(truth.getdata(), '/instances/%s/image-int16' % signed) + + truth2 = map(lambda x: 0 if x >= 32768 else x, truth.getdata()) + self.AssertSameImages(truth2, '/instances/%s/image-uint16' % signed) + + truth3 = map(lambda x: 255 if x >= 256 else x, truth2) + self.AssertSameImages(truth3, '/instances/%s/image-uint8' % signed) + + tmp = map(lambda x: x - 65536 if x >= 32768 else x, truth.getdata()) + tmp = map(lambda x: (255.0 * (x - minValue)) / (maxValue - minValue), tmp) + self.AssertSameImages(tmp, '/instances/%s/preview' % signed) + + self.assertEqual('b57e6c872a3da50877c7da689b03a444', ComputeMD5(DoGet(_REMOTE, '/instances/%s/matlab' % signed))) diff -r f3d08a75a636 -r 2a29bcff60a7 Tests/Toolbox.py --- a/Tests/Toolbox.py Mon Jun 22 09:47:25 2015 +0200 +++ b/Tests/Toolbox.py Mon Jun 22 14:14:37 2015 +0200 @@ -77,6 +77,7 @@ d = '?' + urlencode(data) http = httplib2.Http() + http.follow_redirects = False _SetupCredentials(orthanc, http) resp, content = http.request(orthanc['Url'] + uri + d, 'GET', body = body, @@ -91,6 +92,7 @@ def _DoPutOrPost(orthanc, uri, method, data, contentType, headers): http = httplib2.Http() + http.follow_redirects = False _SetupCredentials(orthanc, http) if isinstance(data, str): @@ -116,6 +118,7 @@ def DoDelete(orthanc, uri): http = httplib2.Http() + http.follow_redirects = False _SetupCredentials(orthanc, http) resp, content = http.request(orthanc['Url'] + uri, 'DELETE')