def __init__(self): super().__init__() self.grid = None self.data = None self.data_subset = None self.subset_indices = [] self.nonempty = [] self.allAttrs = [] self.stringAttrs = [] self.domainAttrs = [] self.label_model = DomainModel(placeholder="(No labels)") self.selection = None #: List of _ImageItems self.items = [] self._errcount = 0 self._successcount = 0 self.imageAttrCB = gui.comboBox( self.controlArea, self, "imageAttr", box="Image Filename Attribute", tooltip="Attribute with image filenames", callback=self.change_image_attr, contentsLength=12, addSpace=True, ) # cell fit (resize or crop) self.cellFitRB = gui.radioButtons(self.controlArea, self, "cell_fit", ["Resize", "Crop"], box="Image cell fit", callback=self.set_crop) self.gridSizeBox = gui.vBox(self.controlArea, "Grid size") form = QFormLayout(labelAlignment=Qt.AlignLeft, formAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow, verticalSpacing=10) self.colSpinner = gui.spin(self.gridSizeBox, self, "columns", minv=2, maxv=40, callback=self.update_size) self.rowSpinner = gui.spin(self.gridSizeBox, self, "rows", minv=2, maxv=40, callback=self.update_size) form.addRow("Columns:", self.colSpinner) form.addRow("Rows:", self.rowSpinner) gui.separator(self.gridSizeBox, 10) self.gridSizeBox.layout().addLayout(form) gui.button(self.gridSizeBox, self, "Set size automatically", callback=self.auto_set_size) self.label_box = gui.vBox(self.controlArea, "Labels") # labels control self.label_attr_cb = gui.comboBox(self.label_box, self, "label_attr", tooltip="Show labels", callback=self.update_size, addSpace=True, model=self.label_model) gui.rubber(self.controlArea) # auto commit self.autoCommitBox = gui.auto_commit( self.controlArea, self, "auto_commit", "Apply", checkbox_label="Apply automatically") self.image_grid = None self.cell_fit = 0 self.thumbnailView = ThumbnailView( alignment=Qt.AlignTop | Qt.AlignLeft, focusPolicy=Qt.StrongFocus, verticalScrollBarPolicy=Qt.ScrollBarAlwaysOff, horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff) self.mainArea.layout().addWidget(self.thumbnailView) self.scene = self.thumbnailView.scene() self.scene.selectionChanged.connect(self.on_selection_changed) self.loader = ImageLoader(self)
def __init__(self): super().__init__() self.grid = None self.data = None self.data_subset = None self.subset_indices = [] self.nonempty = [] self.allAttrs = [] self.stringAttrs = [] self.domainAttrs = [] self.selection = None #: List of _ImageItems self.items = [] self._errcount = 0 self._successcount = 0 self.imageAttrCB = gui.comboBox( self.controlArea, self, "imageAttr", box="Image Filename Attribute", tooltip="Attribute with image filenames", callback=self.change_image_attr, contentsLength=12, addSpace=True, ) # cell fit (resize or crop) self.cellFitRB = gui.radioButtons(self.controlArea, self, "cell_fit", ["Resize", "Crop"], box="Image cell fit", callback=self.set_crop) self.gridSizeBox = gui.vBox(self.controlArea, "Grid size") form = QFormLayout( labelAlignment=Qt.AlignLeft, formAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow, verticalSpacing=10 ) self.colSpinner = gui.spin(self.gridSizeBox, self, "columns", minv=2, maxv=40, callback=self.update_size) self.rowSpinner = gui.spin(self.gridSizeBox, self, "rows", minv=2, maxv=40, callback=self.update_size) form.addRow("Columns:", self.colSpinner) form.addRow("Rows:", self.rowSpinner) gui.separator(self.gridSizeBox, 10) self.gridSizeBox.layout().addLayout(form) gui.button(self.gridSizeBox, self, "Set size automatically", callback=self.auto_set_size) gui.rubber(self.controlArea) # auto commit self.autoCommitBox = gui.auto_commit(self.controlArea, self, "auto_commit", "Apply", checkbox_label="Apply automatically") self.info = gui.widgetLabel(gui.vBox(self.controlArea, "Info"), "Waiting for input.\n") self.image_grid = None self.cell_fit = 0 self.thumbnailView = ThumbnailView( alignment=Qt.AlignTop | Qt.AlignLeft, focusPolicy=Qt.StrongFocus, verticalScrollBarPolicy=Qt.ScrollBarAlwaysOff, horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff ) self.mainArea.layout().addWidget(self.thumbnailView) self.scene = self.thumbnailView.scene() self.scene.selectionChanged.connect(self.on_selection_changed) self.loader = ImageLoader(self)
class OWImageGrid(widget.OWWidget): name = "Image Grid" description = "Visualize images in a similarity grid" icon = "icons/ImageGrid.svg" priority = 160 keywords = ["image", "grid", "similarity"] graph_name = "scene" class Inputs: data = Input("Embeddings", Orange.data.Table, default=True) data_subset = Input("Data Subset", Orange.data.Table) class Outputs: selected_data = Output("Selected Images", Orange.data.Table, default=True) data = Output("Images", Orange.data.Table) settingsHandler = settings.DomainContextHandler() cell_fit = settings.Setting("Resize") columns = settings.Setting(10) rows = settings.Setting(10) imageAttr = settings.ContextSetting(0) imageSize = settings.Setting(100) label_attr = settings.ContextSetting(None, required=ContextSetting.OPTIONAL) label_selected = settings.Setting(True) auto_update = settings.Setting(True) auto_commit = settings.Setting(True) class Warning(OWWidget.Warning): incompatible_subset = Msg("Data subset is incompatible with Data") no_valid_data = Msg("No valid data") def __init__(self): super().__init__() self.grid = None self.data = None self.data_subset = None self.subset_indices = [] self.nonempty = [] self.allAttrs = [] self.stringAttrs = [] self.domainAttrs = [] self.label_model = DomainModel(placeholder="(No labels)") self.selection = None #: List of _ImageItems self.items = [] self._errcount = 0 self._successcount = 0 self.imageAttrCB = gui.comboBox( self.controlArea, self, "imageAttr", box="Image Filename Attribute", tooltip="Attribute with image filenames", callback=self.change_image_attr, contentsLength=12, addSpace=True, ) # cell fit (resize or crop) self.cellFitRB = gui.radioButtons(self.controlArea, self, "cell_fit", ["Resize", "Crop"], box="Image cell fit", callback=self.set_crop) self.gridSizeBox = gui.vBox(self.controlArea, "Grid size") form = QFormLayout(labelAlignment=Qt.AlignLeft, formAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow, verticalSpacing=10) self.colSpinner = gui.spin(self.gridSizeBox, self, "columns", minv=2, maxv=40, callback=self.update_size) self.rowSpinner = gui.spin(self.gridSizeBox, self, "rows", minv=2, maxv=40, callback=self.update_size) form.addRow("Columns:", self.colSpinner) form.addRow("Rows:", self.rowSpinner) gui.separator(self.gridSizeBox, 10) self.gridSizeBox.layout().addLayout(form) gui.button(self.gridSizeBox, self, "Set size automatically", callback=self.auto_set_size) self.label_box = gui.vBox(self.controlArea, "Labels") # labels control self.label_attr_cb = gui.comboBox(self.label_box, self, "label_attr", tooltip="Show labels", callback=self.update_size, addSpace=True, model=self.label_model) gui.rubber(self.controlArea) # auto commit self.autoCommitBox = gui.auto_commit( self.controlArea, self, "auto_commit", "Apply", checkbox_label="Apply automatically") self.image_grid = None self.cell_fit = 0 self.thumbnailView = ThumbnailView( alignment=Qt.AlignTop | Qt.AlignLeft, focusPolicy=Qt.StrongFocus, verticalScrollBarPolicy=Qt.ScrollBarAlwaysOff, horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff) self.mainArea.layout().addWidget(self.thumbnailView) self.scene = self.thumbnailView.scene() self.scene.selectionChanged.connect(self.on_selection_changed) self.loader = ImageLoader(self) def process(self, size_x=0, size_y=0): if self.image_grid: self.image_grid.process(size_x, size_y) def sizeHint(self): return QSize(600, 600) # checks the input data for the right meta-attributes and finds images @Inputs.data def set_data(self, data): self.closeContext() self.clear() self.Warning.no_valid_data.clear() self.data = data if data is not None: domain = data.domain self.allAttrs = (domain.class_vars + domain.metas + domain.attributes) self.stringAttrs = [a for a in domain.metas if a.is_string] self.domainAttrs = len(domain.attributes) self.stringAttrs = sorted(self.stringAttrs, key=lambda attr: 0 if "type" in attr.attributes else 1) indices = [ i for i, var in enumerate(self.stringAttrs) if var.attributes.get("type") == "image" ] if indices: self.imageAttr = indices[0] self.imageAttrCB.setModel(VariableListModel(self.stringAttrs)) # set label combo labels self.label_model.set_domain(domain) self.openContext(data) self.label_attr = self.label_model[0] self.imageAttr = max( min(self.imageAttr, len(self.stringAttrs) - 1), 0) if self.is_valid_data(): self.image_grid = ImageGrid(data) self.setup_scene() else: self.Warning.no_valid_data() @Inputs.data_subset def set_data_subset(self, data_subset): self.data_subset = data_subset def clear(self): self.data = None self.image_grid = None self.error() self.imageAttrCB.clear() self.label_attr_cb.clear() self.clear_scene() def is_valid_data(self): return self.data and self.stringAttrs and self.domainAttrs # loads the images and places them into the viewing area def setup_scene(self, process_grid=True): self.clear_scene() self.error() if self.data: attr = self.stringAttrs[self.imageAttr] assert self.thumbnailView.count() == 0 size = QSizeF(self.imageSize, self.imageSize) if process_grid and self.image_grid: self.process() self.columns = self.image_grid.size_x self.rows = self.image_grid.size_y self.thumbnailView.setFixedColumnCount(self.columns) self.thumbnailView.setFixedRowCount(self.rows) for i, inst in enumerate(self.image_grid.image_list): label_text = (str(inst[self.label_attr]) if self.label_attr is not None else "") if label_text == "?": label_text = "" thumbnail = GraphicsThumbnailWidget( QPixmap(), crop=self.cell_fit == 1, add_label=self.label_selected and self.label_attr is not None, text=label_text) thumbnail.setThumbnailSize(size) thumbnail.instance = inst self.thumbnailView.addThumbnail(thumbnail) if not np.isfinite(inst[attr]) or inst[attr] == "?": # skip missing future, url = None, None else: url = self.url_from_value(inst[attr]) thumbnail.setToolTip(url.toString()) self.nonempty.append(i) if url.isValid() and url.isLocalFile(): reader = QImageReader(url.toLocalFile()) image = reader.read() if image.isNull(): error = reader.errorString() thumbnail.setToolTip(thumbnail.toolTip() + "\n" + error) self._errcount += 1 else: pixmap = QPixmap.fromImage(image) thumbnail.setPixmap(pixmap) self._successcount += 1 future = Future() future.set_result(image) future._reply = None elif url.isValid(): future = self.loader.get(url) @future.add_done_callback def set_pixmap(future, thumb=thumbnail): if future.cancelled(): return assert future.done() if future.exception(): # Should be some generic error image. pixmap = QPixmap() thumb.setToolTip(thumb.toolTip() + "\n" + str(future.exception())) else: pixmap = QPixmap.fromImage(future.result()) thumb.setPixmap(pixmap) self._note_completed(future) else: future = None self.items.append(_ImageItem(i, thumbnail, url, future)) if not any(not it.future.done() if it.future else False for it in self.items): self._update_status() self.apply_subset() self.update_selection() def handleNewSignals(self): self.Warning.incompatible_subset.clear() self.subset_indices = [] if self.data and self.data_subset and len(self.stringAttrs) > 0: transformed = self.data_subset.transform(self.data.domain) attr = self.stringAttrs[self.imageAttr] # for the subset we need to check if it contains the image # attribute, if all indices from the subset are in the original # array, and if same image on the same index data_indices = [e.id for e in self.data] if attr in self.data_subset.domain \ and all(row.id in data_indices and self.data[np.where( data_indices == row.id)[0][0], attr] == row[attr] for row in transformed): indices = {e.id for e in transformed} self.subset_indices = [ex.id in indices for ex in self.data] else: self.Warning.incompatible_subset() self.apply_subset() def url_from_value(self, value): base = value.variable.attributes.get("origin", "") if QDir(base).exists(): base = QUrl.fromLocalFile(base) else: base = QUrl(base) path = base.path() if path.strip() and not path.endswith("/"): base.setPath(path + "/") url = base.resolved(QUrl(str(value))) return url def cancel_all_futures(self): for item in self.items: if item.future is not None: item.future.cancel() if item.future._reply is not None: item.future._reply.close() item.future._reply.deleteLater() item.future._reply = None def clear_scene(self): self.cancel_all_futures() self.items = [] self.nonempty = [] self.selection = None self.thumbnailView.clear() self._errcount = 0 self._successcount = 0 def change_image_attr(self): self.clear_scene() if self.is_valid_data(): self.setup_scene() def thumbnail_items(self): return [item.widget for item in self.items] def update_size(self): try: self.process(self.columns, self.rows) self.colSpinner.setMinimum(2) self.rowSpinner.setMinimum(2) except AssertionError: grid_size = self.thumbnailView.grid_size() self.columns = grid_size[0] self.rows = grid_size[1] self.colSpinner.setMinimum(self.columns) self.rowSpinner.setMinimum(self.rows) return self.clear_scene() if self.is_valid_data(): self.setup_scene(process_grid=False) def set_crop(self): self.thumbnailView.setCrop(self.cell_fit == 1) def auto_set_size(self): self.clear_scene() if self.is_valid_data(): self.setup_scene() def apply_subset(self): if self.image_grid: subset_indices = (self.subset_indices if self.subset_indices else [True] * len(self.items)) ordered_subset_indices = self.image_grid.order_to_grid( subset_indices) for item, in_subset in zip(self.items, ordered_subset_indices): item.widget.setSubset(in_subset) def on_selection_changed(self, selected_items, keys): if self.selection is None: self.selection = np.zeros(len(self.items), dtype=np.uint8) # newly selected indices = [ item.index for item in self.items if item.widget in selected_items ] # Remove from selection if keys & Qt.AltModifier: self.selection[indices] = 0 # Append to the last group elif keys & Qt.ShiftModifier and keys & Qt.ControlModifier: self.selection[indices] = np.max(self.selection) # Create a new group elif keys & Qt.ShiftModifier: self.selection[indices] = np.max(self.selection) + 1 # No modifiers: new selection else: self.selection = np.zeros(len(self.items), dtype=np.uint8) self.selection[indices] = 1 self.update_selection() self.commit() def commit(self): if self.data: # add Group column (group number) self.Outputs.selected_data.send( create_groups_table(self.image_grid.image_list, self.selection, False, "Group")) # filter out empty cells - keep indices of cells that contain images # add Selected column # (Yes/No if one group, else Unselected or group number) if self.selection is not None and np.max(self.selection) > 1: out_data = create_groups_table( self.image_grid.image_list[self.nonempty], self.selection[self.nonempty]) else: out_data = create_annotated_table( self.image_grid.image_list[self.nonempty], np.nonzero(self.selection[self.nonempty])) self.Outputs.data.send(out_data) else: self.Outputs.data.send(None) self.Outputs.selected_data.send(None) def update_selection(self): if self.selection is not None: pen, brush = self.compute_colors() for s, item, p, b in zip(self.selection, self.items, pen, brush): item.widget.setSelected(s > 0) item.widget.setSelectionColor(p, b) # Adapted from Scatter Plot Graph (change brush instead of pen) def compute_colors(self): no_brush = DEFAULT_SELECTION_BRUSH sels = np.max(self.selection) if sels == 1: brushes = [no_brush, no_brush] else: palette = ColorPaletteGenerator(number_of_colors=sels + 1) brushes = [no_brush] + [QBrush(palette[i]) for i in range(sels)] brush = [brushes[a] for a in self.selection] pen = [DEFAULT_SELECTION_PEN] * len(self.items) return pen, brush def send_report(self): if self.is_valid_data(): items = [("Number of images", len(self.data))] self.report_items(items) self.report_plot("Grid", self.scene) def _note_completed(self, future): # Note the completed future's state if future.cancelled(): return if future.exception(): self._errcount += 1 _log.debug("Error: %r", future.exception()) else: self._successcount += 1 self._update_status() def _update_status(self): count = len([item for item in self.items if item.future is not None]) if self._errcount + self._successcount == count: attr = self.stringAttrs[self.imageAttr] if self._errcount == count and "type" not in attr.attributes: self.error("No images found! Make sure the '%s' attribute " "is tagged with 'type=image'" % attr.name) def onDeleteWidget(self): self.cancel_all_futures() self.clear()
class OWImageGrid(widget.OWWidget): name = "Image Grid" description = "Visualize images in a similarity grid" icon = "icons/ImageGrid.svg" priority = 160 keywords = ["image", "grid", "similarity"] graph_name = "scene" class Inputs: data = Input("Embeddings", Orange.data.Table) data_subset = Input("Data Subset", Orange.data.Table) class Outputs: selected_data = Output( "Selected Images", Orange.data.Table, default=True) data = Output("Images", Orange.data.Table) settingsHandler = settings.DomainContextHandler() cell_fit = settings.Setting("Resize") columns = settings.Setting(10) rows = settings.Setting(10) imageAttr = settings.ContextSetting(0) imageSize = settings.Setting(100) auto_update = settings.Setting(True) auto_commit = settings.Setting(True) class Warning(OWWidget.Warning): incompatible_subset = Msg("Data subset is incompatible with Data") no_valid_data = Msg("No valid data") def __init__(self): super().__init__() self.grid = None self.data = None self.data_subset = None self.subset_indices = [] self.nonempty = [] self.allAttrs = [] self.stringAttrs = [] self.domainAttrs = [] self.selection = None #: List of _ImageItems self.items = [] self._errcount = 0 self._successcount = 0 self.imageAttrCB = gui.comboBox( self.controlArea, self, "imageAttr", box="Image Filename Attribute", tooltip="Attribute with image filenames", callback=self.change_image_attr, contentsLength=12, addSpace=True, ) # cell fit (resize or crop) self.cellFitRB = gui.radioButtons(self.controlArea, self, "cell_fit", ["Resize", "Crop"], box="Image cell fit", callback=self.set_crop) self.gridSizeBox = gui.vBox(self.controlArea, "Grid size") form = QFormLayout( labelAlignment=Qt.AlignLeft, formAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow, verticalSpacing=10 ) self.colSpinner = gui.spin(self.gridSizeBox, self, "columns", minv=2, maxv=40, callback=self.update_size) self.rowSpinner = gui.spin(self.gridSizeBox, self, "rows", minv=2, maxv=40, callback=self.update_size) form.addRow("Columns:", self.colSpinner) form.addRow("Rows:", self.rowSpinner) gui.separator(self.gridSizeBox, 10) self.gridSizeBox.layout().addLayout(form) gui.button(self.gridSizeBox, self, "Set size automatically", callback=self.auto_set_size) gui.rubber(self.controlArea) # auto commit self.autoCommitBox = gui.auto_commit(self.controlArea, self, "auto_commit", "Apply", checkbox_label="Apply automatically") self.info = gui.widgetLabel(gui.vBox(self.controlArea, "Info"), "Waiting for input.\n") self.image_grid = None self.cell_fit = 0 self.thumbnailView = ThumbnailView( alignment=Qt.AlignTop | Qt.AlignLeft, focusPolicy=Qt.StrongFocus, verticalScrollBarPolicy=Qt.ScrollBarAlwaysOff, horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff ) self.mainArea.layout().addWidget(self.thumbnailView) self.scene = self.thumbnailView.scene() self.scene.selectionChanged.connect(self.on_selection_changed) self.loader = ImageLoader(self) def process(self, size_x=0, size_y=0): if self.image_grid: self.image_grid.process(size_x, size_y) def sizeHint(self): return QSize(600, 600) # checks the input data for the right meta-attributes and finds the image filename. @Inputs.data def set_data(self, data): self.closeContext() self.clear() self.Warning.no_valid_data.clear() self.data = data if data is not None: domain = data.domain self.allAttrs = (domain.class_vars + domain.metas + domain.attributes) self.stringAttrs = [a for a in domain.metas if a.is_string] self.domainAttrs = len(domain.attributes) self.stringAttrs = sorted( self.stringAttrs, key=lambda attr: 0 if "type" in attr.attributes else 1 ) indices = [i for i, var in enumerate(self.stringAttrs) if var.attributes.get("type") == "image"] if indices: self.imageAttr = indices[0] self.imageAttrCB.setModel(VariableListModel(self.stringAttrs)) self.openContext(data) self.imageAttr = max(min(self.imageAttr, len(self.stringAttrs) - 1), 0) if self.is_valid_data(): self.image_grid = ImageGrid(data) self.setup_scene() else: self.Warning.no_valid_data() else: self.info.setText("Waiting for input.\n") @Inputs.data_subset def set_data_subset(self, data_subset): self.data_subset = data_subset def clear(self): self.data = None self.image_grid = None self.error() self.imageAttrCB.clear() self.clear_scene() def is_valid_data(self): return self.data and self.stringAttrs and self.domainAttrs # loads the images and places them into the viewing area def setup_scene(self, process_grid=True): self.error() if self.data: attr = self.stringAttrs[self.imageAttr] assert self.thumbnailView.count() == 0 size = QSizeF(self.imageSize, self.imageSize) if process_grid and self.image_grid: self.process() self.columns = self.image_grid.size_x self.rows = self.image_grid.size_y self.thumbnailView.setFixedColumnCount(self.columns) self.thumbnailView.setFixedRowCount(self.rows) for i, inst in enumerate(self.image_grid.image_list): thumbnail = GraphicsThumbnailWidget(QPixmap(), crop=self.cell_fit == 1) thumbnail.setThumbnailSize(size) thumbnail.instance = inst self.thumbnailView.addThumbnail(thumbnail) if not np.isfinite(inst[attr]) or inst[attr] == "?": # skip missing future, url = None, None else: url = self.url_from_value(inst[attr]) thumbnail.setToolTip(url.toString()) self.nonempty.append(i) if url.isValid() and url.isLocalFile(): reader = QImageReader(url.toLocalFile()) image = reader.read() if image.isNull(): error = reader.errorString() thumbnail.setToolTip( thumbnail.toolTip() + "\n" + error) self._errcount += 1 else: pixmap = QPixmap.fromImage(image) thumbnail.setPixmap(pixmap) self._successcount += 1 future = Future() future.set_result(image) future._reply = None elif url.isValid(): future = self.loader.get(url) @future.add_done_callback def set_pixmap(future, thumb=thumbnail): if future.cancelled(): return assert future.done() if future.exception(): # Should be some generic error image. pixmap = QPixmap() thumb.setToolTip(thumb.toolTip() + "\n" + str(future.exception())) else: pixmap = QPixmap.fromImage(future.result()) thumb.setPixmap(pixmap) self._note_completed(future) else: future = None self.items.append(_ImageItem(i, thumbnail, url, future)) if any(not it.future.done() if it.future else False for it in self.items): self.info.setText("Retrieving...\n") else: self._update_status() self.apply_subset() self.update_selection() def handleNewSignals(self): self.Warning.incompatible_subset.clear() self.subset_indices = [] if self.data and self.data_subset: transformed = self.data_subset.transform(self.data.domain) if np.all(self.data.domain.metas == self.data_subset.domain.metas): indices = {e.id for e in transformed} self.subset_indices = [ex.id in indices for ex in self.data] else: self.Warning.incompatible_subset() self.apply_subset() def url_from_value(self, value): base = value.variable.attributes.get("origin", "") if QDir(base).exists(): base = QUrl.fromLocalFile(base) else: base = QUrl(base) path = base.path() if path.strip() and not path.endswith("/"): base.setPath(path + "/") url = base.resolved(QUrl(str(value))) return url def cancel_all_futures(self): for item in self.items: if item.future is not None: item.future.cancel() if item.future._reply is not None: item.future._reply.close() item.future._reply.deleteLater() item.future._reply = None def clear_scene(self): self.cancel_all_futures() self.items = [] self.nonempty = [] self.selection = None self.thumbnailView.clear() self._errcount = 0 self._successcount = 0 def change_image_attr(self): self.clear_scene() if self.is_valid_data(): self.setup_scene() def thumbnail_items(self): return [item.widget for item in self.items] def update_size(self): try: self.process(self.columns, self.rows) self.colSpinner.setMinimum(2) self.rowSpinner.setMinimum(2) except AssertionError: grid_size = self.thumbnailView.grid_size() self.columns = grid_size[0] self.rows = grid_size[1] self.colSpinner.setMinimum(self.columns) self.rowSpinner.setMinimum(self.rows) return self.clear_scene() if self.is_valid_data(): self.setup_scene(process_grid=False) def set_crop(self): self.thumbnailView.setCrop(self.cell_fit == 1) def auto_set_size(self): self.clear_scene() if self.is_valid_data(): self.setup_scene() def apply_subset(self): if self.image_grid: subset_indices = self.subset_indices if self.subset_indices else [True] * len(self.items) ordered_subset_indices = self.image_grid.order_to_grid(subset_indices) for item, in_subset in zip(self.items, ordered_subset_indices): item.widget.setSubset(in_subset) def on_selection_changed(self, selected_items, keys): if self.selection is None: self.selection = np.zeros(len(self.items), dtype=np.uint8) # newly selected indices = [item.index for item in self.items if item.widget in selected_items] # Remove from selection if keys & Qt.AltModifier: self.selection[indices] = 0 # Append to the last group elif keys & Qt.ShiftModifier and keys & Qt.ControlModifier: self.selection[indices] = np.max(self.selection) # Create a new group elif keys & Qt.ShiftModifier: self.selection[indices] = np.max(self.selection) + 1 # No modifiers: new selection else: self.selection = np.zeros(len(self.items), dtype=np.uint8) self.selection[indices] = 1 self.update_selection() self.commit() def commit(self): if self.data: # add Group column (group number) self.Outputs.selected_data.send( create_groups_table(self.image_grid.image_list, self.selection, False, "Group")) # filter out empty cells - keep indices of cells that contain images # add Selected column # (Yes/No if one group, else Unselected or group number) if self.selection is not None and np.max(self.selection) > 1: out_data = create_groups_table( self.image_grid.image_list[self.nonempty], self.selection[self.nonempty]) else: out_data = create_annotated_table( self.image_grid.image_list[self.nonempty], np.nonzero(self.selection[self.nonempty])) self.Outputs.data.send(out_data) else: self.Outputs.data.send(None) self.Outputs.selected_data.send(None) def update_selection(self): if self.selection is not None: pen, brush = self.compute_colors() for s, item, p, b in zip(self.selection, self.items, pen, brush): item.widget.setSelected(s > 0) item.widget.setSelectionColor(p, b) # Adapted from Scatter Plot Graph (change brush instead of pen) def compute_colors(self): no_brush = DEFAULT_SELECTION_BRUSH sels = np.max(self.selection) if sels == 1: brushes = [no_brush, no_brush] else: palette = ColorPaletteGenerator(number_of_colors=sels + 1) brushes = [no_brush] + [QBrush(palette[i]) for i in range(sels)] brush = [brushes[a] for a in self.selection] pen = [DEFAULT_SELECTION_PEN] * len(self.items) return pen, brush def send_report(self): if self.is_valid_data(): items = [("Number of images", len(self.data))] self.report_items(items) self.report_plot("Grid", self.scene) def _note_completed(self, future): # Note the completed future's state if future.cancelled(): return if future.exception(): self._errcount += 1 _log.debug("Error: %r", future.exception()) else: self._successcount += 1 self._update_status() def _update_status(self): count = len([item for item in self.items if item.future is not None]) self.info.setText( "Retrieving:\n" + "{} of {} images".format(self._successcount, count)) if self._errcount + self._successcount == count: if self._errcount: self.info.setText( "Done:\n" + "{} images, {} errors".format(count, self._errcount) ) else: self.info.setText( "Done:\n" + "{} images".format(count) ) attr = self.stringAttrs[self.imageAttr] if self._errcount == count and "type" not in attr.attributes: self.error("No images found! Make sure the '%s' attribute " "is tagged with 'type=image'" % attr.name) def onDeleteWidget(self): self.cancel_all_futures() self.clear()