-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathimtools.py
More file actions
455 lines (368 loc) · 15.9 KB
/
imtools.py
File metadata and controls
455 lines (368 loc) · 15.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# imtools.py - Simple but often used load/save/show routines for images
#
# Author: Stefan Fuertinger [stefan.fuertinger@gmx.at]
# Created: June 13 2012
# Last modified: <2017-09-21 11:15:41>
from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
import os
import datetime
import shutil
from glob import glob
from string import join
##########################################################################################
def imwrite(figobj,fstr,dpi=None):
"""
Save a Matplotlib figure camera-ready using a "tight" bounding box
Parameters
----------
figobj : Matplotlib figure
Matplotlib figure object to be saved
fstr : string
String holding the filename to be used to save the figure. If
a specific file format is wanted, provide it with `fstr`, e.g.,
`fstr = 'output.tiff'`. If `fstr` does not contain a filename extension
the Matplotlib default (png) will be used.
dpi : integer
The wanted resolution of the output in dots per inch. If `None` the
Matplotlib default will be used.
Returns
-------
Nothing : None
Notes
-----
This is a humble attempt to get rid of the huge white areas around plots
that are generated by Matplotlib's `savefig` when saving a figure as an
image using default values. It tries to mimic
`export_fig for MATLAB <http://www.mathworks.com/matlabcentral/fileexchange/23629-export-fig>`_.
The result, however, is not perfect yet...
See also
--------
savefig : in the `Matplotlib documentation <http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.savefig>`_
"""
# Check if `figobj` is really a figure
if type(figobj).__name__ != "Figure":
raise TypeError("figobj has to be a valid matplotlib Figure object!")
# Make sure `fstr` doesn't contain weirdness and points to an existing place
if not isinstance(fstr,(str,unicode)):
raise TypeError("Output filename has to be a string!")
fstr = str(fstr)
if fstr.find("~") == 0:
fstr = os.path.expanduser('~') + fstr[1:]
slash = fstr.rfind(os.sep)
if slash >= 0 and not os.path.isdir(fstr[:slash]):
raise ValueError('Invalid path for output file: '+fstr+'!')
# Check if filename extension has been provided
dt = fstr.rfind('.')
if dt == -1:
fname = fstr+'.png'
ext = 'png'
elif len(fstr[dt+1:]) < 2:
print "Invalid filename extension: "+fstr[dt:]+" Defaulting to png..."
fname = fstr[:dt]+'.png'
ext = 'png'
else:
fname = fstr
ext = fstr[dt+1:]
# Save the figure using "tight" options for the bounding box
figobj.savefig(fname,bbox_inches="tight",ppad_inches=0,dpi=dpi,format=ext)
return
##########################################################################################
def normalize(I,a=0,b=1):
"""
Re-scale a NumPy ndarray
Parameters
----------
I: NumPy ndarray
Array to be normalized
a : float
The lower normalization bound.
By default `a = 0` (`a` has to satisfy `a < b`)
b : float
The upper normalization bound.
By default `b = 1` (`b` has to satisfy `b < a`)
Returns
-------
In : NumPy ndarray
Scaled version of the input array `I`, such that `a = In.min()` and
`b = In.max()`
Examples
--------
>>> I = array([[-1,.2],[100,0]])
>>> In = normalize(I,a=-10,b=12)
>>> In
array([[-10. , -9.73861386],
[ 12. , -10. ]])
"""
# Ensure that `I` is a NumPy-ndarray
try: tmp = I.size == 1
except TypeError: raise TypeError('I has to be a NumPy ndarray!')
if (tmp): raise ValueError('I has to be a NumPy ndarray of size > 1!')
# If normalization bounds are user specified, check them
try: tmp = b <= a
except TypeError: raise TypeError('a and b have to be scalars satisfying a < b!')
if (tmp):
raise ValueError('a has to be strictly smaller than b!')
if np.absolute(a - b) < np.finfo(float).eps:
raise ValueError('|a-b|<eps, no normalization possible')
# Get min and max of I
Imin = I.min()
Imax = I.max()
# If min and max values of I are identical do nothing, if they differ close to machine precision abort
if Imin == Imax:
return I
elif np.absolute(Imin - Imax) < np.finfo(float).eps:
raise ValueError('|Imin-Imax|<eps, no normalization possible')
# Make a local copy of I
I = I.copy()
# Here the normalization is done
I = (I - Imin)*(b - a)/(Imax - Imin) + a
# Return normalized array
return I
##########################################################################################
def blendedges(Im,chim):
"""
Superimpose a (binary) edge set on an gray-scale image using Matplotlib's `imshow`
Parameters
----------
Im: NumPy 2darray
Grayscale image (has to be a 2D array)
chim: NumPy 2darray
Binary edge map (has to be a 2D array). Note that the edge map must be binary, i.e.,
it must only contain the values 0 and 1
Returns
-------
Nothing : None
See also
--------
imshow : in the `Matplotlib documentation <http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.imshow>`_
Stackoverflow : `This submission <http://stackoverflow.com/questions/2495656/variable-alpha-blending-in-pylab>`_ illustrates how to use variable alpha blending in Matplotlib
"""
# Sanity checks
if type(Im).__name__ != "ndarray":
raise TypeError("Im has to be a NumPy ndarray!")
else:
if len(Im.shape) > 2: raise ValueError("Im has to be 2-dimensional!")
try: Im.shape[1]
except: raise ValueError("Im has to be an image!")
if np.isnan(Im).max() == True or np.isinf(Im).max() == True:
raise ValueError("Im must not contain NaNs or Infs!")
if type(chim).__name__ != "ndarray":
raise TypeError("chim has to be a NumPy ndarray!")
else:
if len(chim.shape) > 2: raise ValueError("chim has to be 2-dimensional!")
try: chim.shape[1]
except: raise ValueError("chim has to be an edge map!")
if np.isnan(chim).max() == True or np.isinf(chim).max() == True:
raise ValueError("chim must not contain NaNs or Infs!")
chim = chim.astype(float)
chiu = np.unique(chim)
if chiu.size != 2: raise ValueError("chim has to be binary!")
if chiu.min() != 0 or chiu.max() != 1: raise ValueError("chim has to be a binary edge map!")
# Now do something
plt.imshow(Im,cmap="gray",interpolation="nearest")
plt.hold(True)
plt.imshow(mycmap(chim))
plt.axis("off")
plt.draw()
return
##########################################################################################
def mycmap(x):
"""
Generate a custom color map, setting alpha values to one on edge
points, and to zero otherwise
Notes
-----
This code is based on the suggestion found at this
`Stackoverflow thread <http://stackoverflow.com/questions/2495656/variable-alpha-blending-in-pylab >`
"""
# Convert edge map to Matplotlib colormap (shape (N,N,4))
tmp = plt.cm.hsv(x)
# Set alpha values to one on edge points
tmp[:,:,3] = x
return tmp
##########################################################################################
def recmovie(figobj=None, movie=None, savedir=None, fps=None):
"""
Save Matplotlib figures and generate a movie sequences
Parameters
----------
figobj : Matplotlib figure
Figure to base the movie on
movie : str
Filename of the generated movie
savedir : str
Output directory for video file or name of directory of/for source image files
fps : int
Target frames per second
Returns
-------
Nothing : None
Examples
--------
The command
>>> recmovie(figobj)
saves the Matplotlib figure-object `figobj` in the default directory `_tmp` as png-image.
If the default directory is empty the image will be named `_tmp0000.png`,
otherwise the highest number in the png-file-names incremented by one will be
used as filename.
Use
>>> recmovie(figobj,savedir="somedir")
to save the Matplotlib figure-object `figobj` in the directory defined by the string
`savedir`. If the directory does not exist, it will be created.
If the directory `savedir` is empty the image will be named `_tmp0000.png`, otherwise the
highest number in the png-file-names incremented by one will be used as filename.
The command `recmovie()` will attempt to use `mencoder <http://en.wikipedia.org/wiki/MEncoder>`_
to generate an avi-movie composed of the
png-images found in the default directory `_tmp`. The movie's default name will be
composed of the default prefix `_tmp` and the current date and time.
After the movie has been generated the default-directory `_tmp` and its contents
will be deleted.
Use
>>> recmovie(movie="somename")
to launch mencoder and generate an avi-video composed of the png-images found in the
default directory `_tmp`.
The string `movie` will be used as filename for the generated movie (in the above example
a file `somename.avi` will be created).
After the movie has been generated the default-directory `_tmp` and its contents
will be deleted. Similarly
>>> recmovie(savedir="somedir")
will generate an avi-movie composed of the png-images found in the directory specified
by the string `savedir`. The movie's name is given by the prefix `_tmp` and the current
date and time.
After the movie has been generated it will be moved to the directory `savedir`. If
a movie-file of the same name exists in `savedir` a *WARNING* is printed and the movie
will not be moved. Analogously,
>>> recmovie(movie="somename",savedir="somedir")
will generate an avi-movie named "somename" composed of the png-images found in the
directory specified by the string `savedir`.
After the movie has been generated it will be moved to the directory `savedir`. If
a movie-file of the same name exists in `savedir` a *WARNING* is printed and the movie
will not be moved.
**Note:** the command
>>> recmovie(figobj,movie="somename",savedir="somedir")
will ONLY save the Matplotlib-figure-object `figobj` in the directory defined by the
string `savdir`. The optional argument `movie` will be ignored.
**Note:** the default-directory, image-format and movie-type can be changed in the source code
by editing the variables `prefix`, `imgtype` and `movtype`.
See also
--------
Matplotlib : a `collection of codes <http://matplotlib.org/examples/animation/index.html>`_ illustrating how to use animation in Matplotlib
"""
# Set default values
prefix = "_tmp"
numdigits = "%04d"
imgtype = "png"
movtype = "avi"
fpsno = 25
now = datetime.datetime.now()
savedirname = prefix
moviename = "movie"+"_"+repr(now.hour)+repr(now.minute)+repr(now.second)
# Assign defaults
if movie is None:
movie = moviename
if savedir is None:
savedir = savedirname
if fps is None:
fps = fpsno
# Make sure `figobj` is actually a figure
if figobj != None:
if type(figobj).__name__ != "Figure":
raise TypeError("figobj has to be a valid Matplotlib Figure object!")
# Check if movie filename makes sense and points to an existing location
if not isinstance(movie,(str,unicode)):
raise TypeError("Output filename for movie has to be a string!")
movie = str(movie)
if movie.find("~") == 0:
movie = os.path.expanduser('~') + movie[1:]
if not os.path.isdir(movie[:movie.rfind(os.sep)]):
raise ValueError('Invalid path for output filename for movie: '+movie+'!')
# Make sure `savedir` exists
if not isinstance(savedir,(str,unicode)):
raise TypeError('Output filename has to be a string!')
savedir = str(savedir)
if savedir.find("~") == 0:
savedir = os.path.expanduser('~') + savedir[1:]
if not os.path.isdir(savedir):
raise ValueError('Invalid path for output file: '+savedir+'!')
# Convert possible float argument to integer, if it does not work raise a TypeError
try:
fps = int(fps)
except:
raise TypeError("fps has to be an integer (see man mencoder for details)!")
# Check if mencoder is available
if os.system("which mencoder > /dev/null") != 0:
print "\n\nWARNING: mencoder was not found on your system - movie generation won't work!!!\n\n"
# Check if movie already exists, if yes abort
if len(glob(movie)) != 0:
errormsg = "Movie %s already exists! Aborting..."%savedir
raise ValueError(errormsg)
# If not already existent, create directory savedir
if len(glob(savedir)) == 0:
os.mkdir(savedir)
# If a none-default savedir was chosen, automatically keep images
# and move movie to this non-standard savedir
if savedir != savedirname:
keepimgs = 1
else:
keepimgs = 0
# List all imgtype-files in directory savedir
filelist = glob(savedir+os.sep+"*."+imgtype)
# If we have been called with a figobj save it in savedir
if figobj != None:
# If there are already imgtype-files in the directory then filelist!=0
if len(filelist) != 0:
# Sort filelist, s.t. the file having the highest no. is the last element
filelist.sort()
scounter = filelist[-1]
# Remove the path and prefix from the last elements filename
scounter = scounter.replace(savedir+os.sep+prefix,"")
# Split the name further s.t. it is only number+imgtype
scounter = scounter.split(".")[0]
# Convert the string holding the file's no. to an integer
counter = int(scounter) + 1
# No files are present in savedir, start with 0
else:
counter = 0
# Generate the name the file is stored under (prefix+numdigits(counter).imgtype, e.g. _tmp0102.png)
fname = savedir+os.sep+prefix+numdigits+"."+imgtype
fname = fname%counter
# Save the figure using the just generated filename
figobj.savefig(fname)
# User wants to generate a movie consisting of imgtyp-files in a directory savedir
else:
# Check if there are any files to process in savedir, if not raise an error
if len(filelist) == 0:
errormsg = "No %s-images found in directory %s! Aborting..."%(imgtype,savedir)
raise ValueError(errormsg)
# This is the standard command used to generate the movie
command = ["mencoder",
"mf://*.png",
"-mf",
"type=png:w=800:h=600:fps=25",
"-ovc",
"lavc",
"-lavcopts",
"vcodec=mpeg4",
"-oac",
"copy",
"-o",
"output.avi"]
# Make necessary changes here (like pointing to the right savedir, imgtype, movie,...)
command[1] = "mf://"+savedir+os.sep+"*."+imgtype
command[3] = "type="+imgtype+":w=800:h=600:fps="+str(fps)
command[-1] = movie+"."+movtype
# Call mencoder to generate movie
os.system(join(command))
# If we have been called using the default savedir, erase it (and its contents)
if keepimgs == 0:
for f in glob(savedir+os.sep+"*"+imgtype):
os.unlink(f)
os.rmdir(savedir)
# If not don't erase it but (try to) move the generated movie into this savedir
else:
try:
shutil.move(movie+"."+movtype,savedir+os.sep)
except:
print "\n\n\nWARNING: Movie %s already exists in directory %s. I won't move it there but keep it here. "%(movie,savedir)