~ubuntu-branches/ubuntu/karmic/python-scipy/karmic

« back to all changes in this revision

Viewing changes to Lib/pilutil.py

  • Committer: Bazaar Package Importer
  • Author(s): Daniel T. Chen (new)
  • Date: 2005-03-16 02:15:29 UTC
  • Revision ID: james.westby@ubuntu.com-20050316021529-xrjlowsejs0cijig
Tags: upstream-0.3.2
ImportĀ upstreamĀ versionĀ 0.3.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Functions which need the PIL
 
2
 
 
3
from scipy_base import ppimport
 
4
import types
 
5
import Numeric
 
6
 
 
7
from scipy_base import exp, amin, amax, ravel, asarray, cast, arange, \
 
8
     ones, NewAxis, transpose, mgrid, iscomplexobj, sum, zeros
 
9
 
 
10
Image = ppimport('Image')
 
11
ImageFilter = ppimport('ImageFilter')
 
12
 
 
13
__all__ = ['fromimage','toimage','imsave','imread','bytescale',
 
14
           'imrotate','imresize','imshow','imfilter','radon']
 
15
 
 
16
_UInt8 = Numeric.UnsignedInt8
 
17
 
 
18
# Returns a byte-scaled image
 
19
def bytescale(data, cmin=None, cmax=None, high=255, low=0):
 
20
    if data.typecode == _UInt8:
 
21
        return data
 
22
    high = high - low
 
23
    if cmin is None:
 
24
        cmin = amin(ravel(data))
 
25
    if cmax is None:
 
26
        cmax = amax(ravel(data))
 
27
    scale = high *1.0 / (cmax-cmin or 1)
 
28
    bytedata = ((data*1.0-cmin)*scale + 0.4999).astype(_UInt8)
 
29
    return bytedata + cast[_UInt8](low)
 
30
            
 
31
def imread(name,flatten=0):
 
32
    """Read an image file from a filename.
 
33
 
 
34
    Optional arguments:
 
35
 
 
36
     - flatten (0): if true, the image is flattened by calling convert('F') on
 
37
     the resulting image object.  This flattens the color layers into a single
 
38
     grayscale layer.
 
39
    """
 
40
 
 
41
    im = Image.open(name)
 
42
    return fromimage(im,flatten=flatten)
 
43
 
 
44
def imsave(name, arr):
 
45
    """Save an array to an image file.
 
46
    """
 
47
    im = toimage(arr)
 
48
    im.save(name)
 
49
    return
 
50
 
 
51
def fromimage(im, flatten=0):
 
52
    """Takes a PIL image and returns a copy of the image in a Numeric container.
 
53
    If the image is RGB returns a 3-dimensional array:  arr[:,:,n] is each channel
 
54
 
 
55
    Optional arguments:
 
56
 
 
57
    - flatten (0): if true, the image is flattened by calling convert('F') on
 
58
    the image object before extracting the numerical data.  This flattens the
 
59
    color layers into a single grayscale layer.  Note that the supplied image
 
60
    object is NOT modified.
 
61
    """
 
62
    assert Image.isImageType(im), "Not a PIL image."
 
63
    if flatten:
 
64
        im = im.convert('F')
 
65
    mode = im.mode
 
66
    adjust = 0
 
67
    if mode == '1':
 
68
        im = im.convert(mode='L')
 
69
        mode = 'L'
 
70
        adjust = 1
 
71
    str = im.tostring()
 
72
    type = 'b'
 
73
    if mode == 'F':
 
74
        type = 'f'
 
75
    if mode == 'I':
 
76
        type = 'i'
 
77
    arr = Numeric.fromstring(str,type)
 
78
    shape = list(im.size)
 
79
    shape.reverse()
 
80
    if mode == 'P':
 
81
        arr.shape = shape
 
82
        if im.palette.rawmode != 'RGB':
 
83
            print "Warning: Image has invalid palette."
 
84
            return arr
 
85
        pal = Numeric.fromstring(im.palette.data,type)
 
86
        N = len(pal)
 
87
        pal.shape = (int(N/3.0),3)
 
88
        return arr, pal
 
89
    if mode in ['RGB','YCbCr']:
 
90
        shape += [3]
 
91
    elif mode in ['CMYK','RGBA']:
 
92
        shape += [4]
 
93
    arr.shape = shape
 
94
    if adjust:
 
95
        arr = (arr != 0)
 
96
    return arr
 
97
 
 
98
_errstr = "Mode is unknown or incompatible with input array shape."
 
99
def toimage(arr,high=255,low=0,cmin=None,cmax=None,pal=None,
 
100
            mode=None,channel_axis=None):
 
101
    """Takes a Numeric array and returns a PIL image.  The mode of the
 
102
    PIL image depends on the array shape, the pal keyword, and the mode
 
103
    keyword.
 
104
 
 
105
    For 2-D arrays, if pal is a valid (N,3) byte-array giving the RGB values
 
106
    (from 0 to 255) then mode='P', otherwise mode='L', unless mode is given
 
107
    as 'F' or 'I' in which case a float and/or integer array is made
 
108
 
 
109
    For 3-D arrays, the channel_axis argument tells which dimension of the
 
110
      array holds the channel data. 
 
111
    For 3-D arrays if one of the dimensions is 3, the mode is 'RGB'
 
112
      by default or 'YCbCr' if selected.  
 
113
    if the
 
114
 
 
115
    The Numeric array must be either 2 dimensional or 3 dimensional.
 
116
    """
 
117
    data = asarray(arr)
 
118
    if iscomplexobj(data):
 
119
        raise ValueError, "Cannot convert a complex-valued array."
 
120
    shape = list(data.shape)
 
121
    valid = len(shape)==2 or ((len(shape)==3) and \
 
122
                              ((3 in shape) or (4 in shape)))
 
123
    assert valid, "Not a suitable array shape for any mode."
 
124
    if len(shape) == 2:
 
125
        shape = (shape[1],shape[0]) # columns show up first
 
126
        if mode == 'F':
 
127
            image = Image.fromstring(mode,shape,data.astype('f').tostring())
 
128
            return image
 
129
        if mode in [None, 'L', 'P']:
 
130
            bytedata = bytescale(data,high=high,low=low,cmin=cmin,cmax=cmax)
 
131
            image = Image.fromstring('L',shape,bytedata.tostring())
 
132
            if pal is not None:
 
133
                image.putpalette(asarray(pal,typecode=_UInt8).tostring())
 
134
                # Becomes a mode='P' automagically.
 
135
            elif mode == 'P':  # default gray-scale
 
136
                pal = arange(0,256,1,typecode='b')[:,NewAxis] * \
 
137
                      ones((3,),typecode='b')[NewAxis,:]
 
138
                image.putpalette(asarray(pal,typecode=_UInt8).tostring())
 
139
            return image
 
140
        if mode == '1':  # high input gives threshold for 1
 
141
            bytedata = ((data > high)*255).astype('b')
 
142
            image = Image.fromstring('L',shape,bytedata.tostring())   
 
143
            image = image.convert(mode='1')
 
144
            return image
 
145
        if cmin is None:
 
146
            cmin = amin(ravel(data))
 
147
        if cmax is None:
 
148
            cmax = amax(ravel(data))
 
149
        data = (data*1.0 - cmin)*(high-low)/(cmax-cmin) + low
 
150
        if mode == 'I':
 
151
            image = Image.fromstring(mode,shape,data.astype('i').tostring())
 
152
        else:
 
153
            raise ValueError, _errstr
 
154
        return image
 
155
 
 
156
    # if here then 3-d array with a 3 or a 4 in the shape length.
 
157
    # Check for 3 in datacube shape --- 'RGB' or 'YCbCr'
 
158
    if channel_axis is None:
 
159
        if (3 in shape):
 
160
            ca = Numeric.nonzero(asarray(shape) == 3)[0]
 
161
        else:
 
162
            ca = Numeric.nonzero(asarray(shape) == 4)
 
163
            if len(ca):
 
164
                ca = ca[0]
 
165
            else:
 
166
                raise ValueError, "Could not find channel dimension."
 
167
    else:
 
168
        ca = channel_axis
 
169
 
 
170
    numch = shape[ca]
 
171
    if numch not in [3,4]:
 
172
        raise ValueError, "Channel axis dimension is not valid."
 
173
 
 
174
    bytedata = bytescale(data,high=high,low=low,cmin=cmin,cmax=cmax)
 
175
    if ca == 2:
 
176
        strdata = bytedata.tostring()
 
177
        shape = (shape[1],shape[0])
 
178
    elif ca == 1:
 
179
        strdata = transpose(bytedata,(0,2,1)).tostring()
 
180
        shape = (shape[2],shape[0])
 
181
    elif ca == 0:
 
182
        strdata = transpose(bytedata,(1,2,0)).tostring()
 
183
        shape = (shape[2],shape[1])
 
184
    if mode is None:
 
185
        if numch == 3: mode = 'RGB'
 
186
        else: mode = 'RGBA'
 
187
 
 
188
 
 
189
    if mode not in ['RGB','RGBA','YCbCr','CMYK']:
 
190
        raise ValueError, _errstr
 
191
 
 
192
    if mode in ['RGB', 'YCbCr']:
 
193
        assert numch == 3, "Invalid array shape for mode."
 
194
    if mode in ['RGBA', 'CMYK']:
 
195
        assert numch == 4, "Invalid array shape for mode."
 
196
 
 
197
    # Here we know data and mode is coorect
 
198
    image = Image.fromstring(mode, shape, strdata)
 
199
    return image
 
200
 
 
201
def imrotate(arr,angle,interp='bilinear'):
 
202
    """Rotate an image counter-clockwise by angle degrees.
 
203
 
 
204
    Interpolation methods can be:
 
205
        'nearest' :  for nearest neighbor
 
206
        'bilinear' : for bilinear
 
207
        'cubic' or 'bicubic' : for bicubic 
 
208
    """
 
209
    arr = asarray(arr)
 
210
    func = {'nearest':0,'bilinear':2,'bicubic':3,'cubic':3}
 
211
    im = toimage(arr)
 
212
    im = im.rotate(angle,resample=func[interp])
 
213
    return fromimage(im)
 
214
 
 
215
def imresize(arr,newsize,interp='bilinear',mode=None):
 
216
    newsize=list(newsize)
 
217
    newsize.reverse()
 
218
    newsize = tuple(newsize)
 
219
    arr = asarray(arr)
 
220
    func = {'nearest':0,'bilinear':2,'bicubic':3,'cubic':3}
 
221
    im = toimage(arr,mode=mode)
 
222
    im = im.resize(newsize,resample=func[interp])
 
223
    return fromimage(im)
 
224
    
 
225
def imshow(arr):
 
226
    """Simple showing of an image through an external viewer.
 
227
    """
 
228
    im = toimage(arr)
 
229
    if (len(arr.shape) == 3) and (arr.shape[2] == 4):
 
230
        try:
 
231
            import os
 
232
            im.save('/tmp/scipy_imshow.png')
 
233
            if os.system("(xv /tmp/scipy_imshow.png; rm -f /tmp/scipy_imshow.png)&"):
 
234
                raise RuntimeError
 
235
            return
 
236
        except:
 
237
            print "Warning: Alpha channel may not be handled correctly."
 
238
            
 
239
    im.show()
 
240
    return
 
241
 
 
242
def imresize(arr,size):
 
243
    """Resize an image.
 
244
 
 
245
    If size is an integer it is a percentage of current size.
 
246
    If size is a float it is a fraction of current size.
 
247
    If size is a tuple it is the size of the output image.
 
248
    """
 
249
    im = toimage(arr)
 
250
    ts = type(size)
 
251
    if ts is types.IntType:
 
252
        size = size / 100.0
 
253
    if type(size) is types.FloatType:
 
254
        size = (im.size[0]*size,im.size[1]*size)
 
255
    else:
 
256
        size = (size[1],size[0])
 
257
    imnew = im.resize(size)
 
258
    return fromimage(imnew)
 
259
 
 
260
 
 
261
def imfilter(arr,ftype):
 
262
    """Simple filtering of an image.
 
263
 
 
264
    type can be:
 
265
            'blur', 'contour', 'detail', 'edge_enhance', 'edge_enhance_more',
 
266
            'emboss', 'find_edges', 'smooth', 'smooth_more', 'sharpen'
 
267
    """
 
268
    _tdict = {'blur':ImageFilter.BLUR,
 
269
              'contour':ImageFilter.CONTOUR,
 
270
              'detail':ImageFilter.DETAIL,
 
271
              'edge_enhance':ImageFilter.EDGE_ENHANCE,
 
272
              'edge_enhance_more':ImageFilter.EDGE_ENHANCE_MORE,
 
273
              'emboss':ImageFilter.EMBOSS,
 
274
              'find_edges':ImageFilter.FIND_EDGES,
 
275
              'smooth':ImageFilter.SMOOTH,
 
276
              'smooth_more':ImageFilter.SMOOTH_MORE,
 
277
              'sharpen':ImageFilter.SHARPEN
 
278
              }
 
279
 
 
280
    im = toimage(arr)
 
281
    if ftype not in _tdict.keys():
 
282
        raise ValueError, "Unknown filter type."
 
283
    return fromimage(im.filter(_tdict[ftype]))
 
284
           
 
285
 
 
286
def radon(arr,theta=None):
 
287
    if theta is None:
 
288
        theta = mgrid[0:180]
 
289
    s = zeros((arr.shape[1],len(theta)),'d')
 
290
    k = 0
 
291
    for th in theta:
 
292
        im = imrotate(arr,-th)
 
293
        s[:,k] = sum(im,axis=0)
 
294
        k += 1
 
295
    return s
 
296