Spaces:
Sleeping
Sleeping
| """ | |
| Copyright © 2023 Howard Hughes Medical Institute, Authored by Carsen Stringer and Marius Pachitariu. | |
| """ | |
| import sys, os, pathlib, warnings, datetime, time | |
| from qtpy import QtGui, QtCore | |
| from superqt import QRangeSlider | |
| from qtpy.QtWidgets import QScrollArea, QMainWindow, QApplication, QWidget, QScrollBar, QComboBox, QGridLayout, QPushButton, QFrame, QCheckBox, QLabel, QProgressBar, QLineEdit, QMessageBox, QGroupBox | |
| import pyqtgraph as pg | |
| import numpy as np | |
| from scipy.stats import mode | |
| import cv2 | |
| from . import guiparts, menus, io | |
| from .. import models, core, dynamics, version | |
| from ..utils import download_url_to_file, masks_to_outlines, diameters | |
| from ..io import get_image_files, imsave, imread | |
| from ..transforms import resize_image, normalize99 #fixed import | |
| from ..plot import disk | |
| from ..transforms import normalize99_tile, smooth_sharpen_img | |
| from .gui import MainW | |
| try: | |
| import matplotlib.pyplot as plt | |
| MATPLOTLIB = True | |
| except: | |
| MATPLOTLIB = False | |
| def avg3d(C): | |
| """ smooth value of c across nearby points | |
| (c is center of grid directly below point) | |
| b -- a -- b | |
| a -- c -- a | |
| b -- a -- b | |
| """ | |
| Ly, Lx = C.shape | |
| # pad T by 2 | |
| T = np.zeros((Ly + 2, Lx + 2), "float32") | |
| M = np.zeros((Ly, Lx), "float32") | |
| T[1:-1, 1:-1] = C.copy() | |
| y, x = np.meshgrid(np.arange(0, Ly, 1, int), np.arange(0, Lx, 1, int), | |
| indexing="ij") | |
| y += 1 | |
| x += 1 | |
| a = 1. / 2 #/(z**2 + 1)**0.5 | |
| b = 1. / (1 + 2**0.5) #(z**2 + 2)**0.5 | |
| c = 1. | |
| M = (b * T[y - 1, x - 1] + a * T[y - 1, x] + b * T[y - 1, x + 1] + a * T[y, x - 1] + | |
| c * T[y, x] + a * T[y, x + 1] + b * T[y + 1, x - 1] + a * T[y + 1, x] + | |
| b * T[y + 1, x + 1]) | |
| M /= 4 * a + 4 * b + c | |
| return M | |
| def interpZ(mask, zdraw): | |
| """ find nearby planes and average their values using grid of points | |
| zfill is in ascending order | |
| """ | |
| ifill = np.ones(mask.shape[0], "bool") | |
| zall = np.arange(0, mask.shape[0], 1, int) | |
| ifill[zdraw] = False | |
| zfill = zall[ifill] | |
| zlower = zdraw[np.searchsorted(zdraw, zfill, side="left") - 1] | |
| zupper = zdraw[np.searchsorted(zdraw, zfill, side="right")] | |
| for k, z in enumerate(zfill): | |
| Z = zupper[k] - zlower[k] | |
| zl = (z - zlower[k]) / Z | |
| plower = avg3d(mask[zlower[k]]) * (1 - zl) | |
| pupper = avg3d(mask[zupper[k]]) * zl | |
| mask[z] = (plower + pupper) > 0.33 | |
| #Ml, norml = avg3d(mask[zlower[k]], zl) | |
| #Mu, normu = avg3d(mask[zupper[k]], 1-zl) | |
| #mask[z] = (Ml + Mu) / (norml + normu) > 0.5 | |
| return mask, zfill | |
| def run(image=None): | |
| from ..io import logger_setup | |
| logger, log_file = logger_setup() | |
| # Always start by initializing Qt (only once per application) | |
| warnings.filterwarnings("ignore") | |
| app = QApplication(sys.argv) | |
| icon_path = pathlib.Path.home().joinpath(".cellpose", "logo.png") | |
| guip_path = pathlib.Path.home().joinpath(".cellpose", "cellpose_gui.png") | |
| style_path = pathlib.Path.home().joinpath(".cellpose", "style_choice.npy") | |
| if not icon_path.is_file(): | |
| cp_dir = pathlib.Path.home().joinpath(".cellpose") | |
| cp_dir.mkdir(exist_ok=True) | |
| print("downloading logo") | |
| download_url_to_file( | |
| "https://www.cellpose.org/static/images/cellpose_transparent.png", | |
| icon_path, progress=True) | |
| if not guip_path.is_file(): | |
| print("downloading help window image") | |
| download_url_to_file("https://www.cellpose.org/static/images/cellpose_gui.png", | |
| guip_path, progress=True) | |
| icon_path = str(icon_path.resolve()) | |
| app_icon = QtGui.QIcon() | |
| app_icon.addFile(icon_path, QtCore.QSize(16, 16)) | |
| app_icon.addFile(icon_path, QtCore.QSize(24, 24)) | |
| app_icon.addFile(icon_path, QtCore.QSize(32, 32)) | |
| app_icon.addFile(icon_path, QtCore.QSize(48, 48)) | |
| app_icon.addFile(icon_path, QtCore.QSize(64, 64)) | |
| app_icon.addFile(icon_path, QtCore.QSize(256, 256)) | |
| app.setWindowIcon(app_icon) | |
| app.setStyle("Fusion") | |
| app.setPalette(guiparts.DarkPalette()) | |
| #app.setStyleSheet("QLineEdit { color: yellow }") | |
| # models.download_model_weights() # does not exist | |
| MainW_3d(image=image, logger=logger) | |
| ret = app.exec_() | |
| sys.exit(ret) | |
| class MainW_3d(MainW): | |
| def __init__(self, image=None, logger=None): | |
| # MainW init | |
| MainW.__init__(self, image=image, logger=logger) | |
| # add gradZ view | |
| self.ViewDropDown.insertItem(3, "gradZ") | |
| # turn off single stroke | |
| self.SCheckBox.setChecked(False) | |
| ### add orthoviews and z-bar | |
| # ortho crosshair lines | |
| self.vLine = pg.InfiniteLine(angle=90, movable=False) | |
| self.hLine = pg.InfiniteLine(angle=0, movable=False) | |
| self.vLineOrtho = [ | |
| pg.InfiniteLine(angle=90, movable=False), | |
| pg.InfiniteLine(angle=90, movable=False) | |
| ] | |
| self.hLineOrtho = [ | |
| pg.InfiniteLine(angle=0, movable=False), | |
| pg.InfiniteLine(angle=0, movable=False) | |
| ] | |
| self.make_orthoviews() | |
| # z scrollbar underneath | |
| self.scroll = QScrollBar(QtCore.Qt.Horizontal) | |
| self.scroll.setMaximum(10) | |
| self.scroll.valueChanged.connect(self.move_in_Z) | |
| self.lmain.addWidget(self.scroll, 40, 9, 1, 30) | |
| b = 22 | |
| label = QLabel("stitch threshold:") | |
| label.setToolTip( | |
| "for 3D volumes, turn on stitch_threshold to stitch masks across planes instead of running cellpose in 3D (see docs for details)" | |
| ) | |
| label.setFont(self.medfont) | |
| self.segBoxG.addWidget(label, b, 0, 1, 4) | |
| self.stitch_threshold = QLineEdit() | |
| self.stitch_threshold.setText("0.0") | |
| self.stitch_threshold.setFixedWidth(30) | |
| self.stitch_threshold.setFont(self.medfont) | |
| self.stitch_threshold.setToolTip( | |
| "for 3D volumes, turn on stitch_threshold to stitch masks across planes instead of running cellpose in 3D (see docs for details)" | |
| ) | |
| self.segBoxG.addWidget(self.stitch_threshold, b, 4, 1, 1) | |
| label = QLabel("flow3D_smooth:") | |
| label.setToolTip( | |
| "for 3D volumes, smooth flows by a Gaussian with standard deviation flow3D_smooth (see docs for details)" | |
| ) | |
| label.setFont(self.medfont) | |
| self.segBoxG.addWidget(label, b, 5, 1, 3) | |
| self.flow3D_smooth = QLineEdit() | |
| self.flow3D_smooth.setText("0.0") | |
| self.flow3D_smooth.setFixedWidth(30) | |
| self.flow3D_smooth.setFont(self.medfont) | |
| self.flow3D_smooth.setToolTip( | |
| "for 3D volumes, smooth flows by a Gaussian with standard deviation flow3D_smooth (see docs for details)" | |
| ) | |
| self.segBoxG.addWidget(self.flow3D_smooth, b, 8, 1, 1) | |
| b+=1 | |
| label = QLabel("anisotropy:") | |
| label.setToolTip( | |
| "for 3D volumes, increase in sampling in Z vs XY as a ratio, e.g. set set to 2.0 if Z is sampled half as dense as X or Y (see docs for details)" | |
| ) | |
| label.setFont(self.medfont) | |
| self.segBoxG.addWidget(label, b, 0, 1, 4) | |
| self.anisotropy = QLineEdit() | |
| self.anisotropy.setText("1.0") | |
| self.anisotropy.setFixedWidth(30) | |
| self.anisotropy.setFont(self.medfont) | |
| self.anisotropy.setToolTip( | |
| "for 3D volumes, increase in sampling in Z vs XY as a ratio, e.g. set set to 2.0 if Z is sampled half as dense as X or Y (see docs for details)" | |
| ) | |
| self.segBoxG.addWidget(self.anisotropy, b, 4, 1, 1) | |
| self.resample = QCheckBox("resample") | |
| self.resample.setToolTip("reample before creating masks; if diameter > 30 resample will use more CPU+GPU memory (see docs for more details)") | |
| self.resample.setFont(self.medfont) | |
| self.resample.setChecked(True) | |
| self.segBoxG.addWidget(self.resample, b, 5, 1, 4) | |
| b+=1 | |
| label = QLabel("min_size:") | |
| label.setToolTip( | |
| "all masks less than this size in pixels (volume) will be removed" | |
| ) | |
| label.setFont(self.medfont) | |
| self.segBoxG.addWidget(label, b, 0, 1, 4) | |
| self.min_size = QLineEdit() | |
| self.min_size.setText("15") | |
| self.min_size.setFixedWidth(50) | |
| self.min_size.setFont(self.medfont) | |
| self.min_size.setToolTip( | |
| "all masks less than this size in pixels (volume) will be removed" | |
| ) | |
| self.segBoxG.addWidget(self.min_size, b, 4, 1, 3) | |
| b += 1 | |
| self.orthobtn = QCheckBox("ortho") | |
| self.orthobtn.setToolTip("activate orthoviews with 3D image") | |
| self.orthobtn.setFont(self.medfont) | |
| self.orthobtn.setChecked(False) | |
| self.l0.addWidget(self.orthobtn, b, 0, 1, 2) | |
| self.orthobtn.toggled.connect(self.toggle_ortho) | |
| label = QLabel("dz:") | |
| label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) | |
| label.setFont(self.medfont) | |
| self.l0.addWidget(label, b, 2, 1, 1) | |
| self.dz = 10 | |
| self.dzedit = QLineEdit() | |
| self.dzedit.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) | |
| self.dzedit.setText(str(self.dz)) | |
| self.dzedit.returnPressed.connect(self.update_ortho) | |
| self.dzedit.setFixedWidth(40) | |
| self.dzedit.setFont(self.medfont) | |
| self.l0.addWidget(self.dzedit, b, 3, 1, 2) | |
| label = QLabel("z-aspect:") | |
| label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) | |
| label.setFont(self.medfont) | |
| self.l0.addWidget(label, b, 5, 1, 2) | |
| self.zaspect = 1.0 | |
| self.zaspectedit = QLineEdit() | |
| self.zaspectedit.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) | |
| self.zaspectedit.setText(str(self.zaspect)) | |
| self.zaspectedit.returnPressed.connect(self.update_ortho) | |
| self.zaspectedit.setFixedWidth(40) | |
| self.zaspectedit.setFont(self.medfont) | |
| self.l0.addWidget(self.zaspectedit, b, 7, 1, 2) | |
| b += 1 | |
| # add z position underneath | |
| self.currentZ = 0 | |
| label = QLabel("Z:") | |
| label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) | |
| self.l0.addWidget(label, b, 5, 1, 2) | |
| self.zpos = QLineEdit() | |
| self.zpos.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) | |
| self.zpos.setText(str(self.currentZ)) | |
| self.zpos.returnPressed.connect(self.update_ztext) | |
| self.zpos.setFixedWidth(40) | |
| self.zpos.setFont(self.medfont) | |
| self.l0.addWidget(self.zpos, b, 7, 1, 2) | |
| # if called with image, load it | |
| if image is not None: | |
| self.filename = image | |
| io._load_image(self, self.filename, load_3D=True) | |
| self.load_3D = True | |
| def add_mask(self, points=None, color=(100, 200, 50), dense=True): | |
| # points is list of strokes | |
| points_all = np.concatenate(points, axis=0) | |
| # loop over z values | |
| median = [] | |
| zdraw = np.unique(points_all[:, 0]) | |
| zrange = np.arange(zdraw.min(), zdraw.max() + 1, 1, int) | |
| zmin = zdraw.min() | |
| pix = np.zeros((2, 0), "uint16") | |
| mall = np.zeros((len(zrange), self.Ly, self.Lx), "bool") | |
| k = 0 | |
| for z in zdraw: | |
| ars, acs, vrs, vcs = np.zeros(0, "int"), np.zeros(0, "int"), np.zeros( | |
| 0, "int"), np.zeros(0, "int") | |
| for stroke in points: | |
| stroke = np.concatenate(stroke, axis=0).reshape(-1, 4) | |
| iz = stroke[:, 0] == z | |
| vr = stroke[iz, 1] | |
| vc = stroke[iz, 2] | |
| if iz.sum() > 0: | |
| # get points inside drawn points | |
| mask = np.zeros((np.ptp(vr) + 4, np.ptp(vc) + 4), "uint8") | |
| pts = np.stack((vc - vc.min() + 2, vr - vr.min() + 2), | |
| axis=-1)[:, np.newaxis, :] | |
| mask = cv2.fillPoly(mask, [pts], (255, 0, 0)) | |
| ar, ac = np.nonzero(mask) | |
| ar, ac = ar + vr.min() - 2, ac + vc.min() - 2 | |
| # get dense outline | |
| contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, | |
| cv2.CHAIN_APPROX_NONE) | |
| pvc, pvr = contours[-2][0].squeeze().T | |
| vr, vc = pvr + vr.min() - 2, pvc + vc.min() - 2 | |
| # concatenate all points | |
| ar, ac = np.hstack((np.vstack((vr, vc)), np.vstack((ar, ac)))) | |
| # if these pixels are overlapping with another cell, reassign them | |
| ioverlap = self.cellpix[z][ar, ac] > 0 | |
| if (~ioverlap).sum() < 8: | |
| print("ERROR: cell too small without overlaps, not drawn") | |
| return None | |
| elif ioverlap.sum() > 0: | |
| ar, ac = ar[~ioverlap], ac[~ioverlap] | |
| # compute outline of new mask | |
| mask = np.zeros((np.ptp(ar) + 4, np.ptp(ac) + 4), "uint8") | |
| mask[ar - ar.min() + 2, ac - ac.min() + 2] = 1 | |
| contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, | |
| cv2.CHAIN_APPROX_NONE) | |
| pvc, pvr = contours[-2][0].squeeze().T | |
| vr, vc = pvr + ar.min() - 2, pvc + ac.min() - 2 | |
| ars = np.concatenate((ars, ar), axis=0) | |
| acs = np.concatenate((acs, ac), axis=0) | |
| vrs = np.concatenate((vrs, vr), axis=0) | |
| vcs = np.concatenate((vcs, vc), axis=0) | |
| self.draw_mask(z, ars, acs, vrs, vcs, color) | |
| median.append(np.array([np.median(ars), np.median(acs)])) | |
| mall[z - zmin, ars, acs] = True | |
| pix = np.append(pix, np.vstack((ars, acs)), axis=-1) | |
| mall = mall[:, pix[0].min():pix[0].max() + 1, | |
| pix[1].min():pix[1].max() + 1].astype("float32") | |
| ymin, xmin = pix[0].min(), pix[1].min() | |
| if len(zdraw) > 1: | |
| mall, zfill = interpZ(mall, zdraw - zmin) | |
| for z in zfill: | |
| mask = mall[z].copy() | |
| ar, ac = np.nonzero(mask) | |
| ioverlap = self.cellpix[z + zmin][ar + ymin, ac + xmin] > 0 | |
| if (~ioverlap).sum() < 5: | |
| print("WARNING: stroke on plane %d not included due to overlaps" % | |
| z) | |
| elif ioverlap.sum() > 0: | |
| mask[ar[ioverlap], ac[ioverlap]] = 0 | |
| ar, ac = ar[~ioverlap], ac[~ioverlap] | |
| # compute outline of mask | |
| outlines = masks_to_outlines(mask) | |
| vr, vc = np.nonzero(outlines) | |
| vr, vc = vr + ymin, vc + xmin | |
| ar, ac = ar + ymin, ac + xmin | |
| self.draw_mask(z + zmin, ar, ac, vr, vc, color) | |
| self.zdraw.append(zdraw) | |
| return median | |
| def move_in_Z(self): | |
| if self.loaded: | |
| self.currentZ = min(self.NZ, max(0, int(self.scroll.value()))) | |
| self.zpos.setText(str(self.currentZ)) | |
| self.update_plot() | |
| self.draw_layer() | |
| self.update_layer() | |
| def make_orthoviews(self): | |
| self.pOrtho, self.imgOrtho, self.layerOrtho = [], [], [] | |
| for j in range(2): | |
| self.pOrtho.append( | |
| pg.ViewBox(lockAspect=True, name=f"plotOrtho{j}", | |
| border=[100, 100, 100], invertY=True, enableMouse=False)) | |
| self.pOrtho[j].setMenuEnabled(False) | |
| self.imgOrtho.append(pg.ImageItem(viewbox=self.pOrtho[j], parent=self)) | |
| self.imgOrtho[j].autoDownsample = False | |
| self.layerOrtho.append(pg.ImageItem(viewbox=self.pOrtho[j], parent=self)) | |
| self.layerOrtho[j].setLevels([0., 255.]) | |
| #self.pOrtho[j].scene().contextMenuItem = self.pOrtho[j] | |
| self.pOrtho[j].addItem(self.imgOrtho[j]) | |
| self.pOrtho[j].addItem(self.layerOrtho[j]) | |
| self.pOrtho[j].addItem(self.vLineOrtho[j], ignoreBounds=False) | |
| self.pOrtho[j].addItem(self.hLineOrtho[j], ignoreBounds=False) | |
| self.pOrtho[0].linkView(self.pOrtho[0].YAxis, self.p0) | |
| self.pOrtho[1].linkView(self.pOrtho[1].XAxis, self.p0) | |
| def add_orthoviews(self): | |
| self.yortho = self.Ly // 2 | |
| self.xortho = self.Lx // 2 | |
| if self.NZ > 1: | |
| self.update_ortho() | |
| self.win.addItem(self.pOrtho[0], 0, 1, rowspan=1, colspan=1) | |
| self.win.addItem(self.pOrtho[1], 1, 0, rowspan=1, colspan=1) | |
| qGraphicsGridLayout = self.win.ci.layout | |
| qGraphicsGridLayout.setColumnStretchFactor(0, 2) | |
| qGraphicsGridLayout.setColumnStretchFactor(1, 1) | |
| qGraphicsGridLayout.setRowStretchFactor(0, 2) | |
| qGraphicsGridLayout.setRowStretchFactor(1, 1) | |
| #self.p0.linkView(self.p0.YAxis, self.pOrtho[0]) | |
| #self.p0.linkView(self.p0.XAxis, self.pOrtho[1]) | |
| self.pOrtho[0].setYRange(0, self.Lx) | |
| self.pOrtho[0].setXRange(-self.dz / 3, self.dz * 2 + self.dz / 3) | |
| self.pOrtho[1].setYRange(-self.dz / 3, self.dz * 2 + self.dz / 3) | |
| self.pOrtho[1].setXRange(0, self.Ly) | |
| #self.pOrtho[0].setLimits(minXRange=self.dz*2+self.dz/3*2) | |
| #self.pOrtho[1].setLimits(minYRange=self.dz*2+self.dz/3*2) | |
| self.p0.addItem(self.vLine, ignoreBounds=False) | |
| self.p0.addItem(self.hLine, ignoreBounds=False) | |
| self.p0.setYRange(0, self.Lx) | |
| self.p0.setXRange(0, self.Ly) | |
| self.win.show() | |
| self.show() | |
| #self.p0.linkView(self.p0.XAxis, self.pOrtho[1]) | |
| def remove_orthoviews(self): | |
| self.win.removeItem(self.pOrtho[0]) | |
| self.win.removeItem(self.pOrtho[1]) | |
| self.p0.removeItem(self.vLine) | |
| self.p0.removeItem(self.hLine) | |
| self.win.show() | |
| self.show() | |
| def update_crosshairs(self): | |
| self.yortho = min(self.Ly - 1, max(0, int(self.yortho))) | |
| self.xortho = min(self.Lx - 1, max(0, int(self.xortho))) | |
| self.vLine.setPos(self.xortho) | |
| self.hLine.setPos(self.yortho) | |
| self.vLineOrtho[1].setPos(self.xortho) | |
| self.hLineOrtho[1].setPos(self.zc) | |
| self.vLineOrtho[0].setPos(self.zc) | |
| self.hLineOrtho[0].setPos(self.yortho) | |
| def update_ortho(self): | |
| if self.NZ > 1 and self.orthobtn.isChecked(): | |
| dzcurrent = self.dz | |
| self.dz = min(100, max(3, int(self.dzedit.text()))) | |
| self.zaspect = max(0.01, min(100., float(self.zaspectedit.text()))) | |
| self.dzedit.setText(str(self.dz)) | |
| self.zaspectedit.setText(str(self.zaspect)) | |
| if self.dz != dzcurrent: | |
| self.pOrtho[0].setXRange(-self.dz / 3, self.dz * 2 + self.dz / 3) | |
| self.pOrtho[1].setYRange(-self.dz / 3, self.dz * 2 + self.dz / 3) | |
| dztot = min(self.NZ, self.dz * 2) | |
| y = self.yortho | |
| x = self.xortho | |
| z = self.currentZ | |
| if dztot == self.NZ: | |
| zmin, zmax = 0, self.NZ | |
| else: | |
| if z - self.dz < 0: | |
| zmin = 0 | |
| zmax = zmin + self.dz * 2 | |
| elif z + self.dz >= self.NZ: | |
| zmax = self.NZ | |
| zmin = zmax - self.dz * 2 | |
| else: | |
| zmin, zmax = z - self.dz, z + self.dz | |
| self.zc = z - zmin | |
| self.update_crosshairs() | |
| if self.view == 0 or self.view == 4: | |
| for j in range(2): | |
| if j == 0: | |
| if self.view == 0: | |
| image = self.stack[zmin:zmax, :, x].transpose(1, 0, 2).copy() | |
| else: | |
| image = self.stack_filtered[zmin:zmax, :, | |
| x].transpose(1, 0, 2).copy() | |
| else: | |
| image = self.stack[ | |
| zmin:zmax, | |
| y, :].copy() if self.view == 0 else self.stack_filtered[zmin:zmax, | |
| y, :].copy() | |
| if self.nchan == 1: | |
| # show single channel | |
| image = image[..., 0] | |
| if self.color == 0: | |
| self.imgOrtho[j].setImage(image, autoLevels=False, lut=None) | |
| if self.nchan > 1: | |
| levels = np.array([ | |
| self.saturation[0][self.currentZ], | |
| self.saturation[1][self.currentZ], | |
| self.saturation[2][self.currentZ] | |
| ]) | |
| self.imgOrtho[j].setLevels(levels) | |
| else: | |
| self.imgOrtho[j].setLevels( | |
| self.saturation[0][self.currentZ]) | |
| elif self.color > 0 and self.color < 4: | |
| if self.nchan > 1: | |
| image = image[..., self.color - 1] | |
| self.imgOrtho[j].setImage(image, autoLevels=False, | |
| lut=self.cmap[self.color]) | |
| if self.nchan > 1: | |
| self.imgOrtho[j].setLevels( | |
| self.saturation[self.color - 1][self.currentZ]) | |
| else: | |
| self.imgOrtho[j].setLevels( | |
| self.saturation[0][self.currentZ]) | |
| elif self.color == 4: | |
| if image.ndim > 2: | |
| image = image.astype("float32").mean(axis=2).astype("uint8") | |
| self.imgOrtho[j].setImage(image, autoLevels=False, lut=None) | |
| self.imgOrtho[j].setLevels(self.saturation[0][self.currentZ]) | |
| elif self.color == 5: | |
| if image.ndim > 2: | |
| image = image.astype("float32").mean(axis=2).astype("uint8") | |
| self.imgOrtho[j].setImage(image, autoLevels=False, | |
| lut=self.cmap[0]) | |
| self.imgOrtho[j].setLevels(self.saturation[0][self.currentZ]) | |
| self.pOrtho[0].setAspectLocked(lock=True, ratio=self.zaspect) | |
| self.pOrtho[1].setAspectLocked(lock=True, ratio=1. / self.zaspect) | |
| else: | |
| image = np.zeros((10, 10), "uint8") | |
| self.imgOrtho[0].setImage(image, autoLevels=False, lut=None) | |
| self.imgOrtho[0].setLevels([0.0, 255.0]) | |
| self.imgOrtho[1].setImage(image, autoLevels=False, lut=None) | |
| self.imgOrtho[1].setLevels([0.0, 255.0]) | |
| zrange = zmax - zmin | |
| self.layer_ortho = [ | |
| np.zeros((self.Ly, zrange, 4), "uint8"), | |
| np.zeros((zrange, self.Lx, 4), "uint8") | |
| ] | |
| if self.masksOn: | |
| for j in range(2): | |
| if j == 0: | |
| cp = self.cellpix[zmin:zmax, :, x].T | |
| else: | |
| cp = self.cellpix[zmin:zmax, y] | |
| self.layer_ortho[j][..., :3] = self.cellcolors[cp, :] | |
| self.layer_ortho[j][..., 3] = self.opacity * (cp > 0).astype("uint8") | |
| if self.selected > 0: | |
| self.layer_ortho[j][cp == self.selected] = np.array( | |
| [255, 255, 255, self.opacity]) | |
| if self.outlinesOn: | |
| for j in range(2): | |
| if j == 0: | |
| op = self.outpix[zmin:zmax, :, x].T | |
| else: | |
| op = self.outpix[zmin:zmax, y] | |
| self.layer_ortho[j][op > 0] = np.array(self.outcolor).astype("uint8") | |
| for j in range(2): | |
| self.layerOrtho[j].setImage(self.layer_ortho[j]) | |
| self.win.show() | |
| self.show() | |
| def toggle_ortho(self): | |
| if self.orthobtn.isChecked(): | |
| self.add_orthoviews() | |
| else: | |
| self.remove_orthoviews() | |
| def plot_clicked(self, event): | |
| if event.button()==QtCore.Qt.LeftButton \ | |
| and not event.modifiers() & (QtCore.Qt.ShiftModifier | QtCore.Qt.AltModifier)\ | |
| and not self.removing_region: | |
| if event.double(): | |
| try: | |
| self.p0.setYRange(0, self.Ly + self.pr) | |
| except: | |
| self.p0.setYRange(0, self.Ly) | |
| self.p0.setXRange(0, self.Lx) | |
| elif self.loaded and not self.in_stroke: | |
| if self.orthobtn.isChecked(): | |
| items = self.win.scene().items(event.scenePos()) | |
| for x in items: | |
| if x == self.p0: | |
| pos = self.p0.mapSceneToView(event.scenePos()) | |
| x = int(pos.x()) | |
| y = int(pos.y()) | |
| if y >= 0 and y < self.Ly and x >= 0 and x < self.Lx: | |
| self.yortho = y | |
| self.xortho = x | |
| self.update_ortho() | |
| def update_plot(self): | |
| super().update_plot() | |
| if self.NZ > 1 and self.orthobtn.isChecked(): | |
| self.update_ortho() | |
| self.win.show() | |
| self.show() | |
| def keyPressEvent(self, event): | |
| if self.loaded: | |
| if not (event.modifiers() & | |
| (QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier | | |
| QtCore.Qt.AltModifier) or self.in_stroke): | |
| updated = False | |
| if len(self.current_point_set) > 0: | |
| if event.key() == QtCore.Qt.Key_Return: | |
| self.add_set() | |
| if self.NZ > 1: | |
| if event.key() == QtCore.Qt.Key_Left: | |
| self.currentZ = max(0, self.currentZ - 1) | |
| self.scroll.setValue(self.currentZ) | |
| updated = True | |
| elif event.key() == QtCore.Qt.Key_Right: | |
| self.currentZ = min(self.NZ - 1, self.currentZ + 1) | |
| self.scroll.setValue(self.currentZ) | |
| updated = True | |
| else: | |
| nviews = self.ViewDropDown.count() - 1 | |
| nviews += int( | |
| self.ViewDropDown.model().item(self.ViewDropDown.count() - | |
| 1).isEnabled()) | |
| if event.key() == QtCore.Qt.Key_X: | |
| self.MCheckBox.toggle() | |
| if event.key() == QtCore.Qt.Key_Z: | |
| self.OCheckBox.toggle() | |
| if event.key() == QtCore.Qt.Key_Left or event.key( | |
| ) == QtCore.Qt.Key_A: | |
| self.currentZ = max(0, self.currentZ - 1) | |
| self.scroll.setValue(self.currentZ) | |
| updated = True | |
| elif event.key() == QtCore.Qt.Key_Right or event.key( | |
| ) == QtCore.Qt.Key_D: | |
| self.currentZ = min(self.NZ - 1, self.currentZ + 1) | |
| self.scroll.setValue(self.currentZ) | |
| updated = True | |
| elif event.key() == QtCore.Qt.Key_PageDown: | |
| self.view = (self.view + 1) % (nviews) | |
| self.ViewDropDown.setCurrentIndex(self.view) | |
| elif event.key() == QtCore.Qt.Key_PageUp: | |
| self.view = (self.view - 1) % (nviews) | |
| self.ViewDropDown.setCurrentIndex(self.view) | |
| # can change background or stroke size if cell not finished | |
| if event.key() == QtCore.Qt.Key_Up or event.key() == QtCore.Qt.Key_W: | |
| self.color = (self.color - 1) % (6) | |
| self.RGBDropDown.setCurrentIndex(self.color) | |
| elif event.key() == QtCore.Qt.Key_Down or event.key( | |
| ) == QtCore.Qt.Key_S: | |
| self.color = (self.color + 1) % (6) | |
| self.RGBDropDown.setCurrentIndex(self.color) | |
| elif event.key() == QtCore.Qt.Key_R: | |
| if self.color != 1: | |
| self.color = 1 | |
| else: | |
| self.color = 0 | |
| self.RGBDropDown.setCurrentIndex(self.color) | |
| elif event.key() == QtCore.Qt.Key_G: | |
| if self.color != 2: | |
| self.color = 2 | |
| else: | |
| self.color = 0 | |
| self.RGBDropDown.setCurrentIndex(self.color) | |
| elif event.key() == QtCore.Qt.Key_B: | |
| if self.color != 3: | |
| self.color = 3 | |
| else: | |
| self.color = 0 | |
| self.RGBDropDown.setCurrentIndex(self.color) | |
| elif (event.key() == QtCore.Qt.Key_Comma or | |
| event.key() == QtCore.Qt.Key_Period): | |
| count = self.BrushChoose.count() | |
| gci = self.BrushChoose.currentIndex() | |
| if event.key() == QtCore.Qt.Key_Comma: | |
| gci = max(0, gci - 1) | |
| else: | |
| gci = min(count - 1, gci + 1) | |
| self.BrushChoose.setCurrentIndex(gci) | |
| self.brush_choose() | |
| if not updated: | |
| self.update_plot() | |
| if event.key() == QtCore.Qt.Key_Minus or event.key() == QtCore.Qt.Key_Equal: | |
| self.p0.keyPressEvent(event) | |
| def update_ztext(self): | |
| zpos = self.currentZ | |
| try: | |
| zpos = int(self.zpos.text()) | |
| except: | |
| print("ERROR: zposition is not a number") | |
| self.currentZ = max(0, min(self.NZ - 1, zpos)) | |
| self.zpos.setText(str(self.currentZ)) | |
| self.scroll.setValue(self.currentZ) | |