1 """
2 Provides SheetCoordinateSystem, allowing conversion between the two
3 coordinate systems used in Topographica.
4
5 'Sheet coordinates' allow simulation parameters to be specified in
6 units that are density-independent, whereas 'matrix coordinates'
7 provide a means of realizing the continuous sheets.
8
9 Hence we can have a pair of 'sheet coordinates' (x,y); floating-point
10 Cartesian coordinates indicating an arbitrary point on the sheet's plane.
11 We can also have a pair of 'matrix coordinates' (r,c), which are used
12 to address an underlying matrix. These matrix coordinates are also
13 floating-point numbers to allow precise conversion between the two
14 schemes. Where it is necessary to address a specific element of the
15 matrix (as is often the case in calculations), we also have the usual
16 matrix index coordinates (r_idx, c_idx). We refer to these as
17 matrixidx coordinates. SheetCoordinateSystem provies methods for converting
18 between sheet and matrix coordinates, as well as sheet and matrixidx
19 coordinates.
20
21 Everyone should use these facilities for conversions between the two
22 coordinate systems to guarantee consistency.
23
24
25
26
27 Example of how the matrix stores the representation of the Sheet
28 ================================================================
29
30 For the purposes of this example, assume the goal is a Sheet with
31 density=3 that has a 1 at (-1/2,-1/2), a 5 at (0.0,0.0), and a 9 at
32 (1/2,1/2). More precisely, for this Sheet,
33
34 the continuous area from -1/2,-1/2 to -1/6,-1/6 has value 1,
35 the continuous area from -1/6,-1/6 to 1/6,1/6 has value 5, and
36 the continuous area from 1/6,1/6 to 1/2,1/2 has value 9.
37
38 With the rest of the elements filled in, the Sheet would look like::
39
40 (-1/2,1/2) -+-----+-----+-----+- (1/2,1/2)
41 | | | |
42 | 7 | 8 | 9 |
43 | | | |
44 (-1/2,1/6) -+-----+-----+-----+- (1/2,1/6)
45 | | | |
46 | 4 | 5 | 6 |
47 | | | |
48 (-1/2,-1/6) -+-----+-----+-----+- (1/2,-1/6)
49 | | | |
50 | 1 | 2 | 3 |
51 | | | |
52 (-1/2,-1/2) -+-----+-----+-----+- (1/2,-1/2)
53
54 where element 5 is centered on 0.0,0.0. A matrix that would match
55 these Sheet coordinates is::
56
57 [[7 8 9]
58 [4 5 6]
59 [1 2 3]]
60
61 If we have such a matrix, we can access it in one of two ways: Sheet
62 or matrix/matrixidx coordinates. In matrixidx coordinates, the matrix is indexed
63 by rows and columns, and it is possible to ask for the element at
64 location [0,2] (which returns 9 as in any normal row-major matrix).
65 But the values can additionally be accessed in Sheet coordinates,
66 where the matrix is indexed by a point in the Cartesian plane. In
67 Sheet coordinates, it is possible to ask for the element at location
68 (0.3,0.02), which returns floating-point matrix coordinates that can
69 be cropped to give the nearest matrix element, namely the one with
70 value 6.
71
72 Of course, it would be an error to try to pass matrix coordinates like
73 [0,2] to the sheet2matrix calls; the result would be a value far
74 outside of the actual matrix.
75
76 $Id: sheetcoords.py 10613 2009-10-03 11:42:49Z ceball $
77 """
78
79 __version__ = '$Revision: 10613 $'
80
81
82 from numpy import array,floor,ceil,round_,arange
83
84 from boundingregion import BoundingBox
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 -class SheetCoordinateSystem(object):
127 """
128 Provides methods to allow conversion between sheet and matrix
129 coordinates.
130 """
132 return self.__xdensity
134 return self.__ydensity
137
138
139
140 xdensity = property(__get_xdensity,
141 doc="""The spacing between elements in an underlying
142 matrix representation, in the x direction.""")
143
144 ydensity = property(__get_ydensity,
145 doc="""The spacing between elements in an underlying
146 matrix representation, in the y direction.""")
147
148 shape = property(__get_shape)
149
150
151 - def __init__(self,bounds,xdensity,ydensity=None):
152 """
153 Store the bounds (as l,b,r,t in an array), xdensity, and
154 ydensity.
155
156 If ydensity is not specified, it is assumed that the specified
157 xdensity is nominal and that the true xdensity should be
158 calculated. The top and bottom bounds are adjusted so that
159 the ydensity is equal to the xdensity.
160
161 If both xdensity and ydensity are specified, these and the bounds
162 are taken to be exact and are not adjusted.
163 """
164 if not ydensity:
165 bounds,xdensity = self.__equalize_densities(bounds,xdensity)
166
167 self.bounds = bounds
168 self.__set_xdensity(xdensity)
169 self.__set_ydensity(ydensity or xdensity)
170
171 self.lbrt = array(bounds.lbrt())
172
173 r1,r2,c1,c2 = Slice._boundsspec2slicespec(self.lbrt,self)
174 self.__shape = (r2-r1,c2-c1)
175
176
177
178
179
183
187
188
190 """
191 Calculate the true density along x, and adjust the top and
192 bottom bounds so that the density along y will be equal.
193
194 Returns (adjusted_bounds,true_density)
195 """
196 left,bottom,right,top = nominal_bounds.lbrt()
197 width = right-left; height = top-bottom
198 center_y = bottom + height/2.0
199
200
201 true_density = int(nominal_density*(width))/float(width)
202
203 n_cells = round(height*true_density,0)
204 adjusted_half_height = n_cells/true_density/2.0
205
206
207
208 return (BoundingBox(points=((left, center_y-adjusted_half_height),
209 (right, center_y+adjusted_half_height))),
210 true_density)
211
212
214 """
215 Convert a point (x,y) in Sheet coordinates to continuous matrix
216 coordinates.
217
218 Returns (float_row,float_col), where float_row corresponds to y,
219 and float_col to x.
220
221 Valid for scalar or array x and y.
222
223 Note about Bounds
224 For a Sheet with BoundingBox(points=((-0.5,-0.5),(0.5,0.5)))
225 and density=3, x=-0.5 corresponds to float_col=0.0 and x=0.5
226 corresponds to float_col=3.0. float_col=3.0 is not inside the
227 matrix representing this Sheet, which has the three columns
228 (0,1,2). That is, x=-0.5 is inside the BoundingBox but x=0.5
229 is outside. Similarly, y=0.5 is inside (at row 0) but y=-0.5
230 is outside (at row 3) (it's the other way round for y because
231 the matrix row index increases as y decreases).
232 """
233
234
235
236
237 float_col = (x-self.lbrt[0]) * self.__xdensity
238 float_row = (self.lbrt[3]-y) * self.__ydensity
239 return float_row, float_col
240
241
243 """
244 Convert a point (x,y) in sheet coordinates to the integer row and
245 column index of the matrix cell in which that point falls, given a
246 bounds and density. Returns (row,column).
247
248 Note that if coordinates along the right or bottom boundary are
249 passed into this function, the returned matrix coordinate of the
250 boundary will be just outside the matrix, because the right and
251 bottom boundaries are exclusive.
252
253 Valid for scalar or array x and y.
254 """
255 r,c = self.sheet2matrix(x,y)
256 r = floor(r)
257 c = floor(c)
258
259
260 if hasattr(r,'astype'):
261 return r.astype(int), c.astype(int)
262 else:
263 return int(r),int(c)
264
265
267 """
268 Convert a floating-point location (float_row,float_col) in matrix
269 coordinates to its corresponding location (x,y) in sheet
270 coordinates.
271
272 Valid for scalar or array float_row and float_col.
273
274 Inverse of sheet2matrix().
275 """
276 x = float_col*self.__xstep + self.lbrt[0]
277 y = self.lbrt[3] - float_row*self.__ystep
278 return x, y
279
280
282 """
283 Return (x,y) where x and y are the floating point coordinates of
284 the *center* of the given matrix cell (row,col). If the matrix cell
285 represents a 0.2 by 0.2 region, then the center location returned
286 would be 0.1,0.1.
287
288 NOTE: This is NOT the strict mathematical inverse of
289 sheet2matrixidx(), because sheet2matrixidx() discards all but the integer
290 portion of the continuous matrix coordinate.
291
292 Valid only for scalar or array row and col.
293 """
294 x,y = self.matrix2sheet((row+0.5), (col+0.5))
295
296
297
298
299
300
301 return round_(x,10),round_(y,10)
302
303
305 """
306 Given arbitary sheet coordinates, return the sheet coordinates
307 of the center of the closest unit.
308 """
309 return self.matrixidx2sheet(*self.sheet2matrixidx(x,y))
310
311
313 """
314 Return x,y where x is a vector of sheet coordinates
315 representing the x-center of each matrix cell, and y
316 represents the corresponding y-center of the cell.
317 """
318 rows,cols = self.shape
319 return self.matrixidx2sheet(arange(rows),arange(cols))
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339 from numpy import int32,ndarray
340
341 -class Slice(ndarray):
342 """
343 Represents a slice of a SheetCoordinateSystem; i.e., an array
344 specifying the row and column start and end points for a submatrix
345 of the SheetCoordinateSystem.
346
347 The slice is created from the supplied bounds by calculating the
348 slice that corresponds most closely to the specified bounds.
349 Therefore, the slice does not necessarily correspond exactly to
350 the specified bounds. The bounds that do exactly correspond to the
351 slice are available via the 'bounds' attribute.
352
353 Note that the slice does not respect the bounds of the
354 SheetCoordinateSystem, and that actions such as translate() also
355 do not respect the bounds. To ensure that the slice is within the
356 SheetCoordinateSystem's bounds, use crop_to_sheet().
357 """
358
362
363 __slots__ = []
364
365 - def __new__(cls, bounds, sheet_coordinate_system, force_odd=False,
366 min_matrix_radius=1):
367 """
368 Create a slice of the given sheet_coordinate_system from the
369 specified bounds.
370 """
371
372
373
374
375 if force_odd:
376 slicespec=Slice._createoddslicespec(bounds,sheet_coordinate_system,
377 min_matrix_radius)
378 else:
379 slicespec=Slice._boundsspec2slicespec(bounds.lbrt(),sheet_coordinate_system)
380
381
382
383 a = array(slicespec, dtype=int32, copy=False).view(cls)
384 return a
385
386
388 """
389 Return the submatrix of the given matrix specified by this
390 slice.
391
392 Equivalent to computing the intersection between the
393 SheetCoordinateSystem's bounds and the bounds, and
394 returning the corresponding submatrix of the given matrix.
395
396 The submatrix is just a view into the sheet_matrix; it is not
397 an independent copy.
398 """
399 return matrix[self[0]:self[1],self[2]:self[3]]
400
401
402
403
404
405
406
407
408
409
410
411
413 """
414 Return the correct slice for a weights/mask matrix at this
415 ConnectionField's location on the sheet (i.e. for getting
416 the correct submatrix of the weights or mask in case the
417 unit is near the edge of the sheet).
418 """
419 sheet_rows,sheet_cols = sheet_coord_system.shape
420
421
422 n_rows,n_cols = self.shape_on_sheet()
423
424
425 center_row,center_col = sheet_coord_system.sheet2matrixidx(x,y)
426
427 c1 = -min(0, center_col-n_cols/2)
428 r1 = -min(0, center_row-n_rows/2)
429 c2 = -max(-n_cols, center_col-sheet_cols-n_cols/2)
430 r2 = -max(-n_rows, center_row-sheet_rows-n_rows/2)
431
432 self.set((r1,r2,c1,c2))
433
434
435
437 """
438 Offset the bounds_template to this cf's location and store the
439 result in the 'bounds' attribute.
440
441 Also stores the input_sheet_slice for access by C.
442 """
443
444 cf_row,cf_col = sheet_coord_system.sheet2matrixidx(x,y)
445
446
447
448
449 bounds_x,bounds_y=self.compute_bounds(sheet_coord_system).centroid()
450
451 b_row,b_col=sheet_coord_system.sheet2matrixidx(bounds_x,bounds_y)
452
453 row_offset = cf_row-b_row
454 col_offset = cf_col-b_col
455 self.translate(row_offset,col_offset)
456
457
458
459
461 """
462 Translate the slice by the specified number of rows
463 and columns.
464 """
465 self+=[r,r,c,c]
466
467 - def set(self,slice_specification):
468 """Set this slice from some iterable that specifies (r1,r2,c1,c2)."""
469 self.put([0,1,2,3],slice_specification)
470
472 """Return the shape of the array that this Slice would give on its sheet."""
473 return self[1]-self[0],self[3]-self[2]
474
476 """Crop the slice to the SheetCoordinateSystem's bounds."""
477 maxrow,maxcol = sheet_coord_system.shape
478
479 self[0] = max(0,self[0])
480 self[1] = min(maxrow,self[1])
481 self[2] = max(0,self[2])
482 self[3] = min(maxcol,self[3])
483
484
485
486
487
488
489 @staticmethod
491 """
492 Create the 'odd' Slice that best approximates the specifed
493 sheet-coordinate bounds.
494
495 The supplied bounds are translated to have a center at the
496 center of one of the sheet's units (we arbitrarily use the
497 center unit), and then these bounds are converted to a slice
498 in such a way that the slice exactly includes all units whose
499 centers are within the bounds (see boundsspec2slicespec()).
500 However, to ensure that the bounds are treated symmetrically,
501 we take the right and bottom bounds and reflect these about
502 the center of the slice (i.e. we take the 'xradius' to be
503 right_col-center_col and the 'yradius' to be
504 bottom_col-center_row). Hence, if the bounds happen to go
505 through units, if the units are included on the right and
506 bottom bounds, they will be included on the left and top
507 bounds. This ensures that the slice has odd dimensions.
508 """
509 bounds_xcenter,bounds_ycenter=bounds.centroid()
510 sheet_rows,sheet_cols = scs.shape
511
512
513 center_row,center_col = sheet_rows/2,sheet_cols/2
514 unit_xcenter,unit_ycenter=scs.matrixidx2sheet(center_row,
515 center_col)
516
517 bounds.translate(unit_xcenter-bounds_xcenter,
518 unit_ycenter-bounds_ycenter)
519
520
521 r1,r2,c1,c2 = Slice._boundsspec2slicespec(bounds.lbrt(),scs)
522
523
524 xrad=max(c2-center_col-1,min_matrix_radius)
525 yrad=max(r2-center_row-1,min_matrix_radius)
526
527 r2=center_row+yrad+1
528 c2=center_col+xrad+1
529 r1=center_row-yrad
530 c1=center_col-xrad
531
532
533
534
535
536
537
538 return (r1,r2,c1,c2)
539
540
541
542
543
544
545
546
547
548
549
550 @staticmethod
552 """
553 Convert an iterable boundsspec (supplying l,b,r,t of a
554 BoundingRegion) into a Slice specification.
555
556 Includes all units whose centers are within the specified
557 sheet-coordinate bounds specified by boundsspec.
558
559 Exact inverse of _slicespec2boundsspec().
560 """
561 l,b,r,t = boundsspec
562
563 t_m,l_m = scs.sheet2matrix(l,t)
564 b_m,r_m = scs.sheet2matrix(r,b)
565
566 l_idx = int(ceil(l_m-0.5))
567 t_idx = int(ceil(t_m-0.5))
568
569 r_idx = int(floor(r_m+0.5))
570 b_idx = int(floor(b_m+0.5))
571
572 return t_idx,b_idx,l_idx,r_idx
573
574
575 @staticmethod
577 """
578 Convert an iterable slicespec (supplying r1,r2,c1,c2 of a
579 Slice) into a BoundingRegion specification.
580
581 Exact inverse of _boundsspec2slicespec().
582 """
583 r1,r2,c1,c2 = slicespec
584
585 left,bottom = scs.matrix2sheet(r2,c1)
586 right, top = scs.matrix2sheet(r1,c2)
587
588 return ((left,bottom),(right,top))
589