/usr/share/pyshared/chaco/subdivision_mapper.py is in python-chaco 4.1.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 | """ Defines the SubdivisionDataMapper and SubdivisionLineDataMapper classes.
"""
# Major library imports
import math
from numpy import array, arange, concatenate, searchsorted, nonzero, transpose, \
argsort, zeros, sort, vstack
import numpy
# Enthought library imports
from traits.api import List, Array, Tuple, Int, Float
# Local, relative imports
from datamapper import AbstractDataMapper, right_shift, left_shift, \
sort_points, ArraySortTrait, \
array_zip
from subdivision_cells import AbstractCell, Cell, RangedCell, find_runs, \
arg_find_runs
class SubdivisionDataMapper(AbstractDataMapper):
"""
A data mapper that uses a uniform grid of rectangular cells. It doesn't make
any assumptions about the continuity of the input data set, and explicitly
stores each point in the data set in its cell.
If the incoming data is ordered in some fashion such that most cells end
up with large ranges of data, then it's better to use the
SubdivisionLineDataMapper subclass.
"""
celltype = Cell
_last_region = List(Tuple)
_cellgrid = Array # a Numeric array of Cell objects
_points_per_cell = Int(100) # number of datapoints/cell to shoot for
_cell_lefts = Array # locations of left edge for all cells
_cell_bottoms = Array # locations of bottom edge for all cells
_cell_extents = Tuple(Float, Float) # the width and height of a cell
#-------------------------------------------------------------------
# Public AbstractDataMapper methods
#-------------------------------------------------------------------
def get_points_near(self, pointlist, radius=0.0):
if radius != 0:
# tmp is a list of list of arrays
d = 2*radius
cell_points = [ self.get_points_in_rect((px-radius,py-radius,d,d))
for (px,py) in pointlist ]
else:
indices = self._get_indices_for_points(pointlist)
cells = [self._cellgrid[i,j] for (i,j) in indices]
self._last_region = self._cells_to_rects(indices)
# unique-ify the list of cells
cell_points = [c.get_points() for c in set(cells)]
return vstack(cell_points)
def get_points_in_rect(self, rect):
x_span = (rect[0], rect[0]+rect[2])
y_span = (rect[1], rect[1]+rect[3])
min_i, max_i = searchsorted(self._cell_lefts, x_span) - 1
min_j, max_j = searchsorted(self._cell_bottoms, y_span) - 1
cellpts = [ self._cellgrid[i,j].get_points()
for i in range(min_i, max_i+1) \
for j in range(min_j, max_j+1) ]
self._last_region = ( self._cell_lefts[min_i], self._cell_bottoms[min_j], \
(max_i - min_i + 1) * self._cell_extents[0], \
(max_j - min_j + 1) * self._cell_extents[1] )
return vstack(cellpts)
def get_last_region(self):
return self._last_region
#-------------------------------------------------------------------
# AbstractDataMapper's abstract private methods
#-------------------------------------------------------------------
def _update_datamap(self):
self._last_region = []
# Create a new grid of the appropriate size, initialize it with new
# Cell instance (of type self.celltype), and perform point insertion
# on the new data.
if self._data is None:
self._cellgrid = array([], dtype=object)
self._cell_lefts = array([])
self._cell_bottoms = array([])
else:
num_x_cells, num_y_cells = self._calc_grid_dimensions()
self._cellgrid = zeros((num_x_cells, num_y_cells), dtype=object)
for i in range(num_x_cells):
for j in range(num_y_cells):
self._cellgrid[i,j] = self.celltype(parent=self)
ll, ur = self._extents
cell_width = ur[0]/num_x_cells
cell_height = ur[1]/num_y_cells
# calculate the left and bottom edges of all the cells and store
# them in two arrays
self._cell_lefts = arange(ll[0], ll[0]+ur[0]-cell_width/2, step=cell_width)
self._cell_bottoms = arange(ll[1], ll[1]+ur[1]-cell_height/2, step=cell_height)
self._cell_extents = (cell_width, cell_height)
# insert the data points
self._basic_insertion(self.celltype)
return
def _clear(self):
self._last_region = []
self._cellgrid = []
self._cell_lefts = []
self._cell_bottoms = []
self._cell_extents = (0,0)
return
def _sort_order_changed(self, old, new):
# since trait event notification only happens if the value has changed,
# and there are only two types of sorting, it's safe to just reverse our
# internal _data object
self._data = self._data[::-1]
for cell in self._cellgrid:
# since cellgrid is a Numeric array, iterating over it produces
# a length-1 array
cell[0].reverse_indices()
return
#-------------------------------------------------------------------
# helper private methods
#-------------------------------------------------------------------
def _calc_grid_dimensions(self):
numpoints = self._data.shape[0]
numcells = numpoints / self._points_per_cell
ll, ur = self._extents
aspect_ratio = (ur[0]-ll[0]) / (ur[1]-ll[1])
num_y_cells = int(math.sqrt(numcells / aspect_ratio))
num_x_cells = int(aspect_ratio * num_y_cells)
if num_y_cells == 0:
num_y_cells += 1
if num_y_cells*num_x_cells*self._points_per_cell < numpoints:
num_x_cells += 1
return (num_x_cells, num_y_cells)
def _basic_insertion(self, celltype):
# generate a list of which cell each point in self._data belongs in
cell_indices = self._get_indices_for_points(self._data)
# We now look for ranges of points belonging to the same cell.
# 1. shift lengthwise and difference; runs of cells with the same
# (i,j) indices will be zero, and nonzero value for i or j will
# indicate a transition to a new cell. (Just like find_runs().)
differences = cell_indices[1:] - cell_indices[:-1]
# Since nonzero() only works for 1D arrays, we merge the X and Y columns
# together to detect any point where either X or Y are nonzero. We have
# to add 1 because we shifted cell_indices before differencing (above).
diff_indices = nonzero(differences[:,0] + differences[:,1])[0] + 1
start_indices = concatenate([[0], diff_indices])
end_indices = concatenate([diff_indices, [len(self._data)]])
for start,end in zip(start_indices, end_indices):
gridx, gridy = cell_indices[start] # can use 'end' here just as well
if celltype == RangedCell:
self._cellgrid[gridx,gridy].add_ranges([(start,end)])
else:
self._cellgrid[gridx,gridy].add_indices(range(start,end))
return
def _get_indices_for_points(self, pointlist):
"""
Given an input Nx2 array of points, returns a list Nx2 corresponding
to the column and row indices into the cell grid.
"""
x_array = searchsorted(self._cell_lefts, pointlist[:,0]) - 1
y_array = searchsorted(self._cell_bottoms, pointlist[:,1]) - 1
return array_zip(x_array, y_array)
def _cells_to_rects(self, cells):
"""
Converts the extents of a list of cell grid coordinates (i,j) into
a list of rect tuples (x,y,w,h). The set should be disjoint, but may
or may not be minimal.
"""
# Since this function is generally used to generate clipping regions
# or other screen-related graphics, we should try to return large
# rectangular blocks if possible.
# For now, we just look for horizontal runs and return those.
cells = array(cells)
y_sorted = sort_points(cells, index=1) # sort acoording to row
rownums = sort(array(tuple(set(cells[:,1]))))
row_start_indices = searchsorted(y_sorted[:,1], rownums)
row_end_indices = left_shift(row_start_indices, len(cells))
rects = []
for rownum, start, end in zip(rownums, row_start_indices, row_end_indices):
# y_sorted is sorted by the J (row) coordinate, so after we
# extract the column indices, we need to sort them before
# passing them to find_runs().
grid_column_indices = sort(y_sorted[start:end][:,0])
#pdb.set_trace()
#print grid_column_indices.shape
for span in find_runs(grid_column_indices):
x = self._cell_lefts[span[0]]
y = self._cell_bottoms[rownum]
w = (span[-1] - span[0] + 1) * self._cell_extents[0]
h = self._cell_extents[1]
rects.append((x,y,w,h))
return rects
#~ def _array_insertion(self, celltype):
#~ # use searchsorted() to determine where the borders split the
#~ # data array
#~ x_bins = searchsorted(data[:,0], self._cell_lefts[1:])
#~ x_bins_rshift = right_shift(x_bins, 0)
#~ grid_x = 0
#~ for x_index_range in zip(x_bins_rshift, x_bins):
#~ # now do the same thing in y; the tricky part is remembering
#~ # to use axis=1 since everything happens on the y-coordinate
#~ columnpoints = data[x_index_range[0] : x_index_range[1]]
#~ columnpoints = take(columnpoints, argsort(columnpoints[:,1]))
#~ # use searchsorted() to determine where the cell bottoms split the
#~ # set of column points.
#~ y_bins = searchsorted(columnpoints[:,1], self._cell_bottoms)
#~ y_bins_rshift = right_shift(y_bins, 0)
#~ grid_y = 0
#~ for startndx, endndx in zip(y_bins_rshift, y_bins):
#~ if startndx != endndx:
#~ cell = self._cellgrid[grid_x, grid_y]
#~ cellpts = columnpoints[startndx:endndx]
#~ if cell.sort_order == 'none':
#~ cell.points = cellpts
#~ elif cell.sort_order == 'ascending':
#~ cell.points = find_runs(sort_points(cellpts))
#~ elif cell.sort_order == 'descending':
#~ cell.points = find_runs(sort_points(cellpts)[::-1], 'descending')
#~ else:
#~ raise RuntimeError, "Invalid sort_order: " + cell.sort_order
#~ return
class SubdivisionLineDataMapper(SubdivisionDataMapper):
""" A subdivision data mapper that uses ranged cells.
"""
celltype = RangedCell
#EOF
|