Package topo :: Package patterns :: Module image
[hide private]
[frames] | no frames]

Source Code for Module topo.patterns.image

  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  # PIL Image is imported as PIL because we have our own Image PatternGenerator 
  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   
26 -class PatternSampler(ParameterizedObject):
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]) # getdata() returns transposed image? 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 # create new pattern sample, filled initially with the background value 104 pattern_sample = ones(x.shape, Float)*self.background_value 105 106 # if the height or width is zero, there's no pattern to display... 107 if width==0 or height==0: 108 return pattern_sample 109 110 # scale the supplied coordinates to match the pattern being at density=1 111 x*=sheet_xdensity 112 y*=sheet_ydensity 113 114 # scale according to initial pattern scaling selected (size_normalization) 115 if not scaling=='original': 116 self.__apply_size_normalization(x,y,sheet_xdensity,sheet_ydensity,scaling) 117 118 # scale according to user-specified width and height 119 x/=width 120 y/=height 121 122 # convert the sheet (x,y) coordinates to matrixidx (r,c) ones 123 r,c = self.pattern_sheet.sheet2matrixidx(x,y) 124 125 # now sample pattern at the (r,c) corresponding to the supplied (x,y) 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 # CEBALERT: is there a more Numeric way to do this? 131 rows,cols = pattern_sample.shape 132 for i in xrange(rows): 133 for j in xrange(cols): 134 # indexes outside the pattern are left with the background color 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 # Added by Tikesh for presenting stereo images; may not be needed anymore
142 - def get_image_size(self):
143 r,c=self.pattern_array.shape 144 return r,c
145 146
147 - def __apply_size_normalization(self,x,y,sheet_xdensity,sheet_ydensity,scaling):
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 # Instead of an if-test, could have a class of this type of 157 # function (c.f. OutputFunctions, etc)... 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
182 -def edge_average(a):
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
199 -class FastPatternSampler(ParameterizedObject):
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 # JPALERT: Right now this ignores all options and just fits the image into given array. 230 # It needs to be fleshed out to properly size and crop the 231 # image given the options. (maybe this class needs to be 232 # redesigned? The interface to this function is pretty inscrutable.) 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