def test_range_rois_preserved(self): data = self.add_data_and_attributes() assert self.client.xatt is not self.client.yatt roi = XRangeROI() roi.set_range(1, 2) self.client.apply_roi(roi) assert isinstance(data.edit_subset.subset_state, RangeSubsetState) assert data.edit_subset.subset_state.att == self.client.xatt roi = RectangularROI() roi = YRangeROI() roi.set_range(1, 2) self.client.apply_roi(roi) assert data.edit_subset.subset_state.att == self.client.yatt
def test_apply_roi_empty(self): # Make sure that doing an ROI selection on an empty viewer doesn't # produce error messsages roi = XRangeROI(-0.2, 0.1) self.viewer.apply_roi(roi)
def roi_to_subset_state(roi, x_att=None, y_att=None, x_comp=None, y_comp=None): """ Given a 2D ROI and attributes on the x and y axis, determine the corresponding subset state. """ if isinstance(roi, RangeROI): if roi.ori == 'x': att = x_att comp = x_comp else: att = y_att comp = y_comp if comp.categorical: return CategoricalROISubsetState.from_range(comp, att, roi.min, roi.max) else: return RangeSubsetState(roi.min, roi.max, att) elif x_comp.categorical or y_comp.categorical: if isinstance(roi, RectangularROI): # In this specific case, we can decompose the rectangular ROI into # two RangeROIs that are combined with an 'and' logical operation. range1 = XRangeROI(roi.xmin, roi.xmax) range2 = YRangeROI(roi.ymin, roi.ymax) subset1 = roi_to_subset_state(range1, x_att=x_att, x_comp=x_comp) subset2 = roi_to_subset_state(range2, y_att=y_att, y_comp=y_comp) return AndState(subset1, subset2) elif isinstance(roi, CategoricalROI): # The selection is categorical itself. We assume this is along the x axis return CategoricalROISubsetState(roi=roi, att=x_att) else: # The selection is polygon-like, which requires special care. if x_comp.categorical and y_comp.categorical: # For each category, we check which categories along the other # axis fall inside the polygon: selection = {} for code, label in enumerate(x_comp.categories): # Determine the coordinates of the points to check n_other = len(y_comp.categories) y = np.arange(n_other) x = np.repeat(code, n_other) # Determine which points are in the polygon, and which # categories these correspond to in_poly = roi.contains(x, y) categories = y_comp.categories[in_poly] if len(categories) > 0: selection[label] = set(categories) return CategoricalROISubsetState2D(selection, x_att, y_att) else: # If one of the components is not categorical, we treat this as # if each categorical component was mapped to a numerical value, # and at each value, we keep track of the polygon intersection # with the component. This will result in zero, one, or multiple # separate numerical ranges for each categorical value. # TODO: if we ever allow the category order to be changed, we # need to figure out how to update this! # We loop over each category and for each one we find the # numerical ranges selection = {} if x_comp.categorical: cat_comp = x_comp cat_att = x_att num_att = y_att x, y = roi.to_polygon() else: cat_comp = y_comp cat_att = y_att num_att = x_att y, x = roi.to_polygon() for code, label in enumerate(cat_comp.categories): # We determine all the numerical segments that represent the # ensemble of points in y that fall in the polygon # TODO: profile the following function segments = polygon_line_intersections(x, y, xval=code) if len(segments) > 0: selection[label] = segments return CategoricalMultiRangeSubsetState(selection, cat_att=cat_att, num_att=num_att) else: # The selection is polygon-like and components are numerical subset_state = RoiSubsetState() subset_state.xatt = x_att subset_state.yatt = y_att subset_state.roi = PolygonalROI(*roi.to_polygon()) return subset_state