def __init__(self, parent=None): """Initialize the class. Parameters ---------- parent : None, optional Top-level widget. """ super().__init__(parent) self.setupUi(self) p1 = self.scatterPlot.addPlot() self.scatterPlotItem = ScatterPlotItem() p1.addItem(self.scatterPlotItem) p1.setLabel('left', 'Y', units='pixel') p1.setLabel('bottom', 'X', units='pixel') self.yHistogramItem = None self.xHistogramItem = None self.numBins = 40 self.dataSize = None self.xData = None self.yData = None self.rollArray = False self.dataCounter = 0 self.brushes = None self.brushColor = (159, 159, 159) self.penColor = (255, 255, 255) self.maxAlpha = 255 self.minAlpha = 127 self.histogramFillBrush = mkBrush(*self.brushColor, 200) self.pointBrush = mkBrush(*self.brushColor, self.maxAlpha) self.pointPen = mkPen(*self.penColor) self.scatterPlotItem.setBrush(self.pointBrush)
def __init__(self, *args, **kwargs): self.centerplot = ScatterPlotItem(brush="r") self.centerplot.setZValue(100) super(CenterMarker, self).__init__(*args, **kwargs) self.addItem(self.centerplot) self.drawCenter()
def scatter(self, x: np.ndarray, y: np.ndarray, label: Optional[str] = None, symbol: Optional[list] = None, **kwargs) -> None: kwargs = Axes._add_label_to_kwargs(label, kwargs) item = ScatterPlotItem(**kwargs) item.setData(x=x, y=y) self._raw.addItem(item)
def __init__(self, manager: BarManager, am: ArrayManager): """""" ScatterPlotItem.__init__(self) # ExChartItem.__init__(self,manager) # super(TradeItem,self).__init__(manager, am) super(ExChartItem, self).__init__(manager, am) self.blue_pen: QtGui.QPen = pg.mkPen(color=(100, 100, 255), width=2) self.trades: Dict[int, Dict[str, TradeData]] = {} # {ix:{tradeid:trade}}
class ScatterPlotterNode(Node): nodeName = 'ScatterPlotter' sigUpdatePlot = QtCore.Signal(object) def __init__(self, name): Node.__init__(self, name, terminals={ 'x': { 'io': 'in' }, 'y': { 'io': 'in' }, 'color': { 'io': 'in' }, 'plotItem': { 'io': 'out' } }) self.ScatterPlotItem = ScatterPlotItem(pen=None, symbol='o', size=2, brush=(255, 255, 255, 160)) self.sigUpdatePlot.connect(self.updatePlot) def updatePlot(self, xyb): x, y, brs = xyb self.ScatterPlotItem.setData(x, y) if not brs is None: self.ScatterPlotItem.setBrush(brs) def process(self, x, y, color, display=True): if x is None or y is None: raise Exception('set proper inputs') brs = None if not color is None: brs = [ fn.mkBrush(tuple(c)) for c in (color * 255).astype(np.int32) ] self.sigUpdatePlot.emit((x, y, brs)) return {'plotItem': self.ScatterPlotItem}
def plotCurveAndFit(self, xData, yData): # noinspection PyTypeChecker self.plotAttributes["curve"] = ScatterPlotItem(xData, yData, pen=1, symbol='x', size=5) self.plot.addItem(self.plotAttributes["curve"]) self.plotFit(xData, yData, self.devices["B"] + ' vs. ' + self.devices["A"])
class CenterMarker(QSpace): def __init__(self, *args, **kwargs): self.centerplot = ScatterPlotItem(brush="r") self.centerplot.setZValue(100) super(CenterMarker, self).__init__(*args, **kwargs) self.addItem(self.centerplot) self.drawCenter() def drawCenter(self): try: fit2d = self._geometry.getFit2D() except (TypeError, AttributeError): pass else: if self.imageItem.image is not None: if self.displaymode == DisplayMode.raw: x = fit2d['centerX'] y = self._raw_image.shape[-2] - fit2d['centerY'] self.centerplot.setData(x=[x], y=[y]) elif self.displaymode == DisplayMode.remesh: self.centerplot.setData(x=[0], y=[0]) def setGeometry(self, geometry): super(CenterMarker, self).setGeometry(geometry) self.drawCenter()
def __init__(self, name): Node.__init__(self, name, terminals={ 'x': { 'io': 'in' }, 'y': { 'io': 'in' }, 'color': { 'io': 'in' }, 'plotItem': { 'io': 'out' } }) self.ScatterPlotItem = ScatterPlotItem(pen=None, symbol='o', size=2, brush=(255, 255, 255, 160)) self.sigUpdatePlot.connect(self.updatePlot)
class CenterMarker(QSpace): def __init__(self, *args, **kwargs): self.centerplot = ScatterPlotItem(brush="r") self.centerplot.setZValue(100) super(CenterMarker, self).__init__(*args, **kwargs) self.addItem(self.centerplot) self.drawCenter() def drawCenter(self): try: fit2d = self._geometry.getFit2D() except (TypeError, AttributeError): pass else: x = fit2d['centerX'] y = fit2d['centerY'] self.centerplot.setData(x=[x], y=[y]) def setGeometry(self, geometry): super(CenterMarker, self).setGeometry(geometry) self.drawCenter()
def initPlotView(self): self.plot = PlotWidget(enableAutoRange=True) self.plot.setXRange(self.lower - self.range * 0.05, self.upper + self.range * 0.05) self.plotLegand = self.plot.addLegend() self.graphicsView.addWidget(self.plot) self.plotRegion = LinearRegionItem() self.plotRegion.setZValue(10) self.peakPoint = ScatterPlotItem(size=8, pen=mkPen(color='0000FF', width=2), symbol="+", brush=mkBrush(255, 255, 255, 240)) self.plot.addItem(self.plotRegion, ignoreBounds=True) self.plot.addItem(self.peakPoint) self.setGraphViewStyle()
def __init__( self, date_format="%Y-%m-%d %H:%M:%S", y_format="%0.4f", trigger_point_size=10, ): super(DataInspectorLine, self).__init__(angle=90, movable=True) self._labels = [] self._plot_item = None self.y_format = y_format self.trigger_point_size = trigger_point_size self.date_format = date_format self._label_style = "background-color: #35393C;" self.sigPositionChanged.connect(self._inspect) self._highlights = ScatterPlotItem( pos=(), symbol="s", brush="35393C88", pxMode=True, size=trigger_point_size, ) # hack to make the CurvesPropertiesTool ignore the highlight points self._highlights._UImodifiable = False
class Masker(QDialog): """ An interface for the user to draw masks on an image or movie. This includes: - draw masks on the raw image, either by placing discrete vertices or freestyle drawing - modify masks by adding / deleting / moving vertices - change between frames - export masks - apply masks to image files init ---- image_path : str, path to an image file (e.g. ND2 or TIF) max_points_freestyle : int, the maximum number of points to allow for a freestyle mask. This limits memory usage. parent : root QWidget """ def __init__(self, image_path, max_points_freestyle=40, dialog_mode=False, parent=None): super(Masker, self).__init__(parent=parent) self.image_path = image_path self.max_points_freestyle = max_points_freestyle self.dialog_mode = dialog_mode self.initData() self.initUI() def initData(self): """ Load the images and related data. """ # Create the image reader self.imageReader = ImageReader(self.image_path) # Get the first image self.image = self.imageReader.get_frame(0) def initUI(self): """ Initialize the user interface. """ # Main layout L_master = QGridLayout(self) # An ImageView on the left to contain the subject of masking self.imageView = ImageView(parent=self) L_master.addWidget(self.imageView, 0, 0, 15, 2) self.imageView.setImage(self.image) # Override the default ImageView.imageItem.mouseClickEvent self.imageView.imageItem.mouseClickEvent = self._imageView_mouseClickEvent # Override the default ImageView.imageItem.mouseDoubleClickEvent self.imageView.imageItem.mouseDoubleClickEvent = \ self._imageView_mouseDoubleClickEvent # All currently clicked points self.points = [] # All current PolyLineROI objects self.polyLineROIs = [] # ScatterPlotItem, to show current accumulated clicks self.scatterPlotItem = ScatterPlotItem() self.scatterPlotItem.setParentItem(self.imageView.imageItem) ## WIDGETS widget_align = Qt.AlignTop # Frame slider self.frame_slider = IntSlider(minimum=0, maximum=self.imageReader.n_frames - 1, interval=1, init_value=0, name='Frame', parent=self) L_master.addWidget(self.frame_slider, 0, 2, 1, 1, alignment=widget_align) self.frame_slider.assign_callback(self.frame_slider_callback) # Button: create new ROI self.create_roi_mode = False self.B_create_ROI = QPushButton("Draw ROI", parent=self) self.B_create_ROI.clicked.connect(self.B_create_ROI_callback) L_master.addWidget(self.B_create_ROI, 10, 2, 1, 1, alignment=widget_align) # Button: freestyle drawing to make ROI self.freestyle_mode = False self.B_freestyle = QPushButton("Freestyle", parent=self) self.B_freestyle.clicked.connect(self.B_freestyle_callback) L_master.addWidget(self.B_freestyle, 11, 2, 1, 1, alignment=widget_align) # Pure dialog mode: no options to save or load masks, only # define and accept if self.dialog_mode: # Button: accept the current set of masks. This is only meaningful # when this class is being used as a dialog by other classes. self.B_accept = QPushButton("Accept masks", parent=self) self.B_accept.clicked.connect(self.B_accept_callback) L_master.addWidget(self.B_accept, 12, 2, 1, 1, alignment=widget_align) # Non-dialog mode: full range of options else: # Button: apply these masks to the localizations in a file self.B_apply = QPushButton("Apply masks", parent=self) self.B_apply.clicked.connect(self.B_apply_callback) L_master.addWidget(self.B_apply, 12, 2, 1, 1, alignment=widget_align) # Button: save masks to a file self.B_save = QPushButton("Save masks", parent=self) self.B_save.clicked.connect(self.B_save_callback) L_master.addWidget(self.B_save, 13, 2, 1, 1, alignment=widget_align) # Button: load preexisting masks from a file self.B_load = QPushButton("Load masks", parent=self) self.B_load.clicked.connect(self.B_load_callback) L_master.addWidget(self.B_load, 14, 2, 1, 1, alignment=widget_align) # Resize and launch self.resize(800, 600) self.show() ## CORE FUNCTIONS def update_image(self, frame_index=None, autoRange=False, autoLevels=False, autoHistogramRange=False): if frame_index is None: frame_index = self.frame_slider.value() self.image = self.imageReader.get_frame(frame_index) self.imageView.setImage(self.image, autoRange=autoRange, autoLevels=autoLevels, autoHistogramRange=autoHistogramRange) def update_scatter(self): """ Write the contents of self.points to self.scatterPlotItem. """ self.scatterPlotItem.setData( pos=np.asarray(self.points), pxMode=False, symbol='o', pen={ 'color': '#FFFFFF', 'width': 3.0 }, size=2.0, brush=None, ) def clear_scatter(self): self.points = [] self.scatterPlotItem.setData() def createPolyLineROI(self, points): p = PolyLineROI(self.points, closed=True, removable=True) self.polyLineROIs.append(p) self.imageView.view.addItem(self.polyLineROIs[-1]) # If the user requests to remove this ROI, remove it self.polyLineROIs[-1].sigRemoveRequested.connect(self._remove_ROI) def getPoints(self, polyLineROI): """ Return the set of points that make up a PolyLineROI as a 2D ndarray (shape (n_points, 2)). """ state = polyLineROI.getState() return np.asarray([[p.x(), p.y()] for p in state['points']]) def get_currdir(self): """ Get the last directory that was accessed by the user. If no directory was previously accessed, then this is just the same directory as the image file. """ if not hasattr(self, "_currdir"): self.set_currdir(self.image_path) return self._currdir def set_currdir(self, path): """ Set the directory returned by self.get_currdir(). args ---- path : str, a file path or directory path. If a file path, its parent directory is used. """ if os.path.isfile(path): self._currdir = os.path.split(os.path.abspath(path))[0] elif os.path.isdir(path): self._currdir = path def clear_polyLineROIs(self): """ Destroy all current PolyLineROI objects. """ for p in self.polyLineROIs[::-1]: self._remove_ROI(p) ## SIGNAL CALLBACKS def _imageView_mouseClickEvent(self, ev): if ev.button() == QtCore.Qt.RightButton: if self.imageView.imageItem.raiseContextMenu(ev): ev.accept() if ev.button() == QtCore.Qt.LeftButton and self.create_roi_mode: self.points.append(ev.pos()) self.update_scatter() def _imageView_mouseDoubleClickEvent(self, ev): if self.create_roi_mode: self.createPolyLineROI(self.points) self.clear_scatter() self.create_roi_mode = False self.imageView.view.state['mouseEnabled'] = np.array([True, True]) elif self.freestyle_mode: self.B_freestyle_callback() else: pass def _remove_ROI(self, polylineroi): polylineroi.sigRemoveRequested.disconnect(self._remove_ROI) self.polyLineROIs.remove(polylineroi) self.imageView.view.removeItem(polylineroi) del polylineroi ## WIDGET CALLBACKS def frame_slider_callback(self): """ Callback for user changes to the frame slider, which changes the current frame shown beneath the masks. """ frame_index = self.frame_slider.value() self.update_image(frame_index=frame_index) def B_create_ROI_callback(self): """ Callback for user selection of the "Draw ROI" button. Enter create ROI mode, which allows the user to place discrete vertices sequentially to build a mask. """ self.create_roi_mode = True self.imageView.view.state['mouseEnabled'] = np.array([False, False]) def B_freestyle_callback(self): """ Callback for user selection of the "freestyle" button. Enter freestyle mode, which allows the user to draw a mask on the image by dragging the mouse. """ # End freestyle mode by creating a mask from the drawn points if self.freestyle_mode: self.freestyle_mode = False self.imageView.imageItem.setDrawKernel(kernel=None, mask=None, center=None) mask = (self.image == self.draw_val) self.points = get_ordered_mask_points( mask, max_points=self.max_points_freestyle) self.createPolyLineROI(self.points) self.frame_slider_callback() self.points = [] self.B_freestyle.setText("Freestyle") # Start freestyle mode by enabling drawing on self.imageView.imageItem else: self.freestyle_mode = True self.draw_val = int(self.image.max() + 2) kernel = np.array([[self.draw_val]]) self.imageView.imageItem.setDrawKernel(kernel=kernel, mask=None, center=(0, 0)) self.B_freestyle.setText("Finish freestyle") def B_save_callback(self): """ Save the current set of masks to a file. Every currently defined mask is saved in a CSV-like format indexed by mask vertex. This CSV has the following columns: filename : str, the source filename mask_index : int, the index of this mask y : float, the y-coordinate of the vertex x : float, the x-coordinate of the vertex vertex : int, the index of this vertex in the context of its mask The "vertex" always ascends from 0 to n-1, where n is the number of vertices in that mask. """ # If no masks are defined, do nothing if len(self.polyLineROIs) == 0: return # Prompt the user to select an output filename out_path = getSaveFilePath(self, "Select output CSV", "{}_masks.csv".format( os.path.splitext(self.image_path)[0]), "CSV files (*.csv)", initialdir=self.get_currdir()) # For each PolyLineROI mask, get the corresponding set of vertices point_arrays = [self.getPoints(p) for p in self.polyLineROIs] n_masks = len(point_arrays) # Get the total size of the output dataframe m = sum([arr.shape[0] for arr in point_arrays]) # Format as a pandas.DataFrame df = pd.DataFrame( index=np.arange(m), columns=["filename", "mask_index", "y", "x", "vertex"]) df["filename"] = self.image_path c = 0 for mask_index, arr in enumerate(point_arrays): l = arr.shape[0] df.loc[c:c + l - 1, "mask_index"] = mask_index df.loc[c:c + l - 1, "y"] = arr[:, 0] df.loc[c:c + l - 1, "x"] = arr[:, 1] df.loc[c:c + l - 1, "vertex"] = np.arange(l) c += l # Save df.to_csv(out_path, index=False) # Save this directory as the last accessed self.set_currdir(out_path) def B_load_callback(self): """ Load a set of masks from a file previously saved with this GUI. This erases any currently defined masks. """ # Prompt the user to select a file path = getOpenFilePath(self, "Select mask CSV", "CSV and TIF files (*.csv *.tif *.tiff)", initialdir=self.get_currdir()) self.set_currdir(path) ext = os.path.splitext(path)[-1] # Open this file and check that it contains the necessary info if ext == ".csv": try: df = pd.read_csv(path) for c in ["filename", "mask_index", "y", "x", "vertex"]: assert c in df.columns except: print("File {} not in the correct format; must be a CSV with " \ "the 'filename', 'mask_index', 'y', 'x', and 'vertex' " \ "columns".format(path)) return # Warn the user if the image file path in this file doesn't # match the current image file path if df.loc[0, "filename"] != self.image_path: print("WARNING: original file path for this mask file is different " \ "than the current image file.\nOriginal: {}\nCurrent: {}".format( df.loc[0, "filename"], self.image_path)) elif ext in [".tif", ".tiff"]: # Load the mask image masks_im = tifffile.imread(path) assert len(masks_im.shape) == 2, "WARNING: RGB TIFs not supported" # Get the set of unique masks defined in this image n_points = 100 mask_indices = [j for j in list(np.unique(masks_im)) if j != 0] dfs = [] for mi in mask_indices: mask_im = masks_im == mi edges = get_edges(mask_im) points = get_ordered_mask_points(edges, max_points=n_points) df = pd.DataFrame(index=list(range(points.shape[0])), columns=["filename", "mask_index", "y", "x"]) df["filename"] = path df["mask_index"] = mi df["y"] = points[:, 0] df["x"] = points[:, 1] df["vertex"] = list(range(points.shape[0])) dfs.append(df) df = pd.concat(dfs, axis=0, ignore_index=True) # Erase the current set of masks, if any self.clear_polyLineROIs() # Generate a PolyLineROI object for each of the loaded masks for mask_index, mask_frame in df.groupby("mask_index"): self.points = np.asarray(mask_frame[["y", "x"]]) self.createPolyLineROI(self.points) self.points = [] def B_apply_callback(self): """ Callback for user selection of the "Apply masks" button. The user is prompted to select a file containing localizations, and the localizations in that file are classified according to which mask they belong to. """ # Prompt the user to select a file containing localizations path = getOpenFilePath( self, "Select localization file", "CSV files and *Tracked.mat files (*.csv *Tracked.mat)", initialdir=self.get_currdir()) self.set_currdir(path) # Open this file and make sure it actually contains localizations ext = os.path.splitext(path)[-1] try: if ext == ".csv": locs = pd.read_csv(path) assert all([c in locs.columns for c in ['frame', 'y', 'x']]) elif ext == ".mat": locs = tracked_mat_to_csv(path) except: print("Could not load file {}; must either be *.csv or " \ "*Tracked.mat format".format(path)) return # Prompt the user to input the mask column. This can be something # as simple as "mask_index", but the user may want something more # descriptive - like "nuclear_mask" or something. This becomes useful # when the user applies masks multiple times to the same file - for # instance, to label primary and secondary features (e.g. nuclei and # nucleoli) in an image. col = getTextInputs(["Output mask column name"], ["mask_index"], title="Select output column name")[0] # Prompt the user to select a mode for the assignment of trajectories # to masks. This can either be "by_localization" (independent of the # trajectory to which each localization is assigned), "single_point" # (assign all localizations to a mask if a single localization is # inside the mask), or "all_points" (only assign all localizations to a # mask if all localizations are inside the mask) options = ["by_localization", "single_point", "all_points"] box = SingleComboBoxDialog( "Method by which trajectories are assigned to masks", options, init_value="single_point", title="Assignment mode", parent=self) box.exec_() if box.Accepted: mode = box.return_val else: print("Dialog not accepted") return # Assign each localization to one of the current masks point_sets = [self.getPoints(p) for p in self.polyLineROIs] locs[col] = apply_masks(point_sets, locs, mode=mode) # Save to the same file if ext == ".csv": out_path = path elif ext == ".mat": if "_Tracked.mat" in path: out_path = path.replace("_Tracked.mat", ".csv") elif "Tracked.mat" in path: out_path = path.replace("Tracked.mat", ".csv") else: out_path = "{}.csv".format(os.path.splitext(path)[0]) locs.to_csv(out_path, index=False) # Save only the masked trajectories to a different file out_file_inside = "{}_in_mask.csv".format( os.path.splitext(out_path)[0]) out_file_outside = "{}_outside_mask.csv".format( os.path.splitext(out_path)[0]) locs[locs["mask_index"] > 0].to_csv(out_file_inside, index=False) locs[locs["mask_index"] == 0].to_csv(out_file_outside, index=False) # Show the result show_mask_assignments(point_sets, locs, mask_col=col, max_points_scatter=5000) def B_accept_callback(self): """ If this GUI is being used as a dialog, accept and return the currently defined set of masks. """ self.return_val = [self.getPoints(p) for p in self.polyLineROIs] print(self.return_val) self.accept()
def initUI(self): """ Initialize the user interface. """ # Main layout L_master = QGridLayout(self) # An ImageView on the left to contain the subject of masking self.imageView = ImageView(parent=self) L_master.addWidget(self.imageView, 0, 0, 15, 2) self.imageView.setImage(self.image) # Override the default ImageView.imageItem.mouseClickEvent self.imageView.imageItem.mouseClickEvent = self._imageView_mouseClickEvent # Override the default ImageView.imageItem.mouseDoubleClickEvent self.imageView.imageItem.mouseDoubleClickEvent = \ self._imageView_mouseDoubleClickEvent # All currently clicked points self.points = [] # All current PolyLineROI objects self.polyLineROIs = [] # ScatterPlotItem, to show current accumulated clicks self.scatterPlotItem = ScatterPlotItem() self.scatterPlotItem.setParentItem(self.imageView.imageItem) ## WIDGETS widget_align = Qt.AlignTop # Frame slider self.frame_slider = IntSlider(minimum=0, maximum=self.imageReader.n_frames - 1, interval=1, init_value=0, name='Frame', parent=self) L_master.addWidget(self.frame_slider, 0, 2, 1, 1, alignment=widget_align) self.frame_slider.assign_callback(self.frame_slider_callback) # Button: create new ROI self.create_roi_mode = False self.B_create_ROI = QPushButton("Draw ROI", parent=self) self.B_create_ROI.clicked.connect(self.B_create_ROI_callback) L_master.addWidget(self.B_create_ROI, 10, 2, 1, 1, alignment=widget_align) # Button: freestyle drawing to make ROI self.freestyle_mode = False self.B_freestyle = QPushButton("Freestyle", parent=self) self.B_freestyle.clicked.connect(self.B_freestyle_callback) L_master.addWidget(self.B_freestyle, 11, 2, 1, 1, alignment=widget_align) # Pure dialog mode: no options to save or load masks, only # define and accept if self.dialog_mode: # Button: accept the current set of masks. This is only meaningful # when this class is being used as a dialog by other classes. self.B_accept = QPushButton("Accept masks", parent=self) self.B_accept.clicked.connect(self.B_accept_callback) L_master.addWidget(self.B_accept, 12, 2, 1, 1, alignment=widget_align) # Non-dialog mode: full range of options else: # Button: apply these masks to the localizations in a file self.B_apply = QPushButton("Apply masks", parent=self) self.B_apply.clicked.connect(self.B_apply_callback) L_master.addWidget(self.B_apply, 12, 2, 1, 1, alignment=widget_align) # Button: save masks to a file self.B_save = QPushButton("Save masks", parent=self) self.B_save.clicked.connect(self.B_save_callback) L_master.addWidget(self.B_save, 13, 2, 1, 1, alignment=widget_align) # Button: load preexisting masks from a file self.B_load = QPushButton("Load masks", parent=self) self.B_load.clicked.connect(self.B_load_callback) L_master.addWidget(self.B_load, 14, 2, 1, 1, alignment=widget_align) # Resize and launch self.resize(800, 600) self.show()
def __init__(self, settings, server): super().__init__() self.server = server self.timer = QTimer() self.timer.timeout.connect(self.time_tick) self.settings = settings self.current_class = None self.current_students = [] self.stack = QStackedLayout() self.setLayout(self.stack) # First page: connection instructions connect_widget = QWidget() connect_layout = QVBoxLayout(connect_widget) connect_layout.addStretch() connect_label = QLabel( "Open a world in Minecraft, open a terminal (press t), and type:", self) connect_layout.addWidget(connect_label) self.connect_command = f'/connect {server.get_ip()}:{PORT}' connect_command_box = QLineEdit(self.connect_command, self) connect_command_box.setReadOnly(True) connect_layout.addWidget(connect_command_box) connect_copy_button = QPushButton("Copy to Clipboard", self) connect_copy_button.clicked.connect( lambda: QApplication.clipboard().setText(self.connect_command)) connect_layout.addWidget(connect_copy_button) connection_problems_button = QPushButton("Connection Problems?", self) connection_problems_button.clicked.connect(self.show_connection_help) connect_layout.addWidget(connection_problems_button) connect_layout.addStretch() self.stack.addWidget(connect_widget) # Main page main_col_widget = QWidget() columns = QHBoxLayout(main_col_widget) col_left = QVBoxLayout() col_mid = QVBoxLayout() col_right = QVBoxLayout() columns.addLayout(col_left) columns.addSpacing(10) columns.addLayout(col_mid) columns.addSpacing(10) columns.addLayout(col_right) self.stack.addWidget(main_col_widget) self.pause_button = self.setup_toggle_button(col_left, self.server.pause_game, 'Un-pause', self.server.unpause_game, 'Pause') button_size = self.pause_button.size() self.disable_chat_button = self.setup_toggle_button( col_left, self.server.disable_chat, 'Enable Chat', self.server.enable_chat, 'Disable Chat', button_size) self.allow_mobs_button = self.setup_toggle_button( col_left, self.server.disallow_mobs, 'Allow Mobs', self.server.allow_mobs, 'Disable Mobs', button_size) self.allow_destructiveobjects_button = self.setup_toggle_button( col_left, self.server.disallow_destructiveobjects, 'Enable Destructive Items', self.server.allow_destructiveobjects, 'Disable Destructive Items', button_size) self.allow_player_damage_button = self.setup_toggle_button( col_left, self.server.disallow_player_damage, 'Enable Player Damage', self.server.allow_player_damage, 'Disable Player Damage', button_size) self.allow_pvp_button = self.setup_toggle_button( col_left, self.server.disallow_pvp, 'Allow Player Fighting', self.server.allow_pvp, 'Disable Player Fighting', button_size) self.immutable_button = self.setup_toggle_button( col_left, self.server.immutable_world, 'Enable World Modifications', self.server.mutable_world, 'Disable World Modifications', button_size) self.weather_button = self.setup_toggle_button( col_left, self.server.perfect_weather, 'Disable Perfect Weather', self.server.imperfect_weather, 'Enable Perfect Weather', button_size) self.disable_potions_button = self.setup_toggle_button( col_left, self.server.disable_potions, 'Enable Potions', self.server.enable_potions, 'Disable Potions', button_size) # self.clear_potions_button = QPushButton('Clear All Potion Effects', self) # self.clear_potions_button.resize(button_size) # self.clear_potions_button.clicked.connect(lambda: self.server.clear_effects("@a")) # col_left.addWidget(self.clear_potions_button) self.teleport_button = QPushButton('Teleport Everyone to You', self) self.teleport_button.resize(button_size) self.teleport_button.clicked.connect( lambda: self.server.teleport_all_to("@s")) col_left.addWidget(self.teleport_button) self.disconnect_button = QPushButton('Disconnect', self) self.disconnect_button.resize(button_size) self.disconnect_button.clicked.connect(self.server.socket_disconnected) col_left.addWidget(self.disconnect_button) col_left.addStretch() self.version_label = QLabel(f'Version {VERSION}', self) self.version_label.setAlignment(Qt.AlignCenter) col_left.addWidget(self.version_label) self.feedback_button = QPushButton('Feedback/Bug report', self) self.feedback_button.resize(button_size) self.feedback_button.clicked.connect( lambda: QDesktopServices.openUrl(FEEDBACK_URL)) col_left.addWidget(self.feedback_button) # Middle Column: Roll/Register self.classes_combo = QComboBox(self) class_names = self.settings.value("class_names", []) self.classes_combo.addItem("Select class") self.classes_combo.addItem("Add a class") self.classes_combo.addItem("Delete a class") self.classes_combo.addItems(class_names) self.classes_combo.currentTextChanged.connect(self.class_changed) col_mid.addWidget(self.classes_combo) self.users_table = QTableWidget(0, 2) self.users_table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.users_table.setSizePolicy( QSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)) self.users_table.setFixedWidth(140) self.users_table.verticalHeader().hide() header = self.users_table.horizontalHeader() header.setSectionResizeMode(0, QHeaderView.Stretch) header.setSectionResizeMode(1, QHeaderView.ResizeToContents) header.hide() col_mid.addWidget(self.users_table) self.class_edit_button = QPushButton("Edit Class", self) col_mid.addWidget(self.class_edit_button) self.class_edit_button.clicked.connect(self.edit_class) self.chat_box = QPlainTextEdit( f'Minecraft Education Chat Logs {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}', self) self.chat_box.setReadOnly(True) col_right.addWidget(self.chat_box) self.chat_input = QLineEdit(self) self.chat_input.setPlaceholderText("Type chat here; enter to send") self.chat_input.returnPressed.connect(self.chat_enter) col_right.addWidget(self.chat_input) self.chat_save = QPushButton("Save Chat Logs", self) self.chat_save.clicked.connect(self.save_chat) col_right.addWidget(self.chat_save) self.user_map = PlotWidget() self.map_item = ScatterPlotItem(size=10) self.user_map.addItem(self.map_item) self.user_map.getPlotItem().hideAxis('left') self.user_map.getPlotItem().hideAxis('bottom') self.map_item.scene().sigMouseMoved.connect(self.map_hover) map_viewbox = self.map_item.getViewBox() map_viewbox.menu = None col_right.addWidget(self.user_map) self.user_map_info = QLineEdit("Hover over a user", self) self.user_map_info.setReadOnly(True) col_right.addWidget(self.user_map_info) self.setGeometry(300, 300, 800, 600) self.setWindowTitle('MineClass') self.stack.setCurrentIndex(0) self.activate_buttons(False) self.show() if is_newer_version_available(): QMessageBox.about( self, "Newer Version Available", f'A newer version of this program is available <a href="{GITHUB_DOWNLOAD_URL}">here</a>.' ) if not self.settings.value("HasRunFirstTime", False): self.show_connection_help() self.settings.setValue("HasRunFirstTime", True)
class MCClassroom(QWidget): def __init__(self, settings, server): super().__init__() self.server = server self.timer = QTimer() self.timer.timeout.connect(self.time_tick) self.settings = settings self.current_class = None self.current_students = [] self.stack = QStackedLayout() self.setLayout(self.stack) # First page: connection instructions connect_widget = QWidget() connect_layout = QVBoxLayout(connect_widget) connect_layout.addStretch() connect_label = QLabel( "Open a world in Minecraft, open a terminal (press t), and type:", self) connect_layout.addWidget(connect_label) self.connect_command = f'/connect {server.get_ip()}:{PORT}' connect_command_box = QLineEdit(self.connect_command, self) connect_command_box.setReadOnly(True) connect_layout.addWidget(connect_command_box) connect_copy_button = QPushButton("Copy to Clipboard", self) connect_copy_button.clicked.connect( lambda: QApplication.clipboard().setText(self.connect_command)) connect_layout.addWidget(connect_copy_button) connection_problems_button = QPushButton("Connection Problems?", self) connection_problems_button.clicked.connect(self.show_connection_help) connect_layout.addWidget(connection_problems_button) connect_layout.addStretch() self.stack.addWidget(connect_widget) # Main page main_col_widget = QWidget() columns = QHBoxLayout(main_col_widget) col_left = QVBoxLayout() col_mid = QVBoxLayout() col_right = QVBoxLayout() columns.addLayout(col_left) columns.addSpacing(10) columns.addLayout(col_mid) columns.addSpacing(10) columns.addLayout(col_right) self.stack.addWidget(main_col_widget) self.pause_button = self.setup_toggle_button(col_left, self.server.pause_game, 'Un-pause', self.server.unpause_game, 'Pause') button_size = self.pause_button.size() self.disable_chat_button = self.setup_toggle_button( col_left, self.server.disable_chat, 'Enable Chat', self.server.enable_chat, 'Disable Chat', button_size) self.allow_mobs_button = self.setup_toggle_button( col_left, self.server.disallow_mobs, 'Allow Mobs', self.server.allow_mobs, 'Disable Mobs', button_size) self.allow_destructiveobjects_button = self.setup_toggle_button( col_left, self.server.disallow_destructiveobjects, 'Enable Destructive Items', self.server.allow_destructiveobjects, 'Disable Destructive Items', button_size) self.allow_player_damage_button = self.setup_toggle_button( col_left, self.server.disallow_player_damage, 'Enable Player Damage', self.server.allow_player_damage, 'Disable Player Damage', button_size) self.allow_pvp_button = self.setup_toggle_button( col_left, self.server.disallow_pvp, 'Allow Player Fighting', self.server.allow_pvp, 'Disable Player Fighting', button_size) self.immutable_button = self.setup_toggle_button( col_left, self.server.immutable_world, 'Enable World Modifications', self.server.mutable_world, 'Disable World Modifications', button_size) self.weather_button = self.setup_toggle_button( col_left, self.server.perfect_weather, 'Disable Perfect Weather', self.server.imperfect_weather, 'Enable Perfect Weather', button_size) self.disable_potions_button = self.setup_toggle_button( col_left, self.server.disable_potions, 'Enable Potions', self.server.enable_potions, 'Disable Potions', button_size) # self.clear_potions_button = QPushButton('Clear All Potion Effects', self) # self.clear_potions_button.resize(button_size) # self.clear_potions_button.clicked.connect(lambda: self.server.clear_effects("@a")) # col_left.addWidget(self.clear_potions_button) self.teleport_button = QPushButton('Teleport Everyone to You', self) self.teleport_button.resize(button_size) self.teleport_button.clicked.connect( lambda: self.server.teleport_all_to("@s")) col_left.addWidget(self.teleport_button) self.disconnect_button = QPushButton('Disconnect', self) self.disconnect_button.resize(button_size) self.disconnect_button.clicked.connect(self.server.socket_disconnected) col_left.addWidget(self.disconnect_button) col_left.addStretch() self.version_label = QLabel(f'Version {VERSION}', self) self.version_label.setAlignment(Qt.AlignCenter) col_left.addWidget(self.version_label) self.feedback_button = QPushButton('Feedback/Bug report', self) self.feedback_button.resize(button_size) self.feedback_button.clicked.connect( lambda: QDesktopServices.openUrl(FEEDBACK_URL)) col_left.addWidget(self.feedback_button) # Middle Column: Roll/Register self.classes_combo = QComboBox(self) class_names = self.settings.value("class_names", []) self.classes_combo.addItem("Select class") self.classes_combo.addItem("Add a class") self.classes_combo.addItem("Delete a class") self.classes_combo.addItems(class_names) self.classes_combo.currentTextChanged.connect(self.class_changed) col_mid.addWidget(self.classes_combo) self.users_table = QTableWidget(0, 2) self.users_table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.users_table.setSizePolicy( QSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)) self.users_table.setFixedWidth(140) self.users_table.verticalHeader().hide() header = self.users_table.horizontalHeader() header.setSectionResizeMode(0, QHeaderView.Stretch) header.setSectionResizeMode(1, QHeaderView.ResizeToContents) header.hide() col_mid.addWidget(self.users_table) self.class_edit_button = QPushButton("Edit Class", self) col_mid.addWidget(self.class_edit_button) self.class_edit_button.clicked.connect(self.edit_class) self.chat_box = QPlainTextEdit( f'Minecraft Education Chat Logs {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}', self) self.chat_box.setReadOnly(True) col_right.addWidget(self.chat_box) self.chat_input = QLineEdit(self) self.chat_input.setPlaceholderText("Type chat here; enter to send") self.chat_input.returnPressed.connect(self.chat_enter) col_right.addWidget(self.chat_input) self.chat_save = QPushButton("Save Chat Logs", self) self.chat_save.clicked.connect(self.save_chat) col_right.addWidget(self.chat_save) self.user_map = PlotWidget() self.map_item = ScatterPlotItem(size=10) self.user_map.addItem(self.map_item) self.user_map.getPlotItem().hideAxis('left') self.user_map.getPlotItem().hideAxis('bottom') self.map_item.scene().sigMouseMoved.connect(self.map_hover) map_viewbox = self.map_item.getViewBox() map_viewbox.menu = None col_right.addWidget(self.user_map) self.user_map_info = QLineEdit("Hover over a user", self) self.user_map_info.setReadOnly(True) col_right.addWidget(self.user_map_info) self.setGeometry(300, 300, 800, 600) self.setWindowTitle('MineClass') self.stack.setCurrentIndex(0) self.activate_buttons(False) self.show() if is_newer_version_available(): QMessageBox.about( self, "Newer Version Available", f'A newer version of this program is available <a href="{GITHUB_DOWNLOAD_URL}">here</a>.' ) if not self.settings.value("HasRunFirstTime", False): self.show_connection_help() self.settings.setValue("HasRunFirstTime", True) def show_connection_help(self): QMessageBox.about( self, "Connection Help", '''Before using (or if Minecraft has been newly installed), go to Settings->Profile and disable "Require Encrypted Websockets"\n\n Sometimes you'll need to attempt connecting twice (use the up arrow in the Minecraft terminal to access history''' ) def save_chat(self): options = QFileDialog.Options() file_name, _ = QFileDialog.getSaveFileName( self, "Save Chat Logs", "", "Text Files (*.txt);;All Files (*)", options=options) if file_name: with open(file_name, "w") as f: f.write(self.chat_box.toPlainText()) def chat_enter(self): server.send_chat(self.chat_input.text()) self.update_chat_box("Teacher", self.chat_input.text(), "chat") self.chat_input.clear() def map_hover(self, pos): act_pos = self.map_item.mapFromScene(pos) points = self.map_item.pointsAt(act_pos) text = "" for p in points: text += f'{p.data()}: ({round(p.pos()[0])}, {round(p.pos()[1])}), ' self.user_map_info.setText(text) def time_tick(self): self.server.get_users() def start_timer(self): self.time_tick() self.timer.start(10000) def stop_timer(self): self.timer.stop() def update_chat_box(self, sender, message, message_type, receiver=None): t = datetime.datetime.now().strftime("%H:%M:%S") if message_type == "chat": out = f'{t} <{sender}> {message}' elif message_type == "tell": out = f'{t} <{sender} whispers to {receiver}> {message}' self.chat_box.appendPlainText(out) def activate_buttons(self, activate): self.pause_button.setDisabled(not activate) def setup_toggle_button(self, parent, checked_action, checked_text, unchecked_action, unchecked_text, size=None): button = QPushButton(unchecked_text, self) button.resize(button.sizeHint() if size is None else size) button.setCheckable(True) parent.addWidget(button) def toggle_button_clicked(checked_status): if checked_status: checked_action() button.setText(checked_text) else: unchecked_action() button.setText(unchecked_text) button.toggled.connect(toggle_button_clicked) return button def get_students_from_grid(self): try: return [ self.users_table.item(i, 0).text() for i in range(self.users_table.rowCount()) ] except AttributeError: return [] def edit_class(self): selection = self.classes_combo.currentText() if selection in ("Select class", "Add a class"): QMessageBox.about(self, "Error", "Please select (or create) a class first") return current_list = self.get_students_from_grid() new_list, ok_pressed = QInputDialog().getMultiLineText( self, "Edit Class List", "Add or remove students from this class", text="\n".join(current_list)) if ok_pressed: students = [i for i in new_list.split("\n") if i] # list comprehension to remove empty strings self.current_students = students self.settings.setValue(f'classes/{self.current_class}', students) self.load_users() def class_changed(self): selection = self.classes_combo.currentText() # if selection != "Select class": # self.classes_combo.removeItem(self.classes_combo.findText("Select class")) if selection == "Add a class": new_class, ok_pressed = QInputDialog.getText( self, 'Class Name', 'Enter the Class Name or Code') if ok_pressed: classes = self.settings.value('class_names', []) if new_class in classes: print('Class already exists; ignoring') else: classes.append(new_class) self.settings.setValue('class_names', classes) self.classes_combo.addItem(new_class) self.classes_combo.setCurrentIndex( self.classes_combo.findText(new_class)) elif selection == "Select class": pass elif selection == "Delete a class": current_classes = self.settings.value('class_names', []) if current_classes: class_to_delete, ok_pressed = QInputDialog.getItem( self, 'Delete Class', 'Select which class to delete', current_classes, 0, False) if ok_pressed and class_to_delete: current_classes.remove(class_to_delete) self.settings.setValue('class_names', current_classes) self.settings.remove(f'classes/{class_to_delete}') self.classes_combo.removeItem( self.classes_combo.findText(class_to_delete)) #TODO delete stdents from table self.current_class = None self.current_students = [] else: QMessageBox.information(self, "No Classes!", "No Class to delete!") self.classes_combo.setCurrentIndex(0) else: self.current_class = selection self.current_students = self.settings.value( f'classes/{selection}', []) self.load_users() def load_users(self): if len(self.current_students) != self.users_table.rowCount(): self.users_table.setRowCount(len(self.current_students)) for i, user in enumerate(self.current_students): self.users_table.setItem(i, 0, QTableWidgetItem(user)) self.users_table.sortItems(0, QtCore.Qt.AscendingOrder) self.users_table.sortItems(1, QtCore.Qt.DescendingOrder) def update_users_from_mc(self, users): table_user_count = self.users_table.rowCount() for i in range(table_user_count): current_table_user = self.users_table.item(i, 0).text() if current_table_user in users: tick = QTableWidgetItem("✓") tick.setTextAlignment(QtCore.Qt.AlignCenter) tick.setBackground(QColor(QtCore.Qt.green)) self.users_table.setItem(i, 1, tick) users.remove(current_table_user) else: cross = QTableWidgetItem("✗") cross.setTextAlignment(QtCore.Qt.AlignCenter) cross.setBackground(QColor(QtCore.Qt.red)) self.users_table.setItem(i, 1, cross) #handle users from server but not in table self.users_table.setRowCount(table_user_count + len(users)) for i, user in enumerate(users): self.users_table.setItem(i + table_user_count, 0, QTableWidgetItem(user)) self.users_table.sortItems(0, QtCore.Qt.AscendingOrder) self.users_table.sortItems(1, QtCore.Qt.DescendingOrder) self.users_table.resizeRowsToContents() def update_map(self, users): data = [ { 'pos': (int(u['position']['x']), int(u['position']['z'])), 'data': u['name'], 'brush': mkBrush( 'g' ), #mkBrush("r" if u['name'] == self.server.self_name else "g"), 'symbol': ("s" if u['name'] == self.server.self_name else "o"), } for u in users.values() ] self.map_item.setData(data)
def __init__(self, imodel, iview, geom, parent=None): QObject.__init__(self, parent) self.imodel = imodel self.dmodel = imodel.model self.iview = iview self.iview.ui.menuBtn.hide() self.iview.ui.roiBtn.hide() # Thanks CXIVIEW, Anton & Valerio pos = np.array([0.0, 0.5, 1.0]) color = np.array( [[255, 255, 255, 255], [128, 128, 128, 255], [0, 0, 0, 255]], dtype=np.ubyte) new_color_map = ColorMap(pos, color) self.iview.ui.histogram.gradient.setColorMap(new_color_map) self.lastrow = -1 self.curFileName = None self.curFile = None self.canDraw = self.dmodel.canSaveLst() self.checkEvent = "event" in self.dmodel.cols self.yxmap = None self.slab_shape = None self.img_shape = None self.geom_coffset = 0 self.geom_pixsize = None self.im_out = None self.resolutionLambda = None self.pixRadiusToRes = None self.hkl = None self.hklLookup = None self.imodel.dataChanged.connect(self.draw) self.iview.view.menu.addSeparator() openGeomAct = self.iview.view.menu.addAction("Load CrystFEL Geometry") openGeomAct.triggered.connect(self.openGeom) peakmenu = self.iview.view.menu.addMenu("Peaks") self.peakActionGroup = QActionGroup(self) self.peakActionGroup.setExclusive(True) self.peakActionGroup.triggered.connect(self.drawPeaks) peakNone = peakmenu.addAction("None") peakNone.setCheckable(True) peakNone.setChecked(True) peakNone.setData(0) self.peakActionGroup.addAction(peakNone) peakCXI = peakmenu.addAction("CXI") peakCXI.setCheckable(True) peakCXI.setEnabled(self.dmodel.canSaveLst()) peakCXI.setData(1) self.peakActionGroup.addAction(peakCXI) peakStream = peakmenu.addAction("Stream") peakStream.setCheckable(True) peakStream.setEnabled(self.dmodel.hasStreamPeaks()) peakStream.setData(2) self.peakActionGroup.addAction(peakStream) self.peakCanvas = ScatterPlotItem() self.iview.getView().addItem(self.peakCanvas) reflectionmenu = self.iview.view.menu.addMenu("Reflections") self.reflectionActionGroup = QActionGroup(self) self.reflectionActionGroup.setExclusive(True) self.reflectionActionGroup.triggered.connect(self.drawReflections) refNone = reflectionmenu.addAction("None") refNone.setCheckable(True) refNone.setChecked(True) refNone.setData(0) self.reflectionActionGroup.addAction(refNone) refStream = reflectionmenu.addAction("Stream") refStream.setCheckable(True) refStream.setEnabled(self.dmodel.hasStreamReflections()) refStream.setData(1) self.reflectionActionGroup.addAction(refStream) self.reflectionCanvas = ScatterPlotItem() self.iview.getView().addItem(self.reflectionCanvas) self.drawResRingsAct = self.iview.view.menu.addAction( "Resolution Rings") self.drawResRingsAct.setCheckable(True) self.drawResRingsAct.setChecked(False) self.drawResRingsAct.triggered.connect(self.drawResRings) self.drawResRingsAct.setEnabled(self.yxmap is not None) self.resolutionRingsCanvas = ScatterPlotItem() self.iview.getView().addItem(self.resolutionRingsCanvas) self.resRingsTextItems = [] for x in self.dmodel.cfg.viewerResolutionRingsAngstroms: self.resRingsTextItems.append(TextItem('', anchor=(0.5, 0.8))) self.iview.getView().addItem(self.resRingsTextItems[-1]) self.drawResolutionLimitAct = self.iview.view.menu.addAction( "Resolution Limit Ring") self.drawResolutionLimitAct.setCheckable(True) self.drawResolutionLimitAct.setChecked(False) self.drawResolutionLimitAct.triggered.connect(self.drawResLimitRing) self.drawResolutionLimitAct.setEnabled( self.yxmap is not None and 'reslim' in self.dmodel.cols) self.resolutionLimitCanvas = ScatterPlotItem() self.iview.getView().addItem(self.resolutionLimitCanvas) if geom is not None: self.loadGeom(geom) self.toolTipsAct = self.iview.view.menu.addAction( "Show Position in Tool Tip") self.toolTipsAct.setCheckable(True) self.toolTipsAct.setChecked(True) self.iview.scene.sigMouseMoved.connect(self.mouseMove) self.draw()
def initUI(self): # Master layout self.win = QWidget() L_master = QGridLayout(self.win) # Left subwindow, for widgets win_left = QWidget(self.win) L_left = QGridLayout(win_left) L_master.addWidget(win_left, 0, 0, 1, 3) # Right subwindow, for images win_right = QWidget(self.win) L_right = QGridLayout(win_right) L_master.addWidget(win_right, 0, 3, 1, 1) # Add four LabeledImageViews to this window liv_labels = ['(1) Raw', '(2) Filtered', '(3) Convolved', '(4) Binary'] self.LIVs = [LabeledImageView(parent=win_left, label=liv_labels[j]) \ for j in range(4)] for j in range(4): L_left.addWidget(self.LIVs[j], j % 2, j // 2) self.LIVs[j].setImage(self.images[j]) # Link the views between the ImageViews for j in range(1, 4): self.LIVs[j].ImageView.view.setXLink(self.LIVs[0].ImageView.view) self.LIVs[j].ImageView.view.setYLink(self.LIVs[0].ImageView.view) # Set up three ScatterPlotItems for each of the first three ImageViews, # for overlaying detections as symbols self.SPIs = [ScatterPlotItem() for j in range(3)] for i in range(3): self.SPIs[i].setParentItem(self.LIVs[i].ImageView.imageItem) ## WIDGETS widget_align = Qt.AlignTop slider_min_width = 125 # Frame slider self.frame_slider = IntSlider(minimum=self.start_frame, maximum=self.stop_frame, interval=1, name='Frame', init_value=0, min_width=slider_min_width, parent=win_right) L_right.addWidget(self.frame_slider, 0, 0, alignment=widget_align) self.frame_slider.assign_callback(self.frame_slider_callback) # Select filtering method filter_methods = keys_to_str(FILTER_SLIDER_CONFIG.keys()) self.M_filter = LabeledQComboBox(filter_methods, "Filter method", init_value="identity", parent=win_right) L_right.addWidget(self.M_filter, 1, 0, alignment=widget_align) self.M_filter.assign_callback(self.M_filter_callback) # Select filtering chunk size chunk_size_choices = [ str(i) for i in [2, 5, 10, 15, 20, 30, 40, 60, 80, 100, 200] ] self.M_chunk_size = LabeledQComboBox(chunk_size_choices, "Chunk size") L_right.addWidget(self.M_chunk_size, 2, 0, alignment=widget_align) self.M_chunk_size.assign_callback(self.M_chunk_size_callback) self.M_chunk_size.setCurrentText(str(self.ChunkFilter.chunk_size)) # Three semantically-flexible filtering FloatSliders self.filter_sliders = [ FloatSlider(parent=win_right, min_width=slider_min_width) for j in range(3) ] for i in range(len(self.filter_sliders)): L_right.addWidget(self.filter_sliders[i], i + 3, 0, alignment=widget_align) self.filter_sliders[i].assign_callback( getattr(self, "filter_slider_%d_callback" % i)) self.filter_sliders[i].hide() # Button to change ROI self.B_change_roi = QPushButton("Change ROI", win_right) L_right.addWidget(self.B_change_roi, 6, 0, alignment=widget_align) self.B_change_roi.clicked.connect(self.B_change_roi_callback) # Button to change frame range self.B_change_frame_range = QPushButton("Change frame range", win_right) L_right.addWidget(self.B_change_frame_range, 7, 0, alignment=widget_align) self.B_change_frame_range.clicked.connect( self.B_change_frame_range_callback) # Select detection method detect_methods = keys_to_str(DETECT_SLIDER_CONFIG.keys()) self.M_detect = LabeledQComboBox(detect_methods, "Detect method", init_value="llr", parent=win_right) L_right.addWidget(self.M_detect, 0, 1, alignment=widget_align) # Set the initial detection method and arguments init_detect_method = 'llr' self.M_detect.setCurrentText(init_detect_method) self.detect_kwargs = {DETECT_SLIDER_CONFIG[init_detect_method][i]['name']: \ DETECT_SLIDER_CONFIG[init_detect_method][i]['init_value'] \ for i in DETECT_SLIDER_CONFIG[init_detect_method].keys()} self.detect_kwargs['method'] = init_detect_method self.M_detect.assign_callback(self.M_detect_callback) # Four semantically-flexible detection FloatSliders self.detect_sliders = [ FloatSlider(parent=win_right, min_width=slider_min_width) for j in range(4) ] for i in range(len(self.detect_sliders)): L_right.addWidget(self.detect_sliders[i], i + 1, 1, alignment=widget_align) self.detect_sliders[i].assign_callback( getattr(self, "detect_slider_%d_callback" % i)) # Autoscale the detection threshold self.B_auto_threshold = QPushButton("Rescale threshold", parent=win_right) L_right.addWidget(self.B_auto_threshold, 6, 1, alignment=widget_align) self.B_auto_threshold.clicked.connect(self.B_auto_threshold_callback) # Overlay detections as symbols self.B_spot_overlay_state = False self.B_spot_overlay = QPushButton("Overlay detections", parent=win_right) L_right.addWidget(self.B_spot_overlay, 7, 1, alignment=widget_align) self.B_spot_overlay.clicked.connect(self.B_spot_overlay_callback) # Change the symbol used for spot overlays symbol_choices = ['+', 'o', 'open +'] self.M_symbol = LabeledQComboBox(symbol_choices, "Spot symbol", init_value='o', parent=win_right) L_right.addWidget(self.M_symbol, 5, 1, alignment=widget_align) self.M_symbol.assign_callback(self.M_symbol_callback) # Save result to file self.B_save = QPushButton("Save settings", parent=win_right) L_right.addWidget(self.B_save, 8, 1, alignment=widget_align) self.B_save.clicked.connect(self.B_save_callback) # Show individual spots self.B_show_spots = QPushButton("Show individual spots", parent=win_right) L_right.addWidget(self.B_show_spots, 9, 1, alignment=widget_align) self.B_show_spots.clicked.connect(self.B_show_spots_callback) ## EMPTY WIDGETS for j in range(8, 24): _ql = QLabel(win_right) L_right.addWidget(_ql, j, 0, alignment=widget_align) ## KEYBOARD SHORTCUTS - tab right/left through frames self.left_shortcut = QShortcut(QKeySequence(QtGui_Qt.Key_Left), self.win) self.right_shortcut = QShortcut(QKeySequence(QtGui_Qt.Key_Right), self.win) self.left_shortcut.activated.connect(self.tab_prev_frame) self.right_shortcut.activated.connect(self.tab_next_frame) ## INITIALIZATION self.change_detect_method(init_detect_method) self.filter() self.detect() self.auto_threshold() self.update_images(1, 2, 3, autoRange=True, autoLevels=True, autoHistogramRange=True) # Resize GUI self.win.resize(self.gui_width, self.gui_height) # Show the main window self.win.show()
def setupLayout(self): self.layout = QHBoxLayout(self) self.splitter = QSplitter(Qt.Horizontal) self.settingswidget = QWidget(self) self.settingswidget.setMaximumWidth(400) self.settingslayout = QVBoxLayout() self.percolationWidget = PercolationWidget() self.graph_type_combobox = QComboBox(self) self.graph_type_combobox.addItems(self.graph_names) self.graph_type_combobox.currentIndexChanged.connect( self.handleGraphTypeChanged) self.graph_type_combobox.setFocusPolicy(Qt.NoFocus) self.settingslayout.addWidget(self.graph_type_combobox) self.p_slider_layout = QHBoxLayout() self.settingslayout.addLayout(self.p_slider_layout) self.p_slider_layout.addWidget(QLabel("Set p value: ", self)) self.p_slider_label = QLabel(self) self.p_slider = QSlider(Qt.Horizontal, self) self.p_slider.valueChanged.connect(self.handlePSliderChange) self.p_slider_layout.addWidget(self.p_slider) self.p_slider_layout.addWidget(self.p_slider_label) self.N_slider_layout = QHBoxLayout() self.settingslayout.addLayout(self.N_slider_layout) self.N_slider_layout.addWidget(QLabel("Set size (N): ", self)) self.N_slider_label = QLabel(self) self.N_slider = QSlider(Qt.Horizontal, self) self.N_slider.valueChanged.connect(self.handleNSliderChange) self.N_slider_layout.addWidget(self.N_slider) self.N_slider_layout.addWidget(self.N_slider_label) # wether to refresh edges each time or keep edges to add to/remove from self.refresh_edges_checkbox = QCheckBox( "Refresh Edges instead of Adding/Removing", self) self.refresh_edges_checkbox.stateChanged.connect( self.handleRefreshCheckboxChange) self.settingslayout.addWidget(self.refresh_edges_checkbox) self.settingslayout.addSpacerItem( QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.plot_widget = PlotWidget(name="clustersize by p") self.settingslayout.addWidget(self.plot_widget) self.plot_widget.setLabel('left', 'maximum cluster size', units='%') self.plot_widget.setLabel('bottom', 'p', units='') self.plot_widget.setXRange(0, 1) self.plot_widget.setYRange(0, 1) self.plot = ScatterPlotItem() self.plot_widget.addItem(self.plot) self.plot_clear_button = QPushButton("Clear plot") self.plot_clear_button.clicked.connect(self.handlePlotClearButton) self.settingslayout.addWidget(self.plot_clear_button) self.settingswidget.setLayout(self.settingslayout) self.settingswidget.setMinimumSize(50, 50) self.splitter.addWidget(self.settingswidget) self.splitter.addWidget(self.percolationWidget) self.layout.addWidget(self.splitter) self.percolationWidget.graphUpdated.connect(self.handleGraphUpdated) self.setWindowTitle('Percolation Model') self.setFocus()
class AttributeViewer(QWidget): """ Show a scatter plot of two attributes from a set of localizations. init ---- locs_path : str, path to a CSV with localization data max_spots : int, the maximum number of spots to plot. It's often too costly to render more than 30000 spots or so gui_size : int parent : root QWidget """ def __init__(self, locs_path, max_spots=10000, gui_size=600, parent=None): super(AttributeViewer, self).__init__(parent=parent) self.locs_path = locs_path self.max_spots = max_spots self.gui_size = gui_size self.initData() self.initUI() def initData(self): """ Load the spots data. """ # Load the locs dataframe self.locs = pd.read_csv(self.locs_path).sort_values(by='frame') # If too many spots, truncate the dataframe if len(self.locs) > self.max_spots: print("Truncating dataframe to %d spots" % self.max_spots) self.locs = self.locs[:self.max_spots] else: print("Plotting %d spots" % len(self.locs)) # List of available attributes to display dtypes = [ 'float64', 'float', 'int64', 'int32', 'uint16', 'uint8', 'int' ] self.parameters = [c for c in self.locs.columns if \ self.locs[c].dtype in dtypes] # Generate colormap self.n_colors = 1000 self.cmap = cm.get_cmap('viridis', self.n_colors) self.cmap_hex = np.array([mpl_colors.rgb2hex(self.cmap(i)[:3]) \ for i in range(self.n_colors)]) def initUI(self): """ Initialize the user interface. """ # Main window self.win = QWidget() L = QGridLayout(self.win) self.win.resize(self.gui_size * 1.8, self.gui_size) # Two subwindows: one on the left for the scatter plot # and one on the right for widgets self.win_left = QWidget(self.win) self.win_right = QWidget(self.win) L.addWidget(self.win_left, 0, 0, 1, 3) L.addWidget(self.win_right, 0, 3, 1, 1) L_left = QGridLayout(self.win_left) L_right = QGridLayout(self.win_right) # GraphicsLayoutWidget, to organize scatter plot and # associated items self.graphicsLayoutWidget = GraphicsLayoutWidget(parent=self.win_left) # PlotItem, to contain the ScatterPlotItem self.plotItem = self.graphicsLayoutWidget.addPlot() L_left.addWidget(self.graphicsLayoutWidget, 0, 0) # ScatterPlotItem, core data display self.scatterPlotItem = ScatterPlotItem(symbol='o', brush=None, pxMode=True, pen={ 'color': '#FFFFFF', 'width': 4.0 }, size=4.0) self.plotItem.addItem(self.scatterPlotItem) ## WIDGETS widget_align = Qt.AlignTop # Select parameter to map to the x-axis self.M_par_0 = LabeledQComboBox(self.parameters, "x-parameter", init_value="x", parent=self.win_right) L_right.addWidget(self.M_par_0, 0, 0, alignment=widget_align) self.M_par_0.assign_callback(self.M_par_callback) # Select parameter to map to the y-axis self.M_par_1 = LabeledQComboBox(self.parameters, "y-parameter", init_value="y", parent=self.win_right) L_right.addWidget(self.M_par_1, 1, 0, alignment=widget_align) self.M_par_1.assign_callback(self.M_par_callback) # Select which attribute to color the localizations by options = self.parameters + ["density"] self.M_color_by = LabeledQComboBox(options, "Color by", init_value="density", parent=self.win_right) L_right.addWidget(self.M_color_by, 0, 1, alignment=widget_align) self.M_color_by.assign_callback(self.M_color_by_callback) # Select the size of the window to use when computing # localization density window_size_options = [ str(j) for j in [3, 5, 7, 9, 11, 13, 15, 19, 23, 31, 41, 61, 81, 101] ] self.M_density_window = LabeledQComboBox(window_size_options, "Density window", init_value="7", parent=self.win_right) L_right.addWidget(self.M_density_window, 1, 1, alignment=widget_align) self.M_density_window.assign_callback(self.M_density_window_callback) # Button to induce a simpler representation that can handle # more spots self.simple_mode = False self.B_simple = QPushButton("Simple scatter", parent=self.win_right) L_right.addWidget(self.B_simple, 2, 0, alignment=widget_align) self.B_simple.clicked.connect(self.B_simple_callback) # Button to toggle log color scaling self.log_scale_mode = True self.B_log = QPushButton("Log color scale", parent=self.win_right) self.B_log.clicked.connect(self.B_log_callback) L_right.addWidget(self.B_log, 2, 1, alignment=widget_align) # Empty widgets to manipulate the layout n_rows = 15 for j in range(3, n_rows): q = QWidget(self.win_right) L_right.addWidget(q, j, 0) # Show the main window self.update_scatter() self.win.show() ## CORE FUNCTIONS def get_pars(self): """ Get the current attributes mapped to the x and y axes of the scatter plot. returns ------- (str: x attribute, str: y attribute) """ return self.M_par_0.currentText(), self.M_par_1.currentText() def update_scatter(self, rescale=True): """ Update the scatter plot. args ---- rescale : change axis limits """ cx, cy = self.get_pars() # In the special case of plotting x vs y, make sure # that the aspect ratio is right if ((cx == 'x') and (cy == 'y')) or ((cx == 'y') and (cy == 'x')): self.plotItem.setAspectLocked(lock=True) else: self.plotItem.setAspectLocked(lock=False) # Update the scatter plots if self.simple_mode: self.scatter_simple(cx, cy) else: self.scatter_color(cx, cy) # Set axis labels labelStyle = {'font-size': '18pt'} self.plotItem.setLabel('bottom', text=cx, **labelStyle) self.plotItem.setLabel('left', text=cy, **labelStyle) # Change axis limits if rescale: self.plotItem.autoBtnClicked() def scatter_color(self, cx, cy): """ Load the current set of data into a new scatter plot, coloring by the current "color by" attribute. args ---- cx, cy : str, columns in self.locs """ self.scatterPlotItem.setData(size=4.0, brush=None) color_attrib = self.M_color_by.currentText() if color_attrib == 'density': densities, spots = self.make_density(cx, cy) else: spots = self.make_attrib_colors(color_attrib) self.scatterPlotItem.setData(spots=spots) def scatter_simple(self, cx, cy): """ A simpler way of representing the data, using black and white only. args ---- cx, cy : str, columns in self.locs """ self.scatterPlotItem.setData(self.locs[cx], self.locs[cy], pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 255, 10), size=10) def make_density(self, c0, c1): """ Get the normalized density of points along the (c0, c1) axis in the set of localizations. args ---- c0, c1 : str, columns in self.locs returns ------- ( 1D ndarray, the density of each point; dict, argument to pass to ScatterPlotItem.setData ) """ # Get the current density window size w = int(self.M_density_window.currentText()) # Format the desired two attributes as ndarray for # fast indexing data = np.asarray(self.locs[[c0, c1]]) # Each of the attributes could have very different # magnitudes, so scale the distances relative to the # difference between the 5th and 95th percentiles for # each attribute. # Special case: x and y get the same scaling if ((c0 == 'x') and (c1 == 'y')) or ((c0 == 'y') and (c1 == 'x')): ybinsize = 1.0 xbinsize = 1.0 else: norm = 400.0 ybinsize = (np.percentile(self.locs[c0], 95) - \ np.percentile(self.locs[c0], 5)) / norm xbinsize = (np.percentile(self.locs[c1], 95) - \ np.percentile(self.locs[c1], 5)) / norm # Create the binning scheme from these bin sizes ybinedges = np.arange(np.percentile(self.locs[c0], 0.1), np.percentile(self.locs[c0], 99.9), ybinsize) xbinedges = np.arange(np.percentile(self.locs[c1], 0.1), np.percentile(self.locs[c1], 99.9), xbinsize) # Bin the data H, _yedges, _xedges = np.histogram2d(self.locs[c0], self.locs[c1], bins=(ybinedges, xbinedges)) # Count the number of points in the neighborhood # of each point density = uniform_filter(H, w) # Digitize the data according to the binning scheme # X_int = ((data - np.array([ybinedges.min(), xbinedges.min()])) / \ # np.array([ybinsize, xbinsize])).astype('int64') # data_y_int = X_int[:,0] # data_x_int = X_int[:,1] data_y_int = np.digitize(data[:, 0], ybinedges) data_x_int = np.digitize(data[:, 1], xbinedges) # Determine which data points fall within the binning # scheme inside = (data_y_int>=0) & (data_x_int>=0) & \ (data_y_int<(len(ybinedges)-1)) & \ (data_x_int<(len(xbinedges)-1)) data_y_int_inside = data_y_int[inside] data_x_int_inside = data_x_int[inside] # Retrieve the densities for each point point_densities = np.empty(data.shape[0], dtype=density.dtype) point_densities[inside] = density[data_y_int_inside, data_x_int_inside] # Set points outside the binning scheme to density 1 point_densities[~inside] = 1.0 # Rescale a little point_densities = point_densities * (w**2) point_densities[point_densities <= 0] = 0.001 # Log-scale if self.log_scale_mode: point_densities = np.log(point_densities) / np.log(2.0) point_densities[point_densities < 0.0] = 0.0 # Rescale the point densities into color indices R = (point_densities * (self.n_colors - 1) / point_densities.max()).astype(np.int64) R[~inside] = 0 spot_colors = self.cmap_hex[R] # Format the plotting spot dict spots = [{'pos': data[i,:], 'pen': {'color': spot_colors[i], 'width': 3.0}} \ for i in range(data.shape[0])] return point_densities, spots def make_attrib_colors(self, attrib): """ Generate a spot format in which the color of each spot is keyed to a particular attribute, with appropriate rescaling for the different magnitudes of each attribute. args ---- attrib : str, a column in self.locs returns ------- dict, parameters to pass to ScatterPlotItem.setData """ # Current axis attributes c0, c1 = self.get_pars() # Sort by ascending attribute self.locs = self.locs.sort_values(by=attrib) XY = np.asarray(self.locs[[c0, c1]]) Z = np.asarray(self.locs[attrib]) # Filter out unsanitary values sanitary = (~np.isnan(Z)) & (~np.isinf(Z)) if (~sanitary).any(): print("Filtering out unsanitary values in %s" % attrib) XY = XY[sanitary, :] Z = Z[sanitary] # Log scale if self.log_scale_mode: Z[Z <= 0.0] = 0.001 Z = np.log(Z) / np.log(2.0) # Bin localizations by their relative value in # the color attribute norm = 400.0 try: binsize = (np.percentile(Z, 95) - np.percentile(Z, 5)) / norm binedges = np.arange(np.percentile(Z, 0.01), np.percentile(Z, 99.9), binsize) Z[Z < binedges.min()] = binedges.min() Z[Z >= binedges.max()] = binedges.max() - binsize assignments = np.digitize(Z, bins=binedges).astype('float64') except ValueError: print("Can't generate a color scale for %s; " \ "try turning off log scale" % attrib) return [] # Scale into the available color indices spot_colors = self.cmap_hex[((assignments * (self.n_colors-1)) / \ assignments.max()).astype(np.int64)] # Generate the argument to ScatterPlotItem.setData spots = [{'pos': XY[i,:], 'pen': {'color': spot_colors[i], 'width': 3.0}} \ for i in range(XY.shape[0])] return spots ## WIDGET CALLBACKS def M_par_callback(self): """ Change the attribute mapped to the axes. """ self.update_scatter(rescale=True) def M_color_by_callback(self): """ Change which attribute is used to generate the color scheme for the localizations. """ self.update_scatter(rescale=False) def M_density_window_callback(self): """ Change the size of the window used to determine the local density of each localization in the current plane. """ if self.M_color_by.currentText() == "density": self.update_scatter(rescale=False) def B_simple_callback(self): """ Change into simple plotting mode, which uses less memory to render the plot but doesn't support color. """ self.simple_mode = not self.simple_mode self.update_scatter(rescale=False) def B_log_callback(self): """ Toggle between log and linear scales for the color map. """ self.log_scale_mode = not self.log_scale_mode print("Log scale: ", self.log_scale_mode) self.update_scatter(rescale=False)
def handlePlotClearButton(self): self.plot = ScatterPlotItem() self.plot_widget.clear() self.plot_widget.addItem(self.plot) self.plot_widget.update()
class DataInspectorLine(InfiniteLine): """ DataInspectorLine provides a moveable vertical line item that shows labels containing the coordinates of the points of existing curves it touches. It provides a method to attach it to a PlotItem. .. todo:: for now it only works on the main viewbox. """ # TODO: support more than 1 viewbox (e.g. y2axis). # TODO: modify anchor of labels so that they are plotted on the left if # they do not fit in the view def __init__( self, date_format="%Y-%m-%d %H:%M:%S", y_format="%0.4f", trigger_point_size=10, ): super(DataInspectorLine, self).__init__(angle=90, movable=True) self._labels = [] self._plot_item = None self.y_format = y_format self.trigger_point_size = trigger_point_size self.date_format = date_format self._label_style = "background-color: #35393C;" self.sigPositionChanged.connect(self._inspect) self._highlights = ScatterPlotItem( pos=(), symbol="s", brush="35393C88", pxMode=True, size=trigger_point_size, ) # hack to make the CurvesPropertiesTool ignore the highlight points self._highlights._UImodifiable = False def _inspect(self): """ Slot to re inspector line movemethe mouse move event, and perform the action on the plot. :param evt: mouse event """ xpos = self.pos().x() x_px_size, _ = self.getViewBox().viewPixelSize() self._removeLabels() points = [] # iterate over the existing curves for c in self._plot_item.curves: if c is self._highlights: continue if c.xData is not None: # find the index of the closest point of this curve adiff = numpy.abs(c.xData - xpos) idx = numpy.argmin(adiff) # only add a label if the line touches the symbol tolerance = 0.5 * max(1, c.opts["symbolSize"]) * x_px_size if adiff[idx] < tolerance: points.append((c.xData[idx], c.yData[idx])) self._createLabels(points) def _createLabels(self, points): for x, y in points: # create label at x,y _x = self._getXValue(x) _y = self._getYValue(y) text_item = TextItem() text_item.setPos(x, y) text_item.setHtml(("<div style='{}'> " + "<span><b>x=</b>{} " + "<span><b>y=</b>{}</span> " + "</div>").format( self._label_style, _x, _y)) self._labels.append(text_item) self._plot_item.addItem(text_item, ignoreBounds=True) # Add "highlight" marker at each point self._highlights.setData(pos=points) def _getXValue(self, x): """ Helper method converting x value to time if necessary :param x: current x value :return: time or normal x value (depends of the x axis type) """ x_axis = self._plot_item.getAxis("bottom") if isinstance(x_axis, DateAxisItem): return self._timestampToDateTime(x) else: return x def _getYValue(self, y): return str(self.y_format % y) def _timestampToDateTime(self, timestamp): """ Method used to caste the timestamp from the curve to date in proper format (%Y-%m-%d %H:%M:%S) :param timestamp: selected timestamp from curve """ return datetime.utcfromtimestamp(timestamp).strftime(self.date_format) def _removeLabels(self): # remove existing texts for item in self._labels: self.getViewBox().removeItem(item) self._labels = [] # remove existing highlights self._highlights.setData(pos=()) def attachToPlotItem(self, plot_item): """ Method to attach :class:`DataInspectorLine` to the plot :param plot: to attach """ self._plot_item = plot_item self._plot_item.addItem(self, ignoreBounds=True) self._plot_item.addItem(self._highlights, ignoreBounds=True) def dettach(self): """ Method use to detach the class:`DataInspectorLine` from the plot """ self._removeLabels() self._plot_item.removeItem(self._highlights) self._plot_item.removeItem(self) self._plot_item = None
class CentroidScatterPlotWidget(QWidget, Ui_ScatterPlot): """This class handles managing the centroid scatter plot and the histogram projections for both x and y coordinates. Attributes ---------- brushColor : tuple The common color for all brushes in the widget. brushes : list A set of brushes with varying alpha. dataCounter : int Number of times data arrays have been appended to up until array size. dataSize : int The requested size of the data arrays. histogramFillBrush : QtGui.QBrush Brush used to fill the projection histograms. maxAlpha : int The maximum transparency value for a brush minAlpha : int The minimum transparency value for a brush. numBins : int Number of histogram bins for the projections. penColor : tuple The outline color for all the points. pointBrush : QtGui.QBrush The brush for the scatter plot points. pointPen : QtGui.QPen The outline pen for the scatter plot points. rollArray : bool Flag as to when to start rolling the data arrays of centroid values. scatterPlotItem : pyqtgraph.ScatterPlotItem Instance of the centroid scatter plot. xData : numpy.array Container for the x coordinate of the centroid. xHistogramItem : pyqtgraph.PlotItem Instance of the x coordinate histogram projection. yData : numpy.array Container for the y coordinate of the centroid. yHistogramItem : pyqtgraph.PlotItem Instance of the y coordinate histogram projection. """ def __init__(self, parent=None): """Initialize the class. Parameters ---------- parent : None, optional Top-level widget. """ super().__init__(parent) self.setupUi(self) p1 = self.scatterPlot.addPlot() self.scatterPlotItem = ScatterPlotItem() p1.addItem(self.scatterPlotItem) p1.setLabel('left', 'Y', units='pixel') p1.setLabel('bottom', 'X', units='pixel') self.yHistogramItem = None self.xHistogramItem = None self.numBins = 40 self.dataSize = None self.xData = None self.yData = None self.rollArray = False self.dataCounter = 0 self.brushes = None self.brushColor = (159, 159, 159) self.penColor = (255, 255, 255) self.maxAlpha = 255 self.minAlpha = 127 self.histogramFillBrush = mkBrush(*self.brushColor, 200) self.pointBrush = mkBrush(*self.brushColor, self.maxAlpha) self.pointPen = mkPen(*self.penColor) self.scatterPlotItem.setBrush(self.pointBrush) def clearPlot(self): """Reset all data and clear the plot. """ self.rollArray = False self.dataCounter = 0 self.xData = np.array([]) self.yData = np.array([]) self.scatterPlotItem.setData(self.xData, self.yData) self.xHistogramItem.setData([], [], stepMode=False) self.yHistogramItem.setData([], [], stepMode=False) self.scatterPlotItem.getViewBox().setRange(xRange=(0, 1), yRange=(0, 1), disableAutoRange=False) self.xHistogramItem.getViewBox().setRange(xRange=(0, 1), yRange=(0, 1), disableAutoRange=False) self.yHistogramItem.getViewBox().setRange(xRange=(0, 1), yRange=(0, 1), disableAutoRange=False) def getConfiguration(self): """Get the current plot configuration. Returns ------- int The set of current configuration parameters. """ return self.numBins def makeBrushes(self): """Make brushes for spots with differnet alpha factors. """ self.brushes = [] deltaAlpha = self.maxAlpha - self.minAlpha slope = deltaAlpha / (self.dataSize - 1) for i in range(self.dataSize): alpha = slope * i + self.minAlpha self.brushes.append(mkBrush(*self.brushColor, int(alpha))) #c = int(alpha) #self.brushes.append(mkBrush(c, c, c, self.maxAlpha)) def setArraySize(self, arraySize): """Update the stored array size and adjust arrays. Parameters ---------- arraySize : int The new array size to use. """ self.dataSize = arraySize self.xData = np.array([]) self.yData = np.array([]) self.makeBrushes() self.rollArray = False def setConfiguration(self, config): """Set the new parameters into the widget. Parameters ---------- config : `config.CentroidPlotConfig` The new parameters to apply. """ self.numBins = config.numHistogramBins def setup(self, arraySize): """Provide information for setting up the plot. Parameters ---------- arraySize : int The size for the plot data arrays. """ self.dataSize = arraySize self.xData = np.array([]) self.yData = np.array([]) self.makeBrushes() p1 = self.yHistogram.addPlot() self.yHistogramItem = p1.plot(self.yData, self.yData) self.yHistogramItem.setRotation(90) p2 = self.xHistogram.addPlot() self.xHistogramItem = p2.plot(self.xData, self.xData) def updateData(self, centroidX, centroidY): """Update the data arrays with a new centroid coordinate pair. Parameters ---------- centroidX : float The current x coordinate of the centroid to plot. centroidY : float The current y coordinate of the centroid to plot. """ if self.rollArray: self.xData[:-1] = self.xData[1:] self.xData[-1] = centroidX self.yData[:-1] = self.yData[1:] self.yData[-1] = centroidY else: # This does create copies of arrays, so watch performance. self.xData = np.append(self.xData, centroidX) self.yData = np.append(self.yData, centroidY) if self.dataCounter < self.dataSize: self.dataCounter += 1 if self.dataCounter == self.dataSize: self.rollArray = True def showPlot(self): """Show the scatter and histogram plots. """ self.scatterPlotItem.setData(self.xData, self.yData, pen=self.pointPen, brush=self.brushes) xy, xx = np.histogram(self.xData, bins=np.linspace(np.min(self.xData), np.max(self.xData), self.numBins)) self.xHistogramItem.setData(xx, xy, stepMode=True, fillLevel=0, fillBrush=self.histogramFillBrush) yy, yx = np.histogram(self.yData, bins=np.linspace(np.min(self.yData), np.max(self.yData), self.numBins)) # Flip due to rotated plot yy *= -1 self.yHistogramItem.setData(yx, yy, stepMode=True, fillLevel=0, fillBrush=self.histogramFillBrush)
class AppWidget(QWidget): def __init__(self): QWidget.__init__(self) self.graph_types = [Lattice_2d, Triangles_2d, Honeycomb_2d, Lattice_3d] self.graph_names = [ "2D-Grid", "2D-Triangles", "2D-Honeycomb", "3D-Grid" ] self.setupLayout() self.setSettings() def setSettings(self): self.graph_type_combobox.setCurrentIndex(0) self.p_slider.setValue(100) self.N_slider.setValue(5) self.refresh_edges_checkbox.setCheckState(Qt.Checked) def setupLayout(self): self.layout = QHBoxLayout(self) self.splitter = QSplitter(Qt.Horizontal) self.settingswidget = QWidget(self) self.settingswidget.setMaximumWidth(400) self.settingslayout = QVBoxLayout() self.percolationWidget = PercolationWidget() self.graph_type_combobox = QComboBox(self) self.graph_type_combobox.addItems(self.graph_names) self.graph_type_combobox.currentIndexChanged.connect( self.handleGraphTypeChanged) self.graph_type_combobox.setFocusPolicy(Qt.NoFocus) self.settingslayout.addWidget(self.graph_type_combobox) self.p_slider_layout = QHBoxLayout() self.settingslayout.addLayout(self.p_slider_layout) self.p_slider_layout.addWidget(QLabel("Set p value: ", self)) self.p_slider_label = QLabel(self) self.p_slider = QSlider(Qt.Horizontal, self) self.p_slider.valueChanged.connect(self.handlePSliderChange) self.p_slider_layout.addWidget(self.p_slider) self.p_slider_layout.addWidget(self.p_slider_label) self.N_slider_layout = QHBoxLayout() self.settingslayout.addLayout(self.N_slider_layout) self.N_slider_layout.addWidget(QLabel("Set size (N): ", self)) self.N_slider_label = QLabel(self) self.N_slider = QSlider(Qt.Horizontal, self) self.N_slider.valueChanged.connect(self.handleNSliderChange) self.N_slider_layout.addWidget(self.N_slider) self.N_slider_layout.addWidget(self.N_slider_label) # wether to refresh edges each time or keep edges to add to/remove from self.refresh_edges_checkbox = QCheckBox( "Refresh Edges instead of Adding/Removing", self) self.refresh_edges_checkbox.stateChanged.connect( self.handleRefreshCheckboxChange) self.settingslayout.addWidget(self.refresh_edges_checkbox) self.settingslayout.addSpacerItem( QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.plot_widget = PlotWidget(name="clustersize by p") self.settingslayout.addWidget(self.plot_widget) self.plot_widget.setLabel('left', 'maximum cluster size', units='%') self.plot_widget.setLabel('bottom', 'p', units='') self.plot_widget.setXRange(0, 1) self.plot_widget.setYRange(0, 1) self.plot = ScatterPlotItem() self.plot_widget.addItem(self.plot) self.plot_clear_button = QPushButton("Clear plot") self.plot_clear_button.clicked.connect(self.handlePlotClearButton) self.settingslayout.addWidget(self.plot_clear_button) self.settingswidget.setLayout(self.settingslayout) self.settingswidget.setMinimumSize(50, 50) self.splitter.addWidget(self.settingswidget) self.splitter.addWidget(self.percolationWidget) self.layout.addWidget(self.splitter) self.percolationWidget.graphUpdated.connect(self.handleGraphUpdated) self.setWindowTitle('Percolation Model') self.setFocus() def handleGraphTypeChanged(self, index): if (self.graph_types[index].dim == 3): self.N_slider.setValue(10) self.graph = self.graph_types[index](self.N_slider.value()) self.percolationWidget.setGraph(self.graph) self.setFocus() def handlePSliderChange(self, value): self.percolationWidget.setPValue(value) self.p_slider_label.setText(str(value) + " %") def handleNSliderChange(self, value): self.graph = self.graph_types[self.graph_type_combobox.currentIndex()]( value) self.percolationWidget.setGraph(self.graph) self.N_slider_label.setText(str(value)) def handleRefreshCheckboxChange(self, value): self.percolationWidget.setRefresh(value == Qt.Checked) def handleGraphUpdated(self, p, clustersize): self.plot.addPoints(x=[p], y=[clustersize]) def handlePlotClearButton(self): self.plot = ScatterPlotItem() self.plot_widget.clear() self.plot_widget.addItem(self.plot) self.plot_widget.update() def keyPressEvent(self, event): if event.key() in [ Qt.Key_W, Qt.Key_A, Qt.Key_S, Qt.Key_D, Qt.Key_Q, Qt.Key_E, Qt.Key_Plus, Qt.Key_Minus ]: self.percolationWidget.graphwidget.keyPressEvent(event) elif event.key() == Qt.Key_Tab: self.setFocus() else: QWidget.keyPressEvent(self, event)
def initUI(self): """ Initialize the user interface. """ # Main window self.win = QWidget() L = QGridLayout(self.win) self.win.resize(self.gui_size * 1.8, self.gui_size) # Two subwindows: one on the left for the scatter plot # and one on the right for widgets self.win_left = QWidget(self.win) self.win_right = QWidget(self.win) L.addWidget(self.win_left, 0, 0, 1, 3) L.addWidget(self.win_right, 0, 3, 1, 1) L_left = QGridLayout(self.win_left) L_right = QGridLayout(self.win_right) # GraphicsLayoutWidget, to organize scatter plot and # associated items self.graphicsLayoutWidget = GraphicsLayoutWidget(parent=self.win_left) # PlotItem, to contain the ScatterPlotItem self.plotItem = self.graphicsLayoutWidget.addPlot() L_left.addWidget(self.graphicsLayoutWidget, 0, 0) # ScatterPlotItem, core data display self.scatterPlotItem = ScatterPlotItem(symbol='o', brush=None, pxMode=True, pen={ 'color': '#FFFFFF', 'width': 4.0 }, size=4.0) self.plotItem.addItem(self.scatterPlotItem) ## WIDGETS widget_align = Qt.AlignTop # Select parameter to map to the x-axis self.M_par_0 = LabeledQComboBox(self.parameters, "x-parameter", init_value="x", parent=self.win_right) L_right.addWidget(self.M_par_0, 0, 0, alignment=widget_align) self.M_par_0.assign_callback(self.M_par_callback) # Select parameter to map to the y-axis self.M_par_1 = LabeledQComboBox(self.parameters, "y-parameter", init_value="y", parent=self.win_right) L_right.addWidget(self.M_par_1, 1, 0, alignment=widget_align) self.M_par_1.assign_callback(self.M_par_callback) # Select which attribute to color the localizations by options = self.parameters + ["density"] self.M_color_by = LabeledQComboBox(options, "Color by", init_value="density", parent=self.win_right) L_right.addWidget(self.M_color_by, 0, 1, alignment=widget_align) self.M_color_by.assign_callback(self.M_color_by_callback) # Select the size of the window to use when computing # localization density window_size_options = [ str(j) for j in [3, 5, 7, 9, 11, 13, 15, 19, 23, 31, 41, 61, 81, 101] ] self.M_density_window = LabeledQComboBox(window_size_options, "Density window", init_value="7", parent=self.win_right) L_right.addWidget(self.M_density_window, 1, 1, alignment=widget_align) self.M_density_window.assign_callback(self.M_density_window_callback) # Button to induce a simpler representation that can handle # more spots self.simple_mode = False self.B_simple = QPushButton("Simple scatter", parent=self.win_right) L_right.addWidget(self.B_simple, 2, 0, alignment=widget_align) self.B_simple.clicked.connect(self.B_simple_callback) # Button to toggle log color scaling self.log_scale_mode = True self.B_log = QPushButton("Log color scale", parent=self.win_right) self.B_log.clicked.connect(self.B_log_callback) L_right.addWidget(self.B_log, 2, 1, alignment=widget_align) # Empty widgets to manipulate the layout n_rows = 15 for j in range(3, n_rows): q = QWidget(self.win_right) L_right.addWidget(q, j, 0) # Show the main window self.update_scatter() self.win.show()
def __init__(self, manager: BarManager): """""" ScatterPlotItem.__init__(self) super(CandleItem,self).__init__(manager) self.orders : Dict[int,Dict[str,OrderData]] = {} # {ix:{orderid:order}}
class CrystfelImage(QObject): def __init__(self, imodel, iview, geom, parent=None): QObject.__init__(self, parent) self.imodel = imodel self.dmodel = imodel.model self.iview = iview self.iview.ui.menuBtn.hide() self.iview.ui.roiBtn.hide() # Thanks CXIVIEW, Anton & Valerio pos = np.array([0.0, 0.5, 1.0]) color = np.array( [[255, 255, 255, 255], [128, 128, 128, 255], [0, 0, 0, 255]], dtype=np.ubyte) new_color_map = ColorMap(pos, color) self.iview.ui.histogram.gradient.setColorMap(new_color_map) self.lastrow = -1 self.curFileName = None self.curFile = None self.canDraw = self.dmodel.canSaveLst() self.checkEvent = "event" in self.dmodel.cols self.yxmap = None self.slab_shape = None self.img_shape = None self.geom_coffset = 0 self.geom_pixsize = None self.im_out = None self.resolutionLambda = None self.pixRadiusToRes = None self.hkl = None self.hklLookup = None self.imodel.dataChanged.connect(self.draw) self.iview.view.menu.addSeparator() openGeomAct = self.iview.view.menu.addAction("Load CrystFEL Geometry") openGeomAct.triggered.connect(self.openGeom) peakmenu = self.iview.view.menu.addMenu("Peaks") self.peakActionGroup = QActionGroup(self) self.peakActionGroup.setExclusive(True) self.peakActionGroup.triggered.connect(self.drawPeaks) peakNone = peakmenu.addAction("None") peakNone.setCheckable(True) peakNone.setChecked(True) peakNone.setData(0) self.peakActionGroup.addAction(peakNone) peakCXI = peakmenu.addAction("CXI") peakCXI.setCheckable(True) peakCXI.setEnabled(self.dmodel.canSaveLst()) peakCXI.setData(1) self.peakActionGroup.addAction(peakCXI) peakStream = peakmenu.addAction("Stream") peakStream.setCheckable(True) peakStream.setEnabled(self.dmodel.hasStreamPeaks()) peakStream.setData(2) self.peakActionGroup.addAction(peakStream) self.peakCanvas = ScatterPlotItem() self.iview.getView().addItem(self.peakCanvas) reflectionmenu = self.iview.view.menu.addMenu("Reflections") self.reflectionActionGroup = QActionGroup(self) self.reflectionActionGroup.setExclusive(True) self.reflectionActionGroup.triggered.connect(self.drawReflections) refNone = reflectionmenu.addAction("None") refNone.setCheckable(True) refNone.setChecked(True) refNone.setData(0) self.reflectionActionGroup.addAction(refNone) refStream = reflectionmenu.addAction("Stream") refStream.setCheckable(True) refStream.setEnabled(self.dmodel.hasStreamReflections()) refStream.setData(1) self.reflectionActionGroup.addAction(refStream) self.reflectionCanvas = ScatterPlotItem() self.iview.getView().addItem(self.reflectionCanvas) self.drawResRingsAct = self.iview.view.menu.addAction( "Resolution Rings") self.drawResRingsAct.setCheckable(True) self.drawResRingsAct.setChecked(False) self.drawResRingsAct.triggered.connect(self.drawResRings) self.drawResRingsAct.setEnabled(self.yxmap is not None) self.resolutionRingsCanvas = ScatterPlotItem() self.iview.getView().addItem(self.resolutionRingsCanvas) self.resRingsTextItems = [] for x in self.dmodel.cfg.viewerResolutionRingsAngstroms: self.resRingsTextItems.append(TextItem('', anchor=(0.5, 0.8))) self.iview.getView().addItem(self.resRingsTextItems[-1]) self.drawResolutionLimitAct = self.iview.view.menu.addAction( "Resolution Limit Ring") self.drawResolutionLimitAct.setCheckable(True) self.drawResolutionLimitAct.setChecked(False) self.drawResolutionLimitAct.triggered.connect(self.drawResLimitRing) self.drawResolutionLimitAct.setEnabled( self.yxmap is not None and 'reslim' in self.dmodel.cols) self.resolutionLimitCanvas = ScatterPlotItem() self.iview.getView().addItem(self.resolutionLimitCanvas) if geom is not None: self.loadGeom(geom) self.toolTipsAct = self.iview.view.menu.addAction( "Show Position in Tool Tip") self.toolTipsAct.setCheckable(True) self.toolTipsAct.setChecked(True) self.iview.scene.sigMouseMoved.connect(self.mouseMove) self.draw() def draw(self): if not self.canDraw or self.imodel.currow == self.lastrow: return # Load the image image = self.image() if image is None: return # Problem with file, don't try to load anything else # Image was loaded, so update what we've currently got drawn self.lastrow = self.imodel.currow # Apply geometry if self.yxmap is None: image = np.transpose(image) else: image = cfel_geom.apply_geometry_from_pixel_maps( image, self.yxmap, self.im_out) self.iview.setImage(image, autoLevels=False, autoRange=False) # CXI View Scaling bottom, top = histogram_clip_levels( image.ravel(), self.dmodel.cfg.cxiviewHistClipLevelValue) self.iview.setLevels(bottom, top) self.iview.getHistogramWidget().setHistogramRange( min(bottom, self.dmodel.cfg.cxiviewHistMin), max(top, self.dmodel.cfg.cxiviewHistMax), padding=self.dmodel.cfg.cxiviewHistPadding) self.calcResLambda() self.drawPeaks() self.drawReflections() self.drawResRings() self.drawResLimitRing() def loadGeom(self, filename): self.yxmap, self.slab_shape, self.img_shape = cfel_geom.pixel_maps_for_image_view( filename) self.im_out = np.zeros(self.img_shape, dtype=np.dtype(float)) self.geom_coffset = cfel_geom.coffset_from_geometry_file(filename) self.geom_pixsize = 1 / cfel_geom.res_from_geometry_file(filename) self.drawResRingsAct.setEnabled(True) self.drawResolutionLimitAct.setEnabled('reslim' in self.dmodel.cols) self.lastrow = -1 self.draw() def openGeom(self): name = QFileDialog.getOpenFileName( self.iview, 'Select CrystFEL Geomery File (.geom)', filter='*.geom') if qt5: if name: self.loadGeom(name[0]) elif name is not None and len(name): self.loadGeom(name) def mouseMove(self, pos): mapped = self.iview.getView().mapSceneToView(pos) txt = "" if self.toolTipsAct.isChecked() and self.iview.image is not None \ and mapped.x() >= 0 and mapped.x() < self.iview.image.shape[0] \ and mapped.y() >= 0 and mapped.y() < self.iview.image.shape[1]: if self.img_shape is not None: x = mapped.x() - self.img_shape[0] / 2 y = self.img_shape[1] / 2 - mapped.y() else: x = mapped.x() y = mapped.y() txt = "x: %i, y: %i" % (x, y) if self.iview.image is not None: txt += " value: %.2f" % self.iview.image[int(mapped.x()), int(mapped.y())] if self.pixRadiusToRes is not None: txt += " resolution: %.2f" % self.pixRadiusToRes( np.sqrt(x**2 + y**2)) if self.hklLookup is not None: res = self.hklLookup.query([mapped.x(), mapped.y()], k=1) if len(res ) > 0 and res[0] < self.dmodel.cfg.viewerReflectionSize: txt += " hkl: %s" % self.hkl[res[1]] self.iview.setToolTip(txt) # Sub functions called by draw, not meant to be called externally def fromMaybeEvent(self, paths): r = None for path in paths: if path in self.curFile: if self.checkEvent: e = self.dmodel.data["event"][self.imodel.currow] if e == -1: # No event r = self.curFile[path] else: r = self.curFile[path][e] else: # No events r = self.curFile[path] break return r def image(self): image = None ifile = self.dmodel.value("ifile", self.imodel.currow, False) if ifile != self.curFileName: if self.curFile is not None: self.curFile.close() self.curFileName = ifile self.curFile = h5py.File(ifile, 'r') image = self.fromMaybeEvent(self.dmodel.cfg.imageH5paths) return image def cxipeaks(self): # Since this function should ideally only be called internally from draw, # after image has been successfully generated, assume we have correct and valid ifile. px = self.fromMaybeEvent(self.dmodel.cfg.peakXH5paths) if px is None: px = [] py = [] else: if len(px.shape) > 1: # Data is two dimension, assume x is first column and y second py = px[:, 1] px = px[:, 0] else: py = self.fromMaybeEvent(self.dmodel.cfg.peakYH5paths) if py is None: px = [] py = [] return px, py def drawPeaks(self): px = [] py = [] if self.peakActionGroup.checkedAction().data() == 1: # H5 Peaks px, py = self.cxipeaks() elif self.peakActionGroup.checkedAction().data() == 2: # Stream peaks px, py = self.dmodel.streamPeaks(self.imodel.currow) if self.yxmap is not None and len(px): px = np.array(px, dtype=np.dtype(int)) py = np.array(py, dtype=np.dtype(int)) slab = py * self.slab_shape[1] + px px = self.yxmap[0][slab] py = self.yxmap[1][slab] self.peakCanvas.setData(px,py,symbol=self.dmodel.cfg.viewerPeakSymbol,\ size=self.dmodel.cfg.viewerPeakSize,pen=\ mkPen(self.dmodel.cfg.viewerPeakColor,width=self.dmodel.cfg.viewerPeakPenWidth),\ brush=(0,0,0,0),pxMode=False) def drawReflections(self): px = [] py = [] self.hkl = None self.hklLookup = None if self.reflectionActionGroup.checkedAction().data() == 1: # Stream px, py, self.hkl = self.dmodel.streamReflections( self.imodel.currow) if self.yxmap is not None and len(px): px = np.array(px, dtype=np.dtype(int)) py = np.array(py, dtype=np.dtype(int)) slab = py * self.slab_shape[1] + px px = self.yxmap[0][slab] py = self.yxmap[1][slab] self.reflectionCanvas.setData(px,py,symbol=self.dmodel.cfg.viewerReflectionSymbol,\ size=self.dmodel.cfg.viewerReflectionSize,pen=\ mkPen(self.dmodel.cfg.viewerReflectionColor,width=self.dmodel.cfg.viewerReflectionPenWidth),\ brush=(0,0,0,0),pxMode=False) if self.hkl is not None: self.hklLookup = scipy.spatial.cKDTree(np.dstack((px, py))[0]) def calcResLambda(self): self.resolutionLambda = None photon_ev = None if "phoen" in self.dmodel.cols: photon_ev = self.dmodel.data["phoen"][self.imodel.currow] if photon_ev is None or photon_ev <= 0: photon_ev = self.fromMaybeEvent( self.dmodel.cfg.viewerPhotonEvH5Paths) clen = None if "aclen" in self.dmodel.cols: clen = self.dmodel.data["aclen"][ self.imodel.currow] # Value is corrected with coffset if clen is None: clen = self.fromMaybeEvent( self.dmodel.cfg.viewerCameraLengthH5Paths) if clen is not None: # Uncorrected, correct here clen += self.geom_coffset # Need valid photon energy, camera length, and geometry (yxmap) if photon_ev is None or photon_ev <= 0 or clen is None or self.yxmap is None: return # Not enough info lmbd = scipy.constants.h * scipy.constants.c / (scipy.constants.e * photon_ev) self.resolutionLambda=lambda r : (2.0/self.geom_pixsize)*(clen)*np.tan(2.0*np.arcsin(lmbd / \ (2.0 * r *1e-10))) self.pixRadiusToRes = lambda r: 1e10 * lmbd / (2.0 * np.sin( 0.5 * np.arctan(r * self.geom_pixsize / clen))) def drawResRings(self): if not self.drawResRingsAct.isChecked( ) or self.resolutionLambda is None: self.resolutionRingsCanvas.setData([], []) for ti in self.resRingsTextItems: ti.setText('') else: ring_sizes = self.resolutionLambda( np.array(self.dmodel.cfg.viewerResolutionRingsAngstroms)) self.resolutionRingsCanvas.setData([self.img_shape[0]/2]*len(ring_sizes), [self.img_shape[1]/2]*len(ring_sizes),symbol='o',\ size=ring_sizes,pen=mkPen(self.dmodel.cfg.viewerResRingColor, width=self.dmodel.cfg.viewerResRingWidth),\ brush=(0,0,0,0),pxMode=False) for i, ti in enumerate(self.resRingsTextItems): ti.setText("%.1f A" % self.dmodel.cfg.viewerResolutionRingsAngstroms[i], color=self.dmodel.cfg.viewerResRingColor) ti.setPos(self.img_shape[0] / 2, self.img_shape[1] / 2 + ring_sizes[i] / 2) def drawResLimitRing(self): if not self.drawResolutionLimitAct.isChecked( ) or self.resolutionLambda is None: self.resolutionLimitCanvas.setData([], []) else: r = self.dmodel.data['reslim'][self.imodel.currow] if r <= 0: self.resolutionLimitCanvas.setData([], []) else: r = 10 / r self.resolutionLimitCanvas.setData([self.img_shape[0]/2], [self.img_shape[1]/2],symbol='o',\ size=self.resolutionLambda(r),\ pen=mkPen(self.dmodel.cfg.viewerResLimitRingColor, width=self.dmodel.cfg.viewerResLimitRingWidth),\ brush=(0,0,0,0),pxMode=False)
def initUI(self): """ Initialize the user interface. """ # Main window self.win = QWidget() # Figure out the GUI size self.AR = float(self.imageReader.width) / self.imageReader.height self.win.resize(self.gui_size*1.5, self.gui_size) L_main = QGridLayout(self.win) # A subwindow on the left for widgets, and a subwindow # on the right for the image self.win_right = QWidget(self.win) self.win_left = QWidget(self.win) L_right = QGridLayout(self.win_right) L_left = QGridLayout(self.win_left) L_main.addWidget(self.win_right, 0, 1, 1, 3) L_main.addWidget(self.win_left, 0, 0, 1, 1) ## IMAGES / OVERLAYS # ImageView, for rendering image self.imageView = ImageView(parent=self.win_right) L_right.addWidget(self.imageView, 0, 0) # ScatterPlotItem, for overlaying localizations self.scatterPlotItem = ScatterPlotItem() self.scatterPlotItem.setParentItem(self.imageView.imageItem) # GraphItem, for overlaying trajectory histories when # desired self.graphItem = GraphItem() self.graphItem.setParentItem(self.imageView.imageItem) # # Make spots clickable self.lastClicked = [] self.scatterPlotItem.sigClicked.connect(self.spot_clicked) ## WIDGETS widget_align = Qt.AlignTop # Frame slider self.frame_slider = IntSlider(minimum=0, interval=1, maximum=self.imageReader.n_frames-1, init_value=self.start_frame, name='Frame', parent=self.win_left) L_left.addWidget(self.frame_slider, 0, 0, rowSpan=2, alignment=widget_align) self.frame_slider.assign_callback(self.frame_slider_callback) # Button to toggle spot overlays self.B_overlay_state = True self.B_overlay = QPushButton("Overlay spots", parent=self.win_left) self.B_overlay.clicked.connect(self.B_overlay_callback) L_left.addWidget(self.B_overlay, 1, 0, alignment=widget_align) # Button to toggle trajectory trails self.B_overlay_trails_state = False self.B_overlay_trails = QPushButton("Overlay histories", parent=self.win_left) self.B_overlay_trails.clicked.connect(self.B_overlay_trails_callback) L_left.addWidget(self.B_overlay_trails, 1, 1, alignment=widget_align) self.B_overlay_trails.stackUnder(self.B_overlay) # Menu to select current overlay symbol symbol_options = keys_to_str(symbol_sizes.keys()) self.M_symbol = LabeledQComboBox(symbol_options, "Overlay symbol", init_value="o", parent=self.win_left) self.M_symbol.assign_callback(self.M_symbol_callback) L_left.addWidget(self.M_symbol, 2, 0, alignment=widget_align) # Menu to select how the spots are colored color_by_options = ["None", "Trajectory", "Quantitative attribute", "Boolean attribute"] self.M_color_by = LabeledQComboBox(color_by_options, "Color spots by", init_value="None", parent=self.win_left) self.M_color_by.assign_callback(self.M_color_by_callback) L_left.addWidget(self.M_color_by, 3, 0, alignment=widget_align) # Create a new binary spot condition self.B_create_condition = QPushButton("Create Boolean attribute", parent=self.win_left) self.B_create_condition.clicked.connect(self.B_create_condition_callback) L_left.addWidget(self.B_create_condition, 4, 0, alignment=widget_align) # Select the binary column that determines color when "condition" # is selected as the color-by attribute condition_options = [c for c in self.locs.columns if self.locs[c].dtype == 'bool'] self.M_condition = LabeledQComboBox(condition_options, "Boolean attribute", init_value="None", parent=self.win_left) L_left.addWidget(self.M_condition, 3, 1, alignment=widget_align) self.M_condition.assign_callback(self.M_condition_callback) # Compare spots in a separate window that shows a grid of spots self.B_compare_spots = QPushButton("Compare spots", parent=self) L_left.addWidget(self.B_compare_spots, 2, 1, alignment=widget_align) self.B_compare_spots.clicked.connect(self.B_compare_spot_callback) # Some placeholder widgets, for better formatting for j in range(6, 18): q = QLabel(parent=self.win_left) L_left.addWidget(q, j, 0, alignment=widget_align) ## KEYBOARD SHORTCUTS - tab right/left through frames self.left_shortcut = QShortcut(QKeySequence(QtGui_Qt.Key_Left), self.win) self.right_shortcut = QShortcut(QKeySequence(QtGui_Qt.Key_Right), self.win) self.left_shortcut.activated.connect(self.tab_prev_frame) self.right_shortcut.activated.connect(self.tab_next_frame) ## DISPLAY self.update_image(autoRange=True, autoLevels=True, autoHistogramRange=True) self.overlay_spots() if "trajectory" in self.locs.columns: self.M_color_by.setCurrentText("Trajectory") self.M_color_by_callback() self.win.show()
def render(self, intent): if not self.canvas_widget: bases_names = getattr(intent, 'mixins', tuple()) or tuple() bases = map( lambda name: plugin_manager.type_mapping['PlotMixinPlugin'][ name], bases_names) self.canvas_widget = type('PlotViewBlend', (*bases, PlotIntentCanvasBlend), {})() self.layout().addWidget(self.canvas_widget) self.canvas_widget.plotItem.addLegend() items = [] if isinstance(intent, (PlotIntent, ErrorBarIntent, ScatterIntent)): x = intent.x if intent.x is not None: x = np.asarray(intent.x).squeeze() ys = np.asarray(intent.y).squeeze() if ys.ndim == 1: ys = [ys] multicurves = False else: multicurves = True symbol = intent.kwargs.get("symbol", None) for i in range(len(ys)): name = intent.name if multicurves: name += f' {i + 1}' if isinstance(intent, ScatterIntent): item = ScatterPlotItem(x=x, y=ys[i], name=name, symbol=symbol) self.canvas_widget.plotItem.addItem(item) elif isinstance(intent, (PlotIntent, ErrorBarIntent)): item = self.canvas_widget.plot(x=x, y=ys[i], name=name, symbol=symbol) items.append(item) # Use most recent intent's log mode for the canvas's log mode x_log_mode = intent.kwargs.get( "xLogMode", self.canvas_widget.plotItem.getAxis("bottom").logMode) y_log_mode = intent.kwargs.get( "yLogMode", self.canvas_widget.plotItem.getAxis("left").logMode) self.canvas_widget.plotItem.setLogMode(x=x_log_mode, y=y_log_mode) self.canvas_widget.setLabels(**intent.labels) if isinstance(intent, ErrorBarIntent): kwargs = intent.kwargs.copy() for key, value in kwargs.items(): if isinstance(value, DataArray): kwargs[key] = np.asanyarray(value).squeeze() erroritem = ErrorBarItem(x=np.asarray(intent.x).squeeze(), y=np.asarray(intent.y).squeeze(), **kwargs) self.canvas_widget.plotItem.addItem(erroritem) items.append(erroritem) elif isinstance(intent, BarIntent): kwargs = intent.kwargs.copy() for key, value in kwargs.items(): if isinstance(value, DataArray): kwargs[key] = np.asanyarray(value).squeeze() if intent.x is not None: kwargs['x'] = intent.x baritem = pg.BarGraphItem(**kwargs) self.canvas_widget.plotItem.addItem(baritem) items.append(baritem) self.intent_to_items[intent] = items self.colorize() return items
def __init__(self, manager: BarManager, am: ArrayManager): """""" ScatterPlotItem.__init__(self) super(ExChartItem, self).__init__(manager, am) self.orders: Dict[int, Dict[str, Order]] = {} # {ix:{orderid:order}}
class SpotViewer(QWidget): """ Overlay localizations or trajectories onto a raw movie. Useful for QC. The user sees a single image window with the current frame and some overlaid localizations. Each localization is either colored identically or according to a color scheme. Available color schemes are - color each localization by its trajectory index (same color for locs in the same trajectory) - color each localization by one of its numerical attributes (e.g. intensity, BG, H_det, etc.) - color each localization one of two colors according to its value for a boolean attribute. SpotViewer provides the user a way to create new boolean attributes by thresholding attributes, via the "Create Boolean attribute" button. args ---- image_path : str, path to image file (e.g. *.nd2, *.tif, or *.tiff) locs_path : str, path to CSV with localizations gui_size : int start_frame : int, the initial frame shown parent : root QWidget """ def __init__(self, image_path, locs_path, gui_size=800, start_frame=0, parent=None): super(SpotViewer, self).__init__(parent=parent) self.image_path = image_path self.locs_path = locs_path self.gui_size = gui_size self.start_frame = start_frame self.initData() self.initUI() def initData(self): """ Load the datasets for this SpotViewer instance. """ # Make sure the required paths exist assert os.path.isfile(self.image_path) assert os.path.isfile(self.locs_path) assert os.path.splitext(self.image_path)[1] in \ ['.nd2', '.tif', '.tiff'], \ "Image file must be one of (*.nd2 *.tif *.tiff)" # Make an image file reader self.imageReader = ImageReader(self.image_path) # Load the set of localizations self.locs = pd.read_csv(self.locs_path) # Some additional options if these localizations are # assigned a trajectory if 'trajectory' in self.locs.columns: # Assign the traj_len, if it doesn't already exist self.locs = traj_length(self.locs) # Keep an array of localizations as an ndarray, for # fast indexing when overlaying trajectory histories self.tracks = np.asarray( self.locs.loc[ self.locs['traj_len']>1, ['frame', 'trajectory', 'y', 'x'] ].sort_values(by=['trajectory', 'frame']) ) # Adjust for off-by-1/2 plot pixel indexing self.tracks[:,2:] = self.tracks[:,2:] + 0.5 # Generate the set of colors self.generate_color_schemes() # Assign condition colors (initially all negative) self.assign_condition_colors('y') # Load the first round of spots self.load_image(self.start_frame) # Assign each trajectory a color self.assign_traj_colors() # Assign self.locs_curr, the current set of spots self.load_spots(self.start_frame) def initUI(self): """ Initialize the user interface. """ # Main window self.win = QWidget() # Figure out the GUI size self.AR = float(self.imageReader.width) / self.imageReader.height self.win.resize(self.gui_size*1.5, self.gui_size) L_main = QGridLayout(self.win) # A subwindow on the left for widgets, and a subwindow # on the right for the image self.win_right = QWidget(self.win) self.win_left = QWidget(self.win) L_right = QGridLayout(self.win_right) L_left = QGridLayout(self.win_left) L_main.addWidget(self.win_right, 0, 1, 1, 3) L_main.addWidget(self.win_left, 0, 0, 1, 1) ## IMAGES / OVERLAYS # ImageView, for rendering image self.imageView = ImageView(parent=self.win_right) L_right.addWidget(self.imageView, 0, 0) # ScatterPlotItem, for overlaying localizations self.scatterPlotItem = ScatterPlotItem() self.scatterPlotItem.setParentItem(self.imageView.imageItem) # GraphItem, for overlaying trajectory histories when # desired self.graphItem = GraphItem() self.graphItem.setParentItem(self.imageView.imageItem) # # Make spots clickable self.lastClicked = [] self.scatterPlotItem.sigClicked.connect(self.spot_clicked) ## WIDGETS widget_align = Qt.AlignTop # Frame slider self.frame_slider = IntSlider(minimum=0, interval=1, maximum=self.imageReader.n_frames-1, init_value=self.start_frame, name='Frame', parent=self.win_left) L_left.addWidget(self.frame_slider, 0, 0, rowSpan=2, alignment=widget_align) self.frame_slider.assign_callback(self.frame_slider_callback) # Button to toggle spot overlays self.B_overlay_state = True self.B_overlay = QPushButton("Overlay spots", parent=self.win_left) self.B_overlay.clicked.connect(self.B_overlay_callback) L_left.addWidget(self.B_overlay, 1, 0, alignment=widget_align) # Button to toggle trajectory trails self.B_overlay_trails_state = False self.B_overlay_trails = QPushButton("Overlay histories", parent=self.win_left) self.B_overlay_trails.clicked.connect(self.B_overlay_trails_callback) L_left.addWidget(self.B_overlay_trails, 1, 1, alignment=widget_align) self.B_overlay_trails.stackUnder(self.B_overlay) # Menu to select current overlay symbol symbol_options = keys_to_str(symbol_sizes.keys()) self.M_symbol = LabeledQComboBox(symbol_options, "Overlay symbol", init_value="o", parent=self.win_left) self.M_symbol.assign_callback(self.M_symbol_callback) L_left.addWidget(self.M_symbol, 2, 0, alignment=widget_align) # Menu to select how the spots are colored color_by_options = ["None", "Trajectory", "Quantitative attribute", "Boolean attribute"] self.M_color_by = LabeledQComboBox(color_by_options, "Color spots by", init_value="None", parent=self.win_left) self.M_color_by.assign_callback(self.M_color_by_callback) L_left.addWidget(self.M_color_by, 3, 0, alignment=widget_align) # Create a new binary spot condition self.B_create_condition = QPushButton("Create Boolean attribute", parent=self.win_left) self.B_create_condition.clicked.connect(self.B_create_condition_callback) L_left.addWidget(self.B_create_condition, 4, 0, alignment=widget_align) # Select the binary column that determines color when "condition" # is selected as the color-by attribute condition_options = [c for c in self.locs.columns if self.locs[c].dtype == 'bool'] self.M_condition = LabeledQComboBox(condition_options, "Boolean attribute", init_value="None", parent=self.win_left) L_left.addWidget(self.M_condition, 3, 1, alignment=widget_align) self.M_condition.assign_callback(self.M_condition_callback) # Compare spots in a separate window that shows a grid of spots self.B_compare_spots = QPushButton("Compare spots", parent=self) L_left.addWidget(self.B_compare_spots, 2, 1, alignment=widget_align) self.B_compare_spots.clicked.connect(self.B_compare_spot_callback) # Some placeholder widgets, for better formatting for j in range(6, 18): q = QLabel(parent=self.win_left) L_left.addWidget(q, j, 0, alignment=widget_align) ## KEYBOARD SHORTCUTS - tab right/left through frames self.left_shortcut = QShortcut(QKeySequence(QtGui_Qt.Key_Left), self.win) self.right_shortcut = QShortcut(QKeySequence(QtGui_Qt.Key_Right), self.win) self.left_shortcut.activated.connect(self.tab_prev_frame) self.right_shortcut.activated.connect(self.tab_next_frame) ## DISPLAY self.update_image(autoRange=True, autoLevels=True, autoHistogramRange=True) self.overlay_spots() if "trajectory" in self.locs.columns: self.M_color_by.setCurrentText("Trajectory") self.M_color_by_callback() self.win.show() ## CORE FUNCTIONS def update_image(self, autoRange=False, autoLevels=False, autoHistogramRange=False): """ Update self.imageView with the current value of self.image and change the scatter plots if necessary. """ # Update the image view self.imageView.setImage(self.image, autoRange=autoRange, autoLevels=autoLevels, autoHistogramRange=autoHistogramRange) def load_spots(self, frame_index=None): """ Load a new set of localizations. """ if frame_index is None: frame_index = self.frame_slider.value() self.locs_curr = self.locs.loc[self.locs['frame']==frame_index, :] self.locs_pos = np.asarray(self.locs_curr[['y', 'x']]) self.locs_data = self.locs_curr.to_dict(orient='records') def load_image(self, frame_index): """ Load a new image. """ self.image = self.imageReader.get_frame(frame_index) def change_frame(self, frame_index): """ Load a new frame with a new set of spots. """ # Load the new image self.load_image(frame_index) # Load the new set of spots self.load_spots(frame_index) def tab_next_frame(self): """ Load the next frame. """ next_idx = int(self.frame_slider.value()) if next_idx < self.frame_slider.maximum: next_idx += 1 self.frame_slider.setValue(next_idx) def tab_prev_frame(self): """ Load the previous frame. """ prev_idx = int(self.frame_slider.value()) if prev_idx > self.frame_slider.minimum: prev_idx -= 1 self.frame_slider.setValue(prev_idx) def generate_color_spot_dict(self, color_col): """ From the current set of spots, generate an argument suitable to update the scatter plot while coloring each localization by its value of *color_col*. Usually *color_col* is either "traj_color" or "attrib_color". """ symbol = self.M_symbol.currentText() return [{'pos': tuple(self.locs_pos[i,:]+0.5), 'size': symbol_sizes[symbol], 'symbol': symbol, 'pen': {'color': self.locs_data[i][color_col], "width": pen_width}, 'brush': None} for i in range(self.locs_pos.shape[0])] def overlay_spots(self): """ Overlay the current set of spots onto the current image. """ if self.B_overlay_state: # Get the current overlay symbol symbol = self.M_symbol.currentText() # Get the current coloring scheme color_by = self.M_color_by.currentText() # Overlay the spots if color_by == "None": self.scatterPlotItem.setData(pos=self.locs_pos+0.5, data=self.locs_data, symbol=symbol, size=symbol_sizes[symbol], **overlay_params) elif color_by == "Trajectory": self.scatterPlotItem.setData( spots=self.generate_color_spot_dict("traj_color"), data=self.locs_data ) elif color_by == "Quantitative attribute": self.scatterPlotItem.setData( spots=self.generate_color_spot_dict("attrib_color"), data=self.locs_data, ) elif color_by == "Boolean attribute": self.scatterPlotItem.setData( spots=self.generate_color_spot_dict("condition_color"), data=self.locs_data, ) else: self.scatterPlotItem.setData([]) # Also overlay the trajectory histories, if desired if self.B_overlay_trails_state: self.overlay_trails() def generate_color_schemes(self): """ Generate two color schemes for this dataframe. """ self.n_colors = 133 # Color scheme 1: viridis cmap = cm.get_cmap("viridis", self.n_colors) self.colors_0 = np.array( [mpl_colors.rgb2hex(cmap(i)[:3]) for i in range(self.n_colors)] ) # Color scheme 2: inferno cmap = cm.get_cmap("cool", self.n_colors) self.colors_1 = np.array( [mpl_colors.rgb2hex(cmap(i)[:3]) for i in range(self.n_colors)] ) def assign_traj_colors(self): """ Assign the "traj_color" column of the dataframe, which assign each trajectory a different hex color. """ # If the dataframe does not have a "trajectory" column, then # all of the spots get the same color if not "trajectory" in self.locs.columns: print("assign_traj_colors: `trajectory` column not found in dataframe") self.locs["traj_color"] = MASTER_COLOR else: self.locs["traj_color"] = self.colors_0[ (self.locs["trajectory"]*9277) % self.n_colors] # If a spot is not assigned to a trajectory, its color is white unassigned = self.locs["trajectory"] == -1 self.locs.loc[unassigned, "traj_color"] = "#FFFFFF" def assign_attribute_colors(self, attrib): """ Assign the "color" column of the dataframe, which determines how individual spots are colored. """ # Get all of the numerical values for this attribute x = np.asarray(self.locs[attrib].astype("float64")) sanitary = (~np.isinf(x)) & (~np.isnan(x)) # Min and max values for the color map cmin = np.percentile(x, 1) cmax = np.percentile(x, 95) # Divide up the range into bins for each color bins = np.linspace(cmin, cmax, self.n_colors-1) # Assign each localization to one bin on the basis # of its attribute assignments = np.zeros(x.shape[0], dtype='int64') assignments[sanitary] = np.digitize(x[sanitary], bins=bins) # Generate the color indices self.locs["attrib_color"] = self.colors_1[assignments] # Unsanitary inputs are colored white self.locs.loc[~sanitary, "attrib_color"] = "#FFFFFF" def assign_condition_colors(self, condition_col): """ Assign the "condition_color" of the dataframe which is keyed to the truth value of *condition_col*. """ indices = self.locs[condition_col].astype('int64') self.locs["condition_color"] = condition_colors[0] self.locs.loc[self.locs[condition_col].astype('bool'), "condition_color"] = condition_colors[1] def overlay_trails(self): """ For each trajectory running in the current frame, overlay that trajectory on the current image. Relies on an updated self.locs_curr. """ if len(self.locs_curr) == 0: self.graphItem.setData() elif "trajectory" in self.locs.columns and \ self.B_overlay_trails_state: # Get the current frame index frame_index = self.frame_slider.value() # Get all spots before or at the current frame T = self.tracks[self.tracks[:,0]<=frame_index, :] # Find the minimum trajectory index present in this frame t = T[T[:,0]==frame_index, 1].min() # Get all trajectories that coincide with this frame T = T[np.where(T[:,1]==t)[0][0]:,:] # Figure out which points correspond to the same # trajectory indicator = (T[1:,1] - T[:-1,1]) == 0 connect_indices = np.where(indicator)[0] adj = np.asarray([connect_indices, connect_indices+1]).T # Overlay self.graphItem.setData(pos=T[:,2:4], adj=adj, pen={'color': MASTER_COLOR, 'width': pen_width}, symbol=None, brush=None, pxMode=False) else: self.graphItem.setData() ## WIDGET CALLBACKS def spot_clicked(self, plot, points): """ Respond to a spot being selected by the user: change its color and print all of its associated information to the terminal. """ for p in self.lastClicked: p.resetPen() for p in points: print("Spot:") print(format_dict(p.data())) p.setPen('w', width=3) self.lastClicked = points def frame_slider_callback(self): """ Respond to a user change in the frame slider. """ # Get the new frame frame_index = self.frame_slider.value() # Load a new image and set of localizations self.change_frame(frame_index) # Update the image self.update_image() # Update the scatter plot self.overlay_spots() def B_overlay_callback(self): """ Toggle the spot overlay. """ self.B_overlay_state = not self.B_overlay_state self.overlay_spots() def M_color_by_callback(self): """ Change the way that the spots are colored: either None (all the same color), "trajectory", or "attribute". """ color_by = self.M_color_by.currentText() if color_by == "Quantitative attribute": # Prompt the user to select an attribute to color by options = [c for c in self.locs.columns if \ self.locs[c].dtype in ["int64", "float64", "uint8", \ "uint16", "float32", "uint32", "int32"]] ex = SingleComboBoxDialog("Attribute", options, init_value="I0", title="Choose attribute color by", parent=self) ex.exec_() # Dialog accepted if ex.result() == 1: attrib = ex.return_val # Dialog rejected; default to I0 else: print("rejected; defaulting to I0") attrib = "I0" # Generate the color indices self.assign_attribute_colors(attrib) # Update the current set of localizations self.load_spots(self.frame_slider.value()) elif color_by == "Boolean attribute": self.assign_attribute_colors(self.M_condition.currentText()) self.overlay_spots() else: pass # Update the scatter plot self.overlay_spots() def M_symbol_callback(self): """ Change the spot overlay symbol. """ self.overlay_spots() def B_overlay_trails_callback(self): """ Show the history of each trajectory on the raw image. """ self.B_overlay_trails_state = not self.B_overlay_trails_state # Do nothing if these are not trajectories if not "trajectory" in self.locs.columns: return # Else overlay the trajectory histories self.overlay_trails() def B_create_condition_callback(self): """ Launch a sub-GUI to generate a Boolean attribute on from one of the spot columns. """ # Prompt the user to create a condition ex = ConditionDialog(self.locs, parent=self) ex.exec() # Accepted - unpack result if ex.result() == 1: bounds, col = ex.return_val l_bound, u_bound = bounds # Add this as an option condition_name = '%s_condition' % col if condition_name not in self.locs.columns: self.M_condition.QComboBox.addItems([condition_name]) # Create a new binary column on the dataframe self.locs[condition_name] = np.logical_and( self.locs[col]>=l_bound, self.locs[col]<=u_bound, ) print("%d/%d localizations in condition %s" % ( self.locs[condition_name].sum(), len(self.locs), condition_name )) # Update plots, assuming that the user wants to see the # result immediately self.M_condition.QComboBox.setCurrentText(condition_name) self.M_color_by.QComboBox.setCurrentText("Boolean attribute") self.assign_condition_colors(condition_name) self.load_spots() self.overlay_spots() def M_condition_callback(self): """ Select the current Boolean attribute to use for determining color when using the "Boolean attribute" option in M_color_by. """ # Get the new condition name c = self.M_condition.currentText() # Assign each localization a color depending on its value # with this value self.assign_condition_colors(c) # Update the plots self.load_spots() self.overlay_spots() def B_compare_spot_callback(self): """ Launch a subwindow that shows a grid of spots in the current frame and subsequent frames. This callback has two behaviors, depending on the current state of self.M_color_by. If we're color by a Boolean attribute, then launch a ImageSubpositionCompare window. Else launch a ImageSubpositionWindow. The former compares two sets of spots side-by-side, while the second is just a single grid of spots. """ if self.M_color_by.currentText() == "Boolean attribute": # Size of image grid N = 8 # Get the current Boolean attribute column col = self.M_condition.currentText() # Get as many spots as we can to fill this image grid frame_index = self.frame_slider.value() positions_false = [] positions_true = [] images = [] while frame_index<self.imageReader.n_frames and \ (sum([i.shape[0] for i in positions_false])<N**2 or (sum([i.shape[0] for i in positions_true])<N**2)): im = self.imageReader.get_frame(frame_index) frame_locs = self.locs.loc[ self.locs['frame']==frame_index, ['y', 'x', col], ] frame_locs_true = np.asarray(frame_locs.loc[ frame_locs[col], ['y', 'x']]).astype(np.int64) frame_locs_false = np.asarray(frame_locs.loc[ ~frame_locs[col], ['y', 'x']]).astype(np.int64) images.append(im) positions_true.append(frame_locs_true) positions_false.append(frame_locs_false) frame_index += 1 # Launch a ImageSubpositionCompare instance ex = ImageSubpositionCompare(images, positions_true, positions_false, w=15, N=N, colors=condition_colors, parent=self) else: # Size of image grid N = 10 # Get as many spots as we can to fill this image grid frame_index = self.frame_slider.value() positions = [] images = [] while sum([i.shape[0] for i in positions])<N**2 and \ frame_index<self.imageReader.n_frames: im = self.imageReader.get_frame(frame_index) frame_locs = np.asarray(self.locs.loc[ self.locs['frame']==frame_index, ['y', 'x']]).astype(np.int64) images.append(im) positions.append(frame_locs) frame_index += 1 # Launch an ImageSubpositionWindow ex = ImageSubpositionWindow(images, positions, w=15, N=N, parent=self)