1 """
2 PatternGenerators based on bitmap images stored in files.
3
4 $Id: image.py 8001 2008-02-18 15:15:10Z ceball $
5 """
6
7
8 import Image as PIL
9 import ImageOps
10
11 from numpy.oldnumeric import array, Float, sum, ravel, ones
12
13 from topo.base.boundingregion import BoundingBox
14 from topo.base.parameterclasses import Number, Parameter, Enumeration, Integer,\
15 ClassSelectorParameter
16 from topo.base.parameterclasses import StringParameter
17 from topo.base.parameterizedobject import ParameterizedObject
18 from topo.base.patterngenerator import PatternGenerator
19 from topo.base.sheetcoords import SheetCoordinateSystem
20
21 from topo.outputfns.basic import DivisiveNormalizeLinf,IdentityOF,OutputFn
22
23 from topo.misc.filepaths import Filename
24
25
27 """
28 Stores a SheetCoordinateSystem whose activity represents the
29 supplied pattern_array, and when called will resample that array
30 at the supplied Sheet coordinates according to the supplied
31 scaling parameters.
32
33 (x,y) coordinates outside the pattern_array are returned as the
34 background value.
35 """
36
37 - def __init__(self, pattern_array=None, image=None, whole_pattern_output_fn=IdentityOF(), background_value_fn=None):
38 """
39 Create a SheetCoordinateSystem whose activity is pattern_array
40 (where pattern_array is a Numeric array), modified in place by
41 whole_pattern_output_fn.
42
43 If supplied, background_value_fn must accept an array and return a scalar.
44 """
45
46 super(PatternSampler,self).__init__()
47
48 if pattern_array is not None and image is not None:
49 raise ValueError("PatternSampler instances can have a pattern or an image, but not both.")
50 elif pattern_array is not None:
51 pass
52 elif image is not None:
53 pattern_array = array(image.getdata(),Float)
54 pattern_array.shape = (image.size[::-1])
55 else:
56 raise ValueError("PatternSampler instances must have a pattern or an image.")
57
58 rows,cols = pattern_array.shape
59
60 self.pattern_sheet = SheetCoordinateSystem(xdensity=1.0,ydensity=1.0,
61 bounds=BoundingBox(points=((-cols/2.0,-rows/2.0),
62 ( cols/2.0, rows/2.0))))
63
64 whole_pattern_output_fn(pattern_array)
65 self.pattern_sheet.activity = pattern_array
66
67 if not background_value_fn:
68 self.background_value = 0.0
69 else:
70 self.background_value = background_value_fn(self.pattern_sheet.activity)
71
72
73 - def __call__(self, x, y, sheet_xdensity, sheet_ydensity, scaling, width=1.0, height=1.0):
74 """
75 Return pixels from the pattern at the given Sheet (x,y) coordinates.
76
77 sheet_density should be the density of the sheet on which the pattern
78 is to be drawn.
79
80 scaling determines how the pattern is scaled initially; it can be:
81
82 'stretch_to_fit': scale both dimensions of the pattern so they
83 would fill a Sheet with bounds=BoundingBox(radius=0.5)
84 (disregards the original's aspect ratio).
85
86 'fit_shortest': scale the pattern so that its shortest
87 dimension is made to fill the corresponding dimension on a
88 Sheet with bounds=BoundingBox(radius=0.5) (maintains the
89 original's aspect ratio).
90
91 'fit_longest': scale the pattern so that its longest dimension
92 is made to fill the corresponding dimension on a Sheet with
93 bounds=BoundingBox(radius=0.5) (maintains the original's
94 aspect ratio).
95
96 'original': no scaling is applied; one pixel of the pattern is
97 put in one unit of the sheet on which the pattern being
98 displayed.
99
100 The pattern is further scaled according to the supplied width and height.
101 """
102
103
104 pattern_sample = ones(x.shape, Float)*self.background_value
105
106
107 if width==0 or height==0:
108 return pattern_sample
109
110
111 x*=sheet_xdensity
112 y*=sheet_ydensity
113
114
115 if not scaling=='original':
116 self.__apply_size_normalization(x,y,sheet_xdensity,sheet_ydensity,scaling)
117
118
119 x/=width
120 y/=height
121
122
123 r,c = self.pattern_sheet.sheet2matrixidx(x,y)
124
125
126 pattern_rows,pattern_cols = self.pattern_sheet.activity.shape
127 if pattern_rows==0 or pattern_cols==0:
128 return pattern_sample
129 else:
130
131 rows,cols = pattern_sample.shape
132 for i in xrange(rows):
133 for j in xrange(cols):
134
135 if self.pattern_sheet.bounds.contains_exclusive(x[i,j],y[i,j]):
136 pattern_sample[i,j] = self.pattern_sheet.activity[r[i,j],c[i,j]]
137
138 return pattern_sample
139
140
141
143 r,c=self.pattern_array.shape
144 return r,c
145
146
148 """
149 Initial pattern scaling (size_normalization), relative to the
150 default retinal dimension of 1.0 in sheet coordinates.
151
152 See __call__ for a description of the various scaling options.
153 """
154 pattern_rows,pattern_cols = self.pattern_sheet.activity.shape
155
156
157
158 if scaling=='stretch_to_fit':
159 x_sf,y_sf = pattern_cols/sheet_xdensity, pattern_rows/sheet_ydensity
160 x*=x_sf; y*=y_sf
161
162 elif scaling=='fit_shortest':
163 if pattern_rows<pattern_cols:
164 sf = pattern_rows/sheet_ydensity
165 else:
166 sf = pattern_cols/sheet_xdensity
167 x*=sf;y*=sf
168
169 elif scaling=='fit_longest':
170 if pattern_rows<pattern_cols:
171 sf = pattern_cols/sheet_xdensity
172 else:
173 sf = pattern_rows/sheet_ydensity
174 x*=sf;y*=sf
175
176 else:
177 raise ValueError("Unknown scaling option",scaling)
178
179
180
181 from numpy.oldnumeric import sum,ravel
183 "Return the mean value around the edge of an array."
184
185 if len(ravel(a)) < 2:
186 return float(a[0])
187 else:
188 top_edge = a[0]
189 bottom_edge = a[-1]
190 left_edge = a[1:-1,0]
191 right_edge = a[1:-1,-1]
192
193 edge_sum = sum(top_edge) + sum(bottom_edge) + sum(left_edge) + sum(right_edge)
194 num_values = len(top_edge)+len(bottom_edge)+len(left_edge)+len(right_edge)
195
196 return float(edge_sum)/num_values
197
198
200 """
201 A fast-n-dirty pattern sampler using Python Imaging Library
202 routines. Currently this sampler doesn't support user-specified
203 scaling or cropping but rather simply scales and crops the image
204 to fit the given matrix size without distorting the aspect ratio
205 of the original picture.
206 """
207
208 sampling_method = Integer(default=PIL.NEAREST,doc="""
209 Python Imaging Library sampling method for resampling an image.
210 Defaults to Image.NEAREST.""")
211
212
213 - def __init__(self, pattern=None, image=None, whole_pattern_output_fn=IdentityOF(), background_value_fn=None):
214 super(FastPatternSampler,self).__init__()
215
216 if pattern and image:
217 raise ValueError("PatternSampler instances can have a pattern or an image, but not both.")
218 elif pattern is not None:
219 self.image = PIL.new('L',pattern.shape)
220 self.image.putdata(pattern.ravel())
221 elif image is not None:
222 self.image = image
223 else:
224 raise ValueError("PatternSampler instances must have a pattern or an image.")
225
226
227 - def __call__(self, x, y, sheet_xdensity, sheet_ydensity, scaling, width=1.0, height=1.0):
228
229
230
231
232
233
234 im = ImageOps.fit(self.image,x.shape,self.sampling_method)
235
236 result = array(im.getdata(),dtype=Float)
237 result.shape = im.size[::-1]
238
239 return result
240
241