1
1
#!/usr/bin/env python
2
- # (C) British Crown Copyright 2010 - 2015 , Met Office
2
+ # (C) British Crown Copyright 2010 - 2016 , Met Office
3
3
#
4
4
# This file is part of Iris.
5
5
#
25
25
from __future__ import (absolute_import , division , print_function )
26
26
from six .moves import (filter , input , map , range , zip ) # noqa
27
27
28
+ import argparse
29
+ import codecs
30
+ import contextlib
31
+ from glob import glob
32
+ import hashlib
33
+ import json
28
34
import os .path
29
- import shutil
30
35
import sys
36
+ import warnings
31
37
38
+ from PIL import Image
39
+ import filelock
32
40
import matplotlib .pyplot as plt
33
41
import matplotlib .image as mimg
42
+ import matplotlib .testing .compare as mcompare
34
43
import matplotlib .widgets as mwidget
35
-
36
-
37
- def diff_viewer (expected_fname , result_fname , diff_fname ):
38
- plt .figure (figsize = (16 , 16 ))
44
+ import requests
45
+
46
+ # Force iris.tests to use the ```tkagg``` backend by using the '-d'
47
+ # command-line argument as idiff is an interactive tool that requires a
48
+ # gui interface.
49
+ sys .argv .append ('-d' )
50
+ import iris .tests
51
+ import iris .util as iutil
52
+
53
+
54
+ _POSTFIX_DIFF = '-failed-diff.png'
55
+ _POSTFIX_JSON = os .path .join ('results' , 'imagerepo.json' )
56
+ _POSTFIX_LOCK = os .path .join ('results' , 'imagerepo.lock' )
57
+ _PREFIX_URI = 'https://scitools.github.io/test-images-scitools/image_files'
58
+ _TIMEOUT = 30
59
+ _TOL = 0
60
+
61
+
62
+ @contextlib .contextmanager
63
+ def temp_png (suffix = '' ):
64
+ if suffix :
65
+ suffix = '-{}' .format (suffix )
66
+ fname = iutil .create_temp_filename (suffix + '.png' )
67
+ try :
68
+ yield fname
69
+ finally :
70
+ os .remove (fname )
71
+
72
+
73
+ def diff_viewer (repo , key , repo_fname ,
74
+ expected_fname , result_fname , diff_fname ):
75
+ plt .figure (figsize = (14 , 12 ))
39
76
plt .suptitle (os .path .basename (expected_fname ))
40
77
ax = plt .subplot (221 )
41
78
ax .imshow (mimg .imread (expected_fname ))
@@ -44,49 +81,126 @@ def diff_viewer(expected_fname, result_fname, diff_fname):
44
81
ax = plt .subplot (223 , sharex = ax , sharey = ax )
45
82
ax .imshow (mimg .imread (diff_fname ))
46
83
84
+ # Determine the new image hash.png name.
85
+ with open (result_fname , 'rb' ) as fi :
86
+ sha1 = hashlib .sha1 (fi .read ())
87
+ result_dir = os .path .dirname (result_fname )
88
+ fname = sha1 .hexdigest () + '.png'
89
+ uri = os .path .join (_PREFIX_URI , fname )
90
+ hash_fname = os .path .join (result_dir , fname )
91
+
47
92
def accept (event ):
48
- # removes the expected result, and move the most recent result in
49
- print ('ACCEPTED NEW FILE: %s' % (os .path .basename (expected_fname ), ))
50
- os .remove (expected_fname )
51
- shutil .copy2 (result_fname , expected_fname )
93
+ if uri not in repo [key ]:
94
+ # Ensure to maintain strict time order where the first uri
95
+ # associated with the repo key is the oldest, and the last
96
+ # uri is the youngest
97
+ repo [key ].append (uri )
98
+ # Update the image repo.
99
+ with open (repo_fname , 'wb' ) as fo :
100
+ json .dump (repo , fo , indent = 4 , sort_keys = True )
101
+ os .rename (result_fname , hash_fname )
102
+ msg = 'ACCEPTED: {} -> {}'
103
+ print (msg .format (os .path .basename (result_fname ),
104
+ os .path .basename (hash_fname )))
105
+ else :
106
+ msg = 'DUPLICATE: {} -> {} (ignored)'
107
+ print (msg .format (os .path .basename (result_fname ),
108
+ os .path .basename (hash_fname )))
109
+ os .remove (result_fname )
52
110
os .remove (diff_fname )
53
111
plt .close ()
54
112
55
113
def reject (event ):
56
- print ('REJECTED: %s' % (os .path .basename (expected_fname ), ))
114
+ if uri not in repo [key ]:
115
+ print ('REJECTED: {}' .format (os .path .basename (result_fname )))
116
+ else :
117
+ msg = 'DUPLICATE: {} -> {} (ignored)'
118
+ print (msg .format (os .path .basename (result_fname ),
119
+ os .path .basename (hash_fname )))
120
+ os .remove (result_fname )
121
+ os .remove (diff_fname )
57
122
plt .close ()
58
123
59
- ax_accept = plt .axes ([0.7 , 0.05 , 0.1 , 0.075 ])
60
- ax_reject = plt .axes ([0.81 , 0.05 , 0.1 , 0.075 ])
61
- bnext = mwidget .Button (ax_accept , 'Accept change' )
62
- bnext .on_clicked (accept )
63
- bprev = mwidget .Button (ax_reject , 'Reject' )
64
- bprev .on_clicked (reject )
124
+ def skip (event ):
125
+ print ('SKIPPED: {}' .format (os .path .basename (result_fname )))
126
+ os .remove (diff_fname )
127
+ plt .close ()
65
128
129
+ ax_accept = plt .axes ([0.59 , 0.05 , 0.1 , 0.075 ])
130
+ ax_reject = plt .axes ([0.7 , 0.05 , 0.1 , 0.075 ])
131
+ ax_skip = plt .axes ([0.81 , 0.05 , 0.1 , 0.075 ])
132
+ baccept = mwidget .Button (ax_accept , 'Accept' )
133
+ baccept .on_clicked (accept )
134
+ breject = mwidget .Button (ax_reject , 'Reject' )
135
+ breject .on_clicked (reject )
136
+ bskip = mwidget .Button (ax_skip , 'Skip' )
137
+ bskip .on_clicked (skip )
66
138
plt .show ()
67
139
68
140
69
- def step_over_diffs ():
70
- import iris .tests
71
- image_dir = os .path .join (os .path .dirname (iris .tests .__file__ ),
72
- 'results' , 'visual_tests' )
73
- diff_dir = os .path .join (os .path .dirname (iris .tests .__file__ ),
74
- 'result_image_comparison' )
75
-
76
- for expected_fname in sorted (os .listdir (image_dir )):
77
- result_path = os .path .join (diff_dir , 'result-' + expected_fname )
78
- diff_path = result_path [:- 4 ] + '-failed-diff.png'
79
-
80
- # if the test failed, there will be a diff file
81
- if os .path .exists (diff_path ):
82
- expected_path = os .path .join (image_dir , expected_fname )
83
- diff_viewer (expected_path , result_path , diff_path )
141
+ def step_over_diffs (result_dir ):
142
+ processed = False
143
+ dname = os .path .dirname (iris .tests .__file__ )
144
+ lock = filelock .FileLock (os .path .join (dname , _POSTFIX_LOCK ))
145
+
146
+ # Remove old image diff results.
147
+ target = os .path .join (result_dir , '*{}' .format (_POSTFIX_DIFF ))
148
+ for fname in glob (target ):
149
+ os .remove (fname )
150
+
151
+ with lock .acquire (timeout = _TIMEOUT ):
152
+ # Load the imagerepo.
153
+ repo_fname = os .path .join (dname , _POSTFIX_JSON )
154
+ with open (repo_fname , 'rb' ) as fi :
155
+ repo = json .load (codecs .getreader ('utf-8' )(fi ))
156
+
157
+ target = os .path .join (result_dir , 'result-*.png' )
158
+ for result_fname in sorted (glob (target )):
159
+ # We only care about PNG images.
160
+ try :
161
+ im = Image .open (result_fname )
162
+ if im .format != 'PNG' :
163
+ continue
164
+ except IOError :
165
+ continue
166
+ key = os .path .splitext ('-' .join (result_fname .split ('-' )[1 :]))[0 ]
167
+ try :
168
+ uri = repo [key ][0 ]
169
+ except KeyError :
170
+ wmsg = 'Ignoring unregistered test result {!r}.'
171
+ warnings .warn (wmsg .format (key ))
172
+ continue
173
+ with temp_png (key ) as expected_fname :
174
+ processed = True
175
+ resource = requests .get (uri )
176
+ with open (expected_fname , 'wb' ) as fo :
177
+ fo .write (resource .content )
178
+ mcompare .compare_images (expected_fname , result_fname , tol = _TOL )
179
+ diff_fname = os .path .splitext (result_fname )[0 ] + _POSTFIX_DIFF
180
+ diff_viewer (repo , key , repo_fname , expected_fname ,
181
+ result_fname , diff_fname )
182
+ if not processed :
183
+ msg = '\n {}: There are no iris test result images to process.\n '
184
+ print (msg .format (os .path .splitext (sys .argv [0 ])[0 ]))
84
185
85
186
86
187
if __name__ == '__main__' :
87
- # Force iris.tests to use the ```tkagg``` backend by using the '-d'
88
- # command-line argument as idiff is an interactive tool that requires a
89
- # gui interface.
90
- sys .argv .append ('-d' )
91
-
92
- step_over_diffs ()
188
+ default = os .path .join (os .path .dirname (iris .tests .__file__ ),
189
+ 'result_image_comparison' )
190
+ description = 'Iris graphic test difference tool.'
191
+ parser = argparse .ArgumentParser (description = description )
192
+ help = 'Path to iris tests result image directory (default: %(default)s)'
193
+ parser .add_argument ('--resultdir' , '-r' ,
194
+ default = default ,
195
+ help = help )
196
+ help = 'Force "iris.tests" to use the tkagg backend (default: %(default)s)'
197
+ parser .add_argument ('-d' ,
198
+ action = 'store_true' ,
199
+ default = True ,
200
+ help = help )
201
+ args = parser .parse_args ()
202
+ result_dir = args .resultdir
203
+ if not os .path .isdir (result_dir ):
204
+ emsg = 'Invalid results directory: {}'
205
+ raise ValueError (emsg .format (result_dir ))
206
+ step_over_diffs (result_dir )
0 commit comments