class OWQuickSelect(widget.OWWidget): name = 'Quick Select' icon = 'icons/QuickSelect.svg' description = 'Select instances with specific feature value.' class Inputs: data = widget.Input("Data", Table) class Outputs: matching = widget.Output("Matching Data", Table, default=True) unmatched = widget.Output("Unmatched Data", Table) annotated = widget.Output(ANNOTATED_DATA_SIGNAL_NAME, Table) class Error(widget.OWWidget.Error): no_categorical = Msg("No categorical variables") want_main_area = False resizing_enabled = False settingsHandler = settings.DomainContextHandler() variable = settings.ContextSetting(None) pending_value = settings.ContextSetting("") def __init__(self): super().__init__() # This is not a setting because openContext cannot retrieve it before # filling the combo. Settings store this as pending_value self.value = "" self.data = None self.n_matched = None form = QFormLayout() gui.widgetBox(self.controlArea, orientation=form) self.var_model = DomainModel(order=DomainModel.MIXED, valid_types=DiscreteVariable) var_combo = gui.comboBox(None, self, "variable", contentsLength=50, model=self.var_model, callback=self._on_variable_changed) self.value_model = PyListModel() value_combo = gui.comboBox(None, self, "value", label="Value: ", model=self.value_model, callback=self._on_value_changed, contentsLength=50, sizePolicy=(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)) form.addRow("Variable:", var_combo) form.addRow("Value:", value_combo) @Inputs.data def set_data(self, data): self.closeContext() self.data = data self.Error.no_categorical.clear() # TODO: Check that contexts are retrieved properly, also when removing # and re-adding a connection if data: self.var_model.set_domain(data.domain) self.info.set_input_summary(len(data), format_summary_details(data)) if not self.var_model.rowCount(): self.data = None self.Error.no_categorical() else: self.var_model.set_domain(None) self.info.set_input_summary(self.info.NoInput) self.variable = self.var_model[0] if self.data else None self.openContext(self.data) self.set_value_list() if self.variable and self.pending_value in self.variable.values: self.value = self.pending_value self.commit() def set_value_list(self): if self.variable is None: self.value_model.clear() self.value = "" else: self.value_model[:] = self.variable.values self.value = self.value_model[0] if self.variable.values else "" def _on_variable_changed(self): self.set_value_list() self.commit() def _on_value_changed(self): self.pending_value = self.value self.commit() def commit(self): if not (self.data and self.variable and self.value): annotated = matching = unmatched = None self.n_matched = None else: column = self.data.get_column_view(self.variable)[0] valind = self.variable.values.index(self.value) mask = column == valind annotated = create_annotated_table(self.data, np.flatnonzero(mask)) matching = self.data[mask] unmatched = self.data[~mask] self.n_matched = len(matching) self.Outputs.matching.send(matching) self.Outputs.unmatched.send(unmatched) self.Outputs.annotated.send(annotated) def send_report(self): if not self.data: return self.report_items( "", [("Filter", f"{self.variable.name} = '{self.value}'"), ("Matching instances", f"{self.n_matched} (out of {len(self.data)})")])
class OWGeoTransform(OWWidget): name = "Geo Transform" description = "Transform geographic coordinates from one system to another." icon = "icons/GeoTransform.svg" priority = 320 keywords = ["transform", "geo"] class Inputs: data = Input("Data", Table) class Outputs: data = Output("Data", Table) settingsHandler = settings.DomainContextHandler() attr_lat = settings.ContextSetting(None) attr_lon = settings.ContextSetting(None) from_idx = settings.Setting("Slovenia 1996 / Slovene National Grid") to_idx = settings.Setting("WGS 84") replace_original = settings.Setting(True) want_main_area = False resizing_enabled = False EPSG_CODES = get_projections() class Error(OWWidget.Error): no_lat_lon_vars = Msg("Data has no latitude and longitude variables.") def __init__(self): super().__init__() self.data = None layout = QFormLayout() gui.widgetBox(self.controlArea, box="Coordinates:", orientation=layout) self.variable_model = DomainModel(order=DomainModel.MIXED, valid_types=(ContinuousVariable, )) args = dict(contentsLength=100, searchable=True, model=self.variable_model, orientation=Qt.Horizontal) layout.addRow("Latitude:", gui.comboBox(None, self, "attr_lat", **args)) layout.addRow("Longitude:", gui.comboBox(None, self, "attr_lon", **args)) layout.addWidget( gui.checkBox( None, self, "replace_original", "Replace original coordinates", tooltip="If unchecked, the original coordinates are retained " "and new coordinates are added as separate variables.")) layout = QFormLayout() gui.widgetBox(self.controlArea, "Transformation:", orientation=layout) args["model"] = PyListModelTooltip(self.EPSG_CODES, list(self.EPSG_CODES)) layout.addRow("From:", gui.comboBox(None, self, "from_idx", **args)) layout.addRow("To:", gui.comboBox(None, self, "to_idx", **args)) self.commit_button = gui.button(self.controlArea, self, "&Commit", self.apply) def init_attr_values(self): self.variable_model.set_domain(self.data.domain if self.data else None) self.Error.no_lat_lon_vars.clear() if self.variable_model.rowCount() < 2: self.Error.no_lat_lon_vars() lat, lon = None, None self.data = None else: lat, lon = find_lat_lon(self.data, filter_hidden=True, fallback=False) if lat is None or lon is None: lat, lon = self.variable_model[:2] self.attr_lat, self.attr_lon = lat, lon @Inputs.data def set_data(self, data: Table = None): self.data = data self.closeContext() self.init_attr_values() self.openContext(self.data) self.apply() def apply(self): if not self.data: self.Outputs.data.send(None) return out = self.data.transform(self._transformed_domain()) self.Outputs.data.send(out) def _transformed_domain(self): dom = self.data.domain orig_coords = (self.attr_lat, self.attr_lon) names = [var.name for var in orig_coords] if not self.replace_original: # If appending, use the same names, just with numbers for uniqueness existing = [v.name for v in chain(dom.variables, dom.metas)] names = get_unique_names(existing, names) transformer = Transformer.from_crs(self.EPSG_CODES[self.from_idx], self.EPSG_CODES[self.to_idx]) transformer_common = GeoTransformerCommon(transformer, *orig_coords) coords = (ContinuousVariable(name, compute_value=GeoTransformer( transformer_common, col)) for col, name in enumerate(names)) if self.replace_original: tr = dict(zip(orig_coords, coords)) def r(variables): return [tr.get(var, var) for var in variables] return Domain(r(dom.attributes), r(dom.class_vars), r(dom.metas)) # Put each new variable in attributes, if it was there, else to metas attrs, metas = list(dom.attributes), list(dom.metas) for orig, new in zip(orig_coords, coords): (attrs if orig in dom.attributes else metas).append(new) return Domain(attrs, dom.class_vars, metas)