changeset 308:2565d39dd36c c-get

integration mainline->c-get
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 20 May 2020 16:35:06 +0200
parents 5be63aef39b1 (current diff) ed20ead1a8b6 (diff)
children e2647dd96612 65d5c591fece
files Tests/Tests.py
diffstat 5 files changed, 324 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
Binary file Database/TransferSyntaxes/1.2.840.10008.1.2.4.50.png has changed
--- a/Plugins/DicomWeb/DicomWeb.py	Wed May 20 16:31:54 2020 +0200
+++ b/Plugins/DicomWeb/DicomWeb.py	Wed May 20 16:35:06 2020 +0200
@@ -68,7 +68,7 @@
     return DoPost(orthanc, uri, body, headers = headers)
 
 
-def DoGetMultipart(orthanc, uri, headers = {}):
+def DoGetMultipart(orthanc, uri, headers = {}, returnHeaders = False):
     answer = DoGetRaw(orthanc, uri, headers = headers)
 
     header = ''
@@ -93,6 +93,12 @@
     for part in msg.walk():
         payload = part.get_payload(decode = True)
         if payload != None:
-            result.append(payload)
+            if returnHeaders:
+                h = {}
+                for (key, value) in part.items():
+                    h[key] = value
+                result.append((payload, h))
+            else:
+                result.append(payload)
 
     return result
--- a/Plugins/DicomWeb/Run.py	Wed May 20 16:31:54 2020 +0200
+++ b/Plugins/DicomWeb/Run.py	Wed May 20 16:35:06 2020 +0200
@@ -32,13 +32,15 @@
 
 
 
-
+import copy
 import os
 import pprint
 import sys
 import argparse
 import unittest
 import re
+from PIL import ImageChops
+
 from DicomWeb import *
 
 sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'Tests'))
@@ -589,16 +591,19 @@
         b = DoGetMultipart(ORTHANC, '%s/frames/%d' % (uri, 1))
         self.assertEqual(1, len(b))
         self.assertEqual(256 * 256 * 2, len(b[0]))
+        self.assertEqual('ce394eb4d4de4eeef348436108101f3b', ComputeMD5(b[0]))
         
         c = DoGetMultipart(ORTHANC, '%s/frames/%d' % (uri, 1),
                            headers = { 'Accept' : 'multipart/related; type=application/octet-stream' })
         self.assertEqual(1, len(c))
         self.assertEqual(b[0], c[0])
+        self.assertEqual('ce394eb4d4de4eeef348436108101f3b', ComputeMD5(c[0]))
 
         c = DoGetMultipart(ORTHANC, '%s/frames/%d' % (uri, 1),
                            headers = { 'Accept' : 'multipart/related; type="application/octet-stream"' })
         self.assertEqual(1, len(c))
         self.assertEqual(b[0], c[0])
+        self.assertEqual('ce394eb4d4de4eeef348436108101f3b', ComputeMD5(c[0]))
 
         self.assertRaises(Exception, lambda: DoGetMultipart(ORTHANC, '%s/frames/%d' % (uri, 1),
                                                             headers = { 'Accept' : 'multipart/related; type="nope"' }))
@@ -999,6 +1004,14 @@
         self.assertEqual(1, len(p))
         self.assertEqual(743 * 975 * 3, len(p[0]))
 
+        if HasGdcmPlugin(ORTHANC):
+            self.assertTrue(ComputeMD5(p[0]) in [
+                'b952d67da9ff004b0adae3982e89d620', # GDCM >= 3.0
+                'b3662c4bfa24a0c73abb08548c63319b'  # Fallback to DCMTK
+                ])
+        else:
+            self.assertEqual('b3662c4bfa24a0c73abb08548c63319b', ComputeMD5(p[0]))  # DCMTK
+
 
     def test_bitbucket_issue_168(self):
         # "Plugins can't read private tags from the configuration
@@ -1132,10 +1145,141 @@
         self.assertEqual(1, len(DoGet(ORTHANC, u'/dicom-web/studies?PatientName=Гусева*',
                                       headers = { 'accept' : 'application/json' })))
 
-        # This line is the isse
+        # This line is the issue
         self.assertEqual(1, len(DoGet(ORTHANC, u'/dicom-web/studies?PatientName=гусева*',
                                       headers = { 'accept' : 'application/json' })))
 
+
+    def test_transcoding(self):
+        ACCEPT = {
+            '1.2.840.10008.1.2' : 'multipart/related; type=application/octet-stream; transfer-syntax=1.2.840.10008.1.2',
+            '1.2.840.10008.1.2.1' : 'multipart/related; type=application/octet-stream; transfer-syntax=1.2.840.10008.1.2.1',
+            '1.2.840.10008.1.2.4.50' : 'multipart/related; type=image/jpeg; transfer-syntax=1.2.840.10008.1.2.4.50',
+            '1.2.840.10008.1.2.4.51' : 'multipart/related; type=image/jpeg; transfer-syntax=1.2.840.10008.1.2.4.51',
+            '1.2.840.10008.1.2.4.57' : 'multipart/related; type=image/jpeg; transfer-syntax=1.2.840.10008.1.2.4.57',
+            '1.2.840.10008.1.2.4.70' : 'multipart/related; type=image/jpeg; transfer-syntax=1.2.840.10008.1.2.4.70',
+            }
+
+        uri = '/dicom-web%s' % UploadAndGetWadoPath('TransferSyntaxes/1.2.840.10008.1.2.4.50.dcm')
+        truth = Image.open(GetDatabasePath('TransferSyntaxes/1.2.840.10008.1.2.4.50.png'))
+        
+        a = DoGetMultipart(ORTHANC, '%s/frames/1' % uri,
+                           headers = { 'Accept' : ACCEPT['1.2.840.10008.1.2.4.50'] },
+                           returnHeaders = True)
+        self.assertEqual(1, len(a))
+        self.assertEqual(2, len(a[0]))
+        self.assertEqual('http://localhost:8042%s/frames/1' % uri,
+                         a[0][1]['Content-Location'])
+        self.assertEqual(ACCEPT['1.2.840.10008.1.2.4.50'],
+                         'multipart/related; type=%s' % a[0][1]['Content-Type'])
+        self.assertEqual(53476, len(a[0][0]))
+        self.assertEqual('142fdb8a1dc2aa7e6b8952aa294a6e22', ComputeMD5(a[0][0]))
+
+        a = DoGetMultipart(ORTHANC, '%s/frames/1' % uri)
+        self.assertEqual(1, len(a))
+        self.assertEqual(480 * 640 * 3, len(a[0]))
+
+        # http://effbot.org/zone/pil-comparing-images.htm
+        img = Image.frombytes('RGB', [ 640, 480 ], a[0])
+        self.assertLessEqual(GetMaxImageDifference(img, truth), 2)
+
+        ACCEPT2 = copy.deepcopy(ACCEPT)
+        if HasGdcmPlugin(ORTHANC):
+            IS_GDCM = True
+            ACCEPT2['1.2.840.10008.1.2.1'] = 'multipart/related; type=application/octet-stream'
+            del ACCEPT2['1.2.840.10008.1.2']
+        else:
+            self.assertEqual('dfdc79f5070926bbb8ac079ee91f5b91', ComputeMD5(a[0]))
+            IS_GDCM = False
+
+        a = DoGetMultipart(ORTHANC, '%s/frames/1' % uri,
+                           headers = { 'Accept' : ACCEPT2['1.2.840.10008.1.2.1'] })
+        self.assertEqual(1, len(a))
+        self.assertEqual(480 * 640 * 3, len(a[0]))
+
+        img = Image.frombytes('RGB', [ 640, 480 ], a[0])
+        self.assertLessEqual(GetMaxImageDifference(img, truth), 2)
+
+        if not IS_GDCM:
+            self.assertEqual('dfdc79f5070926bbb8ac079ee91f5b91', ComputeMD5(a[0]))
+
+
+        # Test download using the same transfer syntax
+        RESULTS = {
+            '1.2.840.10008.1.2' : 'f54c7ea520ab3ec32b6303581ecd262f',
+            '1.2.840.10008.1.2.1' : '4b350b9353a93c747917c7c3bf9b8f44',
+            '1.2.840.10008.1.2.4.50' : '142fdb8a1dc2aa7e6b8952aa294a6e22',
+            '1.2.840.10008.1.2.4.51' : '8b37945d75f9d2899ed868bdba429a0d',
+            '1.2.840.10008.1.2.4.57' : '75c84823eddb560d127b1d24c9406f30',
+            '1.2.840.10008.1.2.4.70' : '2c35726328f0200396e583a0038b0269',
+        }
+
+        if IS_GDCM:
+            # This file was failing with GDCM, as it has 2 fragments,
+            # and only the first one was returned => the MD5 below is BAD
+            #RESULTS['1.2.840.10008.1.2.4.51'] = '901963a322a817946b074f9ed0afa060'
+            pass
+            
+        for syntax in ACCEPT2:
+            uri = '/dicom-web%s' % UploadAndGetWadoPath('TransferSyntaxes/%s.dcm' % syntax)
+            a = DoGetMultipart(ORTHANC, '%s/frames/1' % uri,
+                               headers = { 'Accept' : ACCEPT2[syntax] })
+            self.assertEqual(1, len(a))
+            self.assertEqual(RESULTS[syntax], ComputeMD5(a[0]))
+
+        # Test transcoding to all the possible transfer syntaxes
+        uri = '/dicom-web%s' % UploadAndGetWadoPath('KarstenHilbertRF.dcm')
+        for syntax in ACCEPT2:
+            a = DoGetMultipart(ORTHANC, '%s/frames/1' % uri,
+                               headers = { 'Accept' : ACCEPT2[syntax] },
+                               returnHeaders = True)
+            self.assertEqual(1, len(a))
+            self.assertEqual(2, len(a[0]))
+            self.assertEqual('http://localhost:8042%s/frames/1' % uri,
+                             a[0][1]['Content-Location'])
+            self.assertEqual(ACCEPT[syntax],
+                             'multipart/related; type=%s' % a[0][1]['Content-Type'])
+            if IS_GDCM:
+                self.assertEqual({
+                    '1.2.840.10008.1.2' : '1c8cebde0c74450ce4dfb75dd52ddad7',
+                    '1.2.840.10008.1.2.1' : '1c8cebde0c74450ce4dfb75dd52ddad7',
+                    '1.2.840.10008.1.2.4.50' : 'f4d145e5f33fbd39375ce0f91453d6cc',
+                    '1.2.840.10008.1.2.4.51' : 'f4d145e5f33fbd39375ce0f91453d6cc',
+                    '1.2.840.10008.1.2.4.57' : 'dc55800ce1a8ac556c266cdb26d75757',
+                    '1.2.840.10008.1.2.4.70' : 'dc55800ce1a8ac556c266cdb26d75757',
+                    } [syntax], ComputeMD5(a[0][0]))
+            else:
+                self.assertEqual({
+                    '1.2.840.10008.1.2' : '1c8cebde0c74450ce4dfb75dd52ddad7',
+                    '1.2.840.10008.1.2.1' : '1c8cebde0c74450ce4dfb75dd52ddad7',
+                    '1.2.840.10008.1.2.4.50' : '0a0ab74fe7c68529bdd416fc9e5e742a',
+                    '1.2.840.10008.1.2.4.51' : '33d1ab2fe169c5b5ba932a9bbc3c6306',
+                    '1.2.840.10008.1.2.4.57' : '3d21c969da846ca41e0498a0dcfad061',
+                    '1.2.840.10008.1.2.4.70' : '49d5353c8673208629847ad45a855557',
+                    } [syntax], ComputeMD5(a[0][0]))
+
+
+        # JPEG image with many fragments for 2 frames        
+        uri = '/dicom-web%s' % UploadAndGetWadoPath('LenaTwiceWithFragments.dcm')
+
+        a = DoGetMultipart(ORTHANC, '%s/frames/1' % uri,
+                           headers = { 'Accept' : ACCEPT['1.2.840.10008.1.2.4.50'] })
+        self.assertEqual(1, len(a))
+        self.assertEqual(69214, len(a[0]))
+        self.assertEqual('0eaf36d4881c513ca70b6684bfaa5b08', ComputeMD5(a[0]))
+        
+        b = DoGetMultipart(ORTHANC, '%s/frames/2' % uri,
+                           headers = { 'Accept' : ACCEPT['1.2.840.10008.1.2.4.50'] })
+        self.assertEqual(1, len(b))
+        self.assertEqual(a[0], b[0])
+        
+        b = DoGetMultipart(ORTHANC, '%s/frames/1,2' % uri,
+                           headers = { 'Accept' : ACCEPT['1.2.840.10008.1.2.4.50'] })
+        self.assertEqual(2, len(b))
+        self.assertEqual(a[0], b[0])
+        self.assertEqual(a[0], b[1])
+
+        
         
 try:
     print('\nStarting the tests...')
--- a/Tests/Tests.py	Wed May 20 16:31:54 2020 +0200
+++ b/Tests/Tests.py	Wed May 20 16:35:06 2020 +0200
@@ -141,7 +141,6 @@
         ]
 
 
-
 class Orthanc(unittest.TestCase):
     def setUp(self):
         if (sys.version_info >= (3, 0)):
@@ -1631,7 +1630,8 @@
         # gdcmconv -i /home/jodogne/DICOM/GdcmDatabase/US_DataSet/HDI5000_US/3EAF5E01 -w -o Issue19.dcm
 
         a = UploadInstance(_REMOTE, 'Issue19.dcm')['ID']
-        self.assertRaises(Exception, lambda: DoGet(_REMOTE, '/instances/941ad3c8-05d05b88-560459f9-0eae0e20-6cddd533/preview'))
+        if not HasGdcmPlugin(_REMOTE):
+            self.assertRaises(Exception, lambda: DoGet(_REMOTE, '/instances/941ad3c8-05d05b88-560459f9-0eae0e20-6cddd533/preview'))
 
 
     def test_googlecode_issue_37(self):
@@ -3074,8 +3074,11 @@
         Check('1.2.840.10008.1.2.4.81', '801579ae7cbf28e604ea74f2c99fa2ca')
         Check('1.2.840.10008.1.2.5', '6ff51ae525d362e0d04f550a64075a0e')  # RLE, supported since Orthanc 1.0.1
         Check('1.2.840.10008.1.2', 'd54aed9f67a100984b42942cc2e9939b')
-        Check('1.2.840.10008.1.2.4.90', None)  # JPEG-2000 image, not supported
-        Check('1.2.840.10008.1.2.4.91', None)  # JPEG-2000 image, not supported
+
+        # JPEG2k image, not supported without GDCM plugin
+        if not HasGdcmPlugin(_REMOTE):
+            Check('1.2.840.10008.1.2.4.90', None)
+            Check('1.2.840.10008.1.2.4.91', None)
 
 
     def test_raw_frame(self):
@@ -5511,29 +5514,52 @@
         self.assertEqual('NORMAL', tags['1337,1001']['Value'])
 
 
-    def test_modify_transcode(self):
+    def test_modify_transcode_instance(self):
         i = UploadInstance(_REMOTE, 'KarstenHilbertRF.dcm')['ID']
         self.assertEqual('1.2.840.10008.1.2.1', GetTransferSyntax(
             DoGet(_REMOTE, '/instances/%s/file' % i)))
 
-        for syntax in [
-                '1.2.840.10008.1.2',        
-                '1.2.840.10008.1.2.1',
-                #'1.2.840.10008.1.2.1.99',  # Deflated Explicit VR Little Endian
-                '1.2.840.10008.1.2.2',
-                '1.2.840.10008.1.2.4.50',
-                '1.2.840.10008.1.2.4.51',
-                '1.2.840.10008.1.2.4.57',
-                '1.2.840.10008.1.2.4.70',
-                #'1.2.840.10008.1.2.4.80',  # This makes DCMTK 3.6.2 crash
-                #'1.2.840.10008.1.2.4.81',  # This makes DCMTK 3.6.2 crash
-        ]:
+        a = ExtractDicomTags(DoGet(_REMOTE, '/instances/%s/file' % i), [ 'SOPInstanceUID' ]) [0]
+        self.assertTrue(len(a) > 20)
+
+        SYNTAXES = [
+            '1.2.840.10008.1.2',        
+            '1.2.840.10008.1.2.1',
+            #'1.2.840.10008.1.2.1.99',  # Deflated Explicit VR Little Endian (cannot be decoded in debug mode if Orthanc is statically linked against DCMTK 3.6.5)
+            '1.2.840.10008.1.2.2',
+            '1.2.840.10008.1.2.4.50',
+            '1.2.840.10008.1.2.4.51',
+            '1.2.840.10008.1.2.4.57',
+            '1.2.840.10008.1.2.4.70',
+        ]
+
+        if HasGdcmPlugin(_REMOTE):
+            SYNTAXES = SYNTAXES + [
+                '1.2.840.10008.1.2.4.80',  # This makes DCMTK 3.6.2 crash
+                '1.2.840.10008.1.2.4.81',  # This makes DCMTK 3.6.2 crash
+                '1.2.840.10008.1.2.4.90',  # JPEG2k, unavailable without GDCM
+                '1.2.840.10008.1.2.4.91',  # JPEG2k, unavailable without GDCM
+            ]
+        
+        for syntax in SYNTAXES:
             transcoded = DoPost(_REMOTE, '/instances/%s/modify' % i, {
                 'Transcode' : syntax,
+                'Keep' : [ 'SOPInstanceUID' ],
+                'Force' : True,
                 })
             
             self.assertEqual(syntax, GetTransferSyntax(transcoded))
 
+            b = ExtractDicomTags(transcoded, [ 'SOPInstanceUID' ]) [0]
+            self.assertTrue(len(b) > 20)
+            if syntax in [ '1.2.840.10008.1.2.4.50',
+                           '1.2.840.10008.1.2.4.51',
+                           '1.2.840.10008.1.2.4.81',
+                           '1.2.840.10008.1.2.4.91' ]:
+                # Lossy transcoding: The SOP instance UID must have changed
+                self.assertNotEqual(a, b)
+            else:
+                self.assertEqual(a, b)
 
     def test_archive_transcode(self):
         info = UploadInstance(_REMOTE, 'KarstenHilbertRF.dcm')
@@ -5634,8 +5660,99 @@
         self.assertEqual(2, len(z.namelist()))
         self.assertEqual('1.2.840.10008.1.2.4.57', GetTransferSyntax(z.read('IMAGES/IM0')))
 
+
+    def test_modify_keep_source(self):
+        # https://groups.google.com/d/msg/orthanc-users/CgU-Wg8vDio/BY5ZWcDEAgAJ
+        i = UploadInstance(_REMOTE, 'DummyCT.dcm')
+        self.assertEqual(1, len(DoGet(_REMOTE, '/studies')))
+
+        j = DoPost(_REMOTE, '/studies/%s/modify' % i['ParentStudy'], {
+            'Replace' : {
+                'StationName' : 'TEST',
+                },
+            'KeepSource' : True,
+        })
+
+        s = DoGet(_REMOTE, '/studies')
+        self.assertEqual(2, len(s))
+        self.assertTrue(i['ParentStudy'] in s)
+        self.assertTrue(j['ID'] in s)
+
+        DoDelete(_REMOTE, '/studies/%s' % j['ID'])
+        self.assertEqual(1, len(DoGet(_REMOTE, '/studies')))
+
+        j = DoPost(_REMOTE, '/studies/%s/modify' % i['ParentStudy'], {
+            'Replace' : {
+                'StationName' : 'TEST',
+                },
+            'KeepSource' : False,
+        })
+
+        s = DoGet(_REMOTE, '/studies')
+        self.assertEqual(1, len(s))
+        self.assertFalse(i['ParentStudy'] in s)
+        self.assertTrue(j['ID'] in s)
+
+
+    def test_modify_transcode_study(self):
+        i = UploadInstance(_REMOTE, 'KarstenHilbertRF.dcm')
+        self.assertEqual('1.2.840.10008.1.2.1', GetTransferSyntax(
+            DoGet(_REMOTE, '/instances/%s/file' % i['ID'])))
+
+        self.assertEqual(1, len(DoGet(_REMOTE, '/instances')))
+        j = DoPost(_REMOTE, '/studies/%s/modify' % i['ParentStudy'], {
+            'Transcode' : '1.2.840.10008.1.2.4.50',
+            'KeepSource' : False
+            })
+
+        k = DoGet(_REMOTE, '/instances')
+        self.assertEqual(1, len(k))
+        self.assertEqual(i['ID'], DoGet(_REMOTE, '/instances/%s/metadata?expand' % k[0]) ['ModifiedFrom'])       
+        self.assertEqual('1.2.840.10008.1.2.4.50', GetTransferSyntax(
+            DoGet(_REMOTE, '/instances/%s/file' % k[0])))
         
 
+    def test_store_peer_transcoding(self):
+        i = UploadInstance(_REMOTE, 'KarstenHilbertRF.dcm')['ID']
+
+        SYNTAXES = [
+            '1.2.840.10008.1.2',        
+            '1.2.840.10008.1.2.1',
+            #'1.2.840.10008.1.2.1.99',  # Deflated Explicit VR Little Endian (cannot be decoded in debug mode if Orthanc is statically linked against DCMTK 3.6.5)
+            '1.2.840.10008.1.2.2',
+            '1.2.840.10008.1.2.4.50',
+            '1.2.840.10008.1.2.4.51',
+            '1.2.840.10008.1.2.4.57',
+            '1.2.840.10008.1.2.4.70',
+        ]
+
+        if HasGdcmPlugin(_REMOTE):
+            SYNTAXES = SYNTAXES + [
+                '1.2.840.10008.1.2.4.80',  # This makes DCMTK 3.6.2 crash
+                '1.2.840.10008.1.2.4.81',  # This makes DCMTK 3.6.2 crash
+                '1.2.840.10008.1.2.4.90',  # JPEG2k, unavailable without GDCM
+                '1.2.840.10008.1.2.4.91',  # JPEG2k, unavailable without GDCM
+            ]
+
+        for syntax in SYNTAXES:
+            body = {
+                'Resources' : [ i ],
+            }
+
+            if syntax != '1.2.840.10008.1.2.1':
+                body['Transcode'] = syntax
+            
+            self.assertEqual(0, len(DoGet(_LOCAL, '/instances')))
+            self.assertEqual(1, len(DoGet(_REMOTE, '/instances')))
+            DoPost(_REMOTE, '/peers/peer/store', body, 'text/plain')
+            self.assertEqual(1, len(DoGet(_LOCAL, '/instances')))
+            self.assertEqual(1, len(DoGet(_REMOTE, '/instances')))
+            self.assertEqual(syntax, GetTransferSyntax(
+                DoGet(_LOCAL, '/instances/%s/file' % DoGet(_LOCAL, '/instances') [0])))
+
+            DropOrthanc(_LOCAL)
+
+        
     def test_getscu(self):
         def CleanTarget():
             if os.path.isdir('/tmp/GETSCU'):
@@ -5686,6 +5803,3 @@
         os.system('ls -l /tmp/GETSCU')
         self.assertTrue(os.path.isfile('/tmp/GETSCU/MR.1.3.46.670589.11.0.0.11.4.2.0.8743.5.5396.2006120114314079549'))
         self.assertTrue(os.path.isfile('/tmp/GETSCU/MR.1.2.276.0.7230010.3.1.4.2831176407.19977.1434973482.75579'))
-
-
-
--- a/Tests/Toolbox.py	Wed May 20 16:31:54 2020 +0200
+++ b/Tests/Toolbox.py	Wed May 20 16:35:06 2020 +0200
@@ -32,7 +32,10 @@
 import time
 import zipfile
 
-from PIL import Image
+from PIL import Image, ImageChops
+import math
+import operator
+
 
 if (sys.version_info >= (3, 0)):
     from urllib.parse import urlencode
@@ -389,3 +392,35 @@
         data = subprocess.check_output([ FindExecutable('dcm2xml'), f.name ])
 
     return re.search('<data-set xfer="(.*?)"', data).group(1)
+
+
+def HasGdcmPlugin(orthanc):
+    plugins = DoGet(orthanc, '/plugins')
+    return ('gdcm' in plugins)
+
+
+def _GetMaxImageDifference(im1, im2):
+    h = ImageChops.difference(im1, im2).histogram()
+
+    if len(h) < 256:
+        raise Exception()
+
+    i = len(h) - 1
+    while h[i] == 0:
+        i -= 1
+
+    return i
+    
+
+def GetMaxImageDifference(im1, im2):
+    if im1.mode != im2.mode:
+        raise Exception('Incompatible image modes')
+
+    if im1.mode == 'RGB':
+        red1, green1, blue1 = im1.split()
+        red2, green2, blue2 = im2.split()
+        return max([ _GetMaxImageDifference(red1, red2), 
+                     _GetMaxImageDifference(green1, green2),
+                     _GetMaxImageDifference(blue1, blue2) ])
+    else:
+        return _GetMaxImageDifference(im1, im2)