From 4bc6b8b2f5908e1e1de6ab39fc11b0c6ce900fac Mon Sep 17 00:00:00 2001 From: Michael Rariden Date: Wed, 22 Apr 2026 13:35:51 -0400 Subject: [PATCH 1/3] minimal example of right click for colormaps --- cellpose/gui/gui.py | 48 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/cellpose/gui/gui.py b/cellpose/gui/gui.py index 471f8bda..fe8195f2 100644 --- a/cellpose/gui/gui.py +++ b/cellpose/gui/gui.py @@ -377,7 +377,8 @@ def make_buttons(self): for r in range(3): widget_row += 1 if r == 0: - label = QLabel('gray/
red') + label = ClickableColorLabel('gray/
red') + label.reqColorSignal.connect(self.set_cmap1) else: label = QLabel(names[r] + ":") label.setStyleSheet(f"color: {colornames[r]}") @@ -649,6 +650,10 @@ def make_buttons(self): return b + + def set_cmap1(self, new_cmap): + self.cmap[1] = new_cmap + self.update_plot() def level_change(self, r): r = ["red", "green", "blue"].index(r) @@ -2009,3 +2014,44 @@ def compute_segmentation(self, custom=False, model_name=None, load_model=True): self.recompute_masks = False except Exception as e: print("ERROR: %s" % e) + + +import matplotlib as mpl + +class ClickableColorLabel(QLabel): + + reqColorSignal = QtCore.Signal(object) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def mousePressEvent(self, ev): + if ev.button() == QtCore.Qt.RightButton: + menu = QMenu(self) + menu.addAction('Grays') + menu.addAction('Purples') + menu.addAction('viridis') + menu.addAction('magenta') + menu.addAction('cyan') + action = menu.exec_(ev.globalPos()) + if action: + self.reqColorSignal.emit(self.mpl_colormap(action.text())) + self.setText(action.text()+":") + + + def mpl_colormap(self, name: str): + if name in list(mpl.colormaps.keys()): + cmap = mpl.colormaps[name](np.linspace(0, 1, 512))[:, :3] + cmap *= 255 + cmap = cmap.astype(np.uint8) + elif name == 'magenta': + cmap = np.linspace(0, 255, 512, dtype=np.uint8) + cmap = np.stack([cmap, np.zeros(512, dtype=np.uint8), cmap], axis=1) + elif name == 'cyan': + cmap = np.linspace(0, 255, 512, dtype=np.uint8) + cmap = np.stack([np.zeros(512, dtype=np.uint8), cmap, cmap], axis=1) + else: + print("unrecognized cmap") + return + print(f'setting cmap to {name}') + return cmap \ No newline at end of file From 2ae8ce76ad3819dcb67518411babd6f3424d3986 Mon Sep 17 00:00:00 2001 From: Michael Rariden Date: Wed, 22 Apr 2026 18:04:31 -0400 Subject: [PATCH 2/3] remove rgbdropdown, rewriting update_plot, connecting labels. Starting to work --- cellpose/gui/gui.py | 321 ++++++++++++++++++++++++------------------ cellpose/gui/gui3d.py | 10 +- 2 files changed, 188 insertions(+), 143 deletions(-) diff --git a/cellpose/gui/gui.py b/cellpose/gui/gui.py index da22e4d9..9a92de0f 100644 --- a/cellpose/gui/gui.py +++ b/cellpose/gui/gui.py @@ -37,17 +37,20 @@ class Slider(QRangeSlider): def __init__(self, parent, name, color): super().__init__(Horizontal) self.setEnabled(False) - self.valueChanged.connect(lambda: self.levelChanged(parent)) self.name = name self.setStyleSheet(""" QSlider{ background-color: transparent; } """) + self.setMinimum(-.1) + self.setMaximum(255.1) + self.setValue([0, 255]) + self.setToolTip( + "NOTE: manually changing the saturation bars does not affect normalization in segmentation" + ) self.show() - def levelChanged(self, parent): - parent.level_change(self.name) class QHLine(QFrame): @@ -328,24 +331,24 @@ def make_buttons(self): self.l0.addWidget(self.satBox, b, 0, 1, 9) widget_row = 0 - self.RGBDropDown = QComboBox() + # self.RGBDropDown = QComboBox() # This is duplication and in the future these things should be tied # together in a class: - self.RGBDropDown.addItems( - ["RGB", "red=R", "green=G", "blue=B", "gray", "spectral"]) - self.RGBDropDown.name_map = { - "rgb" : "rgb", - "red" : "red=r", - "green" : "green=g", - "blue" : "blue=b", - "gray" : "gray", - "spectral": "spectral", - } - self.RGBDropDown.setFont(self.medfont) - self.RGBDropDown.currentIndexChanged.connect(self.color_choose) - self.satBoxG.addWidget(self.RGBDropDown, widget_row, 0, 1, 3) - self.color = 'RGB' # 0=RGB, 1=gray, 2=R, 3=G, 4=B + # self.RGBDropDown.addItems( + # ["Ch1", "Ch2", "Ch3"]) + # self.RGBDropDown.name_map = { + # "rgb" : "rgb", + # "red" : "red=r", + # "green" : "green=g", + # "blue" : "blue=b", + # "gray" : "gray", + # "spectral": "spectral", + # } + # self.RGBDropDown.setFont(self.medfont) + # self.RGBDropDown.currentIndexChanged.connect(self.color_choose) + # self.satBoxG.addWidget(self.RGBDropDown, widget_row, 0, 1, 3) + # self.color = 'RGB' # 0=RGB, 1=gray, 2=R, 3=G, 4=B label = QLabel("

[↑ / ↓ or W/S]

") label.setFont(self.smallfont) @@ -384,24 +387,19 @@ def make_buttons(self): self.sliders = [] colors = [[255, 0, 0], [0, 255, 0], [0, 0, 255], [100, 100, 100]] colornames = ["red", "Chartreuse", "DodgerBlue"] - names = ["red", "green", "blue"] + self.labels = {} for r in range(3): widget_row += 1 - if r == 0: - label = ClickableColorLabel('gray/
red') - label.reqColorSignal.connect(self.set_cmap1) - else: - label = QLabel(names[r] + ":") - label.setStyleSheet(f"color: {colornames[r]}") + name = "Ch" + str(r+1) + label = ClickableColorLabel("Ch" + str(r+1) + ":", color_name=colornames[r]) + label.sigColorMapChoose.connect(lambda cmap, idx=r: self.set_cmap(idx, cmap)) + label.sigEnableToggled.connect(self.update_plot) label.setFont(self.boldmedfont) + self.labels[name.lower()] = label self.satBoxG.addWidget(label, widget_row, 0, 1, 2) - self.sliders.append(Slider(self, names[r], colors[r])) - self.sliders[-1].setMinimum(-.1) - self.sliders[-1].setMaximum(255.1) - self.sliders[-1].setValue([0, 255]) - self.sliders[-1].setToolTip( - "NOTE: manually changing the saturation bars does not affect normalization in segmentation" - ) + slider = Slider(self, name, colors[r]) + slider.valueChanged.connect(lambda lvl, idx=r: self.level_change(idx, lvl)) + self.sliders.append(slider) self.satBoxG.addWidget(self.sliders[-1], widget_row, 2, 1, 7) b += 1 @@ -662,14 +660,12 @@ def make_buttons(self): return b - def set_cmap1(self, new_cmap): - self.cmap[1] = new_cmap + def set_cmap(self, idx: int, new_cmap): + self.cmap[idx + 1] = new_cmap self.update_plot() - def level_change(self, r): - r = ["red", "green", "blue"].index(r) + def level_change(self, r, sval): if self.loaded: - sval = self.sliders[r].value() self.saturation[r][self.currentZ] = sval if not self.autobtn.isChecked(): for r in range(3): @@ -718,33 +714,35 @@ def keyPressEvent(self, event): # 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.go_next_previous_dropdown(self.RGBDropDown, -1) + # self.go_next_previous_dropdown(self.RGBDropDown, -1) + print('ERROR not cycling colors') event.accept() return elif event.key() == QtCore.Qt.Key_Down or event.key( ) == QtCore.Qt.Key_S: - self.go_next_previous_dropdown(self.RGBDropDown, 1) + # self.go_next_previous_dropdown(self.RGBDropDown, 1) + print('ERROR not cycling colors') event.accept() return elif event.key() == QtCore.Qt.Key_R: - if self.color != 'red': - self.color = 'red' + if self.active_channels != 'red': + self.active_channels = 'red' else: - self.color = 'rgb' + self.active_channels = 'rgb' event.accept() return elif event.key() == QtCore.Qt.Key_G: - if self.color != 'green': - self.color = 'green' + if self.active_channels != 'green': + self.active_channels = 'green' else: - self.color = 'rgb' + self.active_channels = 'rgb' event.accept() return elif event.key() == QtCore.Qt.Key_B: - if self.color != 'blue': - self.color = 'blue' + if self.active_channels != 'blue': + self.active_channels = 'blue' else: - self.color = 'rgb' + self.active_channels = 'rgb' event.accept() return elif (event.key() == QtCore.Qt.Key_Comma or @@ -1004,8 +1002,8 @@ def reset(self): self.ismanual = np.zeros(0, "bool") # -- set menus to default -- # - with QtCore.QSignalBlocker(self.RGBDropDown): - self.color = 'RGB' + # with QtCore.QSignalBlocker(self.RGBDropDown): + # self.color = 'RGB' with QtCore.QSignalBlocker(self.ViewDropDown): self.view = 'image' self.delete_restore() @@ -1022,52 +1020,63 @@ def reset(self): self.remove_roi_obj = None @property - def color(self): - """Current color display mode as a lowercase string. - - Reflects the current selection of the RGBDropDown widget. Possible - values are ``'rgb'``, ``'red'``, ``'green'``, ``'blue'``, ``'gray'``, - and ``'spectral'``. + def active_channels(self): + """Return the list of active channels - Returns - ------- - str - The current color mode, always lowercase. """ - # invert mapping - inv_name_map = {v: k for k, v in self.RGBDropDown.name_map.items()} - return inv_name_map[self.RGBDropDown.currentText().lower()].lower() - - @color.setter - def color(self, value: str|int): - """Set the color display mode by name or dropdown index. + print("Error, didn't get color") + l = [k for k, v in self.labels.items() if v.enabled] + print(l) + return l - Updates the RGBDropDown widget, which triggers any connected signals - (e.g. ``update_plot``). + @property + def inactive_channels_idxs(self): + """Return the idxs of inactive channels - Parameters - ---------- - value : str or int - If ``str``, a case-insensitive color name (``'rgb'``, ``'red'``, - ``'green'``, ``'blue'``, ``'gray'``, ``'spectral'``). The name is - looked up via ``RGBDropDown.name_map`` before matching against the - dropdown items, so aliases defined in that map are also accepted. - If ``int``, the zero-based index of the desired dropdown item. - - Raises - ------ - ValueError - If ``value`` is neither a ``str`` nor an ``int``. """ - if isinstance(value, int): - self.RGBDropDown.setCurrentIndex(value) - elif isinstance(value, str): - value = self.RGBDropDown.name_map[value.lower()] - items = [self.RGBDropDown.itemText(i).lower() for i in range(self.RGBDropDown.count())] - if value in items: - self.RGBDropDown.setCurrentIndex(items.index(value)) - else: - raise ValueError('Imcompatible color drop down setting') + l = [] + keys = sorted(self.labels.keys()) + for i, k in enumerate(keys): + if not self.labels[k].enabled: + l.append(i) + return tuple(l) + + + + + + # @color.setter + # def color(self, value: str|int): + # """Set the color display mode by name or dropdown index. + + # Updates the RGBDropDown widget, which triggers any connected signals + # (e.g. ``update_plot``). + + # Parameters + # ---------- + # value : str or int + # If ``str``, a case-insensitive color name (``'rgb'``, ``'red'``, + # ``'green'``, ``'blue'``, ``'gray'``, ``'spectral'``). The name is + # looked up via ``RGBDropDown.name_map`` before matching against the + # dropdown items, so aliases defined in that map are also accepted. + # If ``int``, the zero-based index of the desired dropdown item. + + # Raises + # ------ + # ValueError + # If ``value`` is neither a ``str`` nor an ``int``. + # """ + # # if isinstance(value, int): + # # self.RGBDropDown.setCurrentIndex(value) + # # elif isinstance(value, str): + # # value = self.RGBDropDown.name_map[value.lower()] + # # items = [self.RGBDropDown.itemText(i).lower() for i in range(self.RGBDropDown.count())] + # # if value in items: + # # self.RGBDropDown.setCurrentIndex(items.index(value)) + # # else: + # # raise ValueError('Imcompatible color drop down setting') + # print('ERROR not setting color') + # pass @property def view(self): @@ -1466,49 +1475,56 @@ def update_plot(self): if is_image_view or is_restored_view: if is_image_view: - image = self.stack[self.currentZ] + image = self.stack[self.currentZ].copy() else: - image = self.stack_filtered[self.currentZ] - if self.color == 'rgb': - self.img.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.img.setLevels(levels) - else: - self.img.setLevels(self.saturation[0][self.currentZ]) - elif self.color in rgb_list: - color_index = rgb_list.index(self.color) - if self.nchan > 1: - image = image[:, :, color_index] - self.img.setImage(image, autoLevels=False, lut=self.cmap[color_index+1]) - if self.nchan > 1: - self.img.setLevels(self.saturation[color_index][self.currentZ]) - else: - self.img.setLevels(self.saturation[0][self.currentZ]) - elif self.color == 'gray': - if self.nchan > 1: - # exclude channels with no data: - ranges = np.ptp(image, tuple(range(image.ndim-1))) - range_mask = ranges > 1e-5 - image = image[..., range_mask] - image = image.mean(axis=-1) - self.img.setImage(image, autoLevels=False, lut=None) - self.img.setLevels(self.saturation[0][self.currentZ]) - elif self.color == 'spectral': - if self.nchan > 1: - image = image.mean(axis=-1) - self.img.setImage(image, autoLevels=False, lut=self.cmap[0]) - self.img.setLevels(self.saturation[0][self.currentZ]) - else: - image = np.zeros((self.Ly, self.Lx), np.uint8) - if len(self.flows[flowp_map[self.view]]) > 0: - image = self.flows[flowp_map[self.view]][self.currentZ] - self.img.setImage(image, autoLevels=False, lut=self.bwr) - self.img.setLevels([0.0, 255.0]) + image = self.stack_filtered[self.currentZ].copy() + + inactive_channel_idxs = self.inactive_channels_idxs + image[:, :, inactive_channel_idxs] = 0 # this should really disable the lut instead of the image + + self.img.setImage(image, autoLevels=False, lut=None) + self.img.setLevels(self.saturation[0][self.currentZ]) + + # if self.active_channels == 'rgb': + # self.img.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.img.setLevels(levels) + # else: + # self.img.setLevels(self.saturation[0][self.currentZ]) + # elif self.active_channels in rgb_list: + # color_index = rgb_list.index(self.active_channels) + # if self.nchan > 1: + # image = image[:, :, color_index] + # self.img.setImage(image, autoLevels=False, lut=self.cmap[color_index+1]) + # if self.nchan > 1: + # self.img.setLevels(self.saturation[color_index][self.currentZ]) + # else: + # self.img.setLevels(self.saturation[0][self.currentZ]) + # elif self.active_channels == 'gray': + # if self.nchan > 1: + # # exclude channels with no data: + # ranges = np.ptp(image, tuple(range(image.ndim-1))) + # range_mask = ranges > 1e-5 + # image = image[..., range_mask] + # image = image.mean(axis=-1) + # self.img.setImage(image, autoLevels=False, lut=None) + # self.img.setLevels(self.saturation[0][self.currentZ]) + # elif self.active_channels == 'spectral': + # if self.nchan > 1: + # image = image.mean(axis=-1) + # self.img.setImage(image, autoLevels=False, lut=self.cmap[0]) + # self.img.setLevels(self.saturation[0][self.currentZ]) + # else: + # image = np.zeros((self.Ly, self.Lx), np.uint8) + # if len(self.flows[flowp_map[self.view]]) > 0: + # image = self.flows[flowp_map[self.view]][self.currentZ] + # self.img.setImage(image, autoLevels=False, lut=self.bwr) + # self.img.setLevels([0.0, 255.0]) for r in range(3): # setValue on the slider triggers update_plot() so it needs to be suppressed @@ -2152,13 +2168,20 @@ def go_next_previous_dropdown(self, dropdown, increment=1): class ClickableColorLabel(QLabel): - reqColorSignal = QtCore.Signal(object) + sigColorMapChoose = QtCore.Signal(object) + sigEnableToggled = QtCore.Signal(bool) - def __init__(self, *args, **kwargs): + + def __init__(self, *args, color_name=None,**kwargs): super().__init__(*args, **kwargs) + self._enabled = True + self.color_name = color_name + self.setStyleSheet(f"color: {self.color_name}") def mousePressEvent(self, ev): - if ev.button() == QtCore.Qt.RightButton: + if ev.button() == QtCore.Qt.LeftButton: + self.toggle() + if self._enabled and ev.button() == QtCore.Qt.RightButton: menu = QMenu(self) menu.addAction('Grays') menu.addAction('Purples') @@ -2167,11 +2190,33 @@ def mousePressEvent(self, ev): menu.addAction('cyan') action = menu.exec_(ev.globalPos()) if action: - self.reqColorSignal.emit(self.mpl_colormap(action.text())) + self.sigColorMapChoose.emit(self.colormap(action.text())) self.setText(action.text()+":") + @property + def enabled(self): + return self._enabled + + @enabled.setter + def enabled(self, val: bool): + if isinstance(val, bool): + if val != self._enabled: + self._enabled = val + print('emitting value') + self.sigEnableToggled.emit(val) + else: + raise ValueError('incompatible value') + + def toggle(self): + print('toggling!') + if self.enabled: + self.enabled = False + self.setStyleSheet("color: gray") + else: + self.enabled = True + self.setStyleSheet(f"color: {self.color_name}") - def mpl_colormap(self, name: str): + def colormap(self, name: str): if name in list(mpl.colormaps.keys()): cmap = mpl.colormaps[name](np.linspace(0, 1, 512))[:, :3] cmap *= 255 diff --git a/cellpose/gui/gui3d.py b/cellpose/gui/gui3d.py index 78160b1b..3292064d 100644 --- a/cellpose/gui/gui3d.py +++ b/cellpose/gui/gui3d.py @@ -471,7 +471,7 @@ def update_ortho(self): if self.nchan == 1: # show single channel image = image[..., 0] - if self.color == 'rgb': + if self.active_channels == 'rgb': self.imgOrtho[j].setImage(image, autoLevels=False, lut=None) if self.nchan > 1: levels = np.array([ @@ -483,8 +483,8 @@ def update_ortho(self): else: self.imgOrtho[j].setLevels( self.saturation[0][self.currentZ]) - elif self.color in rgb_list: - color_index = rgb_list.index(self.color) + elif self.active_channels in rgb_list: + color_index = rgb_list.index(self.active_channels) if self.nchan > 1: image = image[..., color_index] self.imgOrtho[j].setImage(image, autoLevels=False, @@ -495,7 +495,7 @@ def update_ortho(self): else: self.imgOrtho[j].setLevels( self.saturation[0][self.currentZ]) - elif self.color == 'gray': + elif self.active_channels == 'gray': if image.ndim > 2: # exclude blank channels: ranges = np.ptp(image, tuple(range(image.ndim-1))) @@ -504,7 +504,7 @@ def update_ortho(self): 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 == 'spectral': + elif self.active_channels == 'spectral': if image.ndim > 2: image = image.astype("float32").mean(axis=2).astype("uint8") self.imgOrtho[j].setImage(image, autoLevels=False, From 2cede9bb7cf88c8704a3c98687b2b64306a4dbb4 Mon Sep 17 00:00:00 2001 From: Michael Rariden Date: Tue, 28 Apr 2026 17:13:33 -0400 Subject: [PATCH 3/3] sort of working color drop downs --- cellpose/gui/gui.py | 85 +++++++++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 23 deletions(-) diff --git a/cellpose/gui/gui.py b/cellpose/gui/gui.py index 9a92de0f..b6dfa06d 100644 --- a/cellpose/gui/gui.py +++ b/cellpose/gui/gui.py @@ -386,8 +386,8 @@ def make_buttons(self): widget_row += 1 self.sliders = [] colors = [[255, 0, 0], [0, 255, 0], [0, 0, 255], [100, 100, 100]] - colornames = ["red", "Chartreuse", "DodgerBlue"] - self.labels = {} + colornames = ["red", "green", "blue"] + self.labels: dict[str, ClickableColorLabel] = {} for r in range(3): widget_row += 1 name = "Ch" + str(r+1) @@ -1020,17 +1020,16 @@ def reset(self): self.remove_roi_obj = None @property - def active_channels(self): - """Return the list of active channels + def active_channels(self) -> list[str]: + """Return the list of active channel names """ print("Error, didn't get color") l = [k for k, v in self.labels.items() if v.enabled] - print(l) return l @property - def inactive_channels_idxs(self): + def inactive_channels_idxs(self) -> tuple[int]: """Return the idxs of inactive channels """ @@ -1041,6 +1040,28 @@ def inactive_channels_idxs(self): l.append(i) return tuple(l) + @property + def active_channels_idxs(self) -> tuple[int]: + """Return the idxs of active channels + + """ + l = [] + keys = sorted(self.labels.keys()) + for i, k in enumerate(keys): + if self.labels[k].enabled: + l.append(i) + return tuple(l) + + @property + def active_luts(self) -> list[tuple[int, np.ndarray]]: + """ List of the active 8-bit LUTs """ + l = [] + keys = sorted(self.labels.keys()) + for i, k in enumerate(keys): + labels_k = self.labels[k] + if labels_k.enabled and labels_k.colormap is not None: + l.append((i, labels_k.colormap)) + return l @@ -1466,24 +1487,22 @@ def update_plot(self): is_image_view = self.view == 'image' is_restored_view = self.view == 'restored' - flowp_map = { - 'gradXY' : 0, - 'cellprob' : 1, - 'gradZ' : 4, - } - rgb_list = ['red', 'green', 'blue'] - if is_image_view or is_restored_view: if is_image_view: image = self.stack[self.currentZ].copy() else: image = self.stack_filtered[self.currentZ].copy() - inactive_channel_idxs = self.inactive_channels_idxs - image[:, :, inactive_channel_idxs] = 0 # this should really disable the lut instead of the image + image_out = np.zeros_like(image) + for chan, lut in self.active_luts: + sat = self.saturation[chan][self.currentZ] + img_c = np.clip(image[:, :, chan], np.uint8(sat[0]), np.uint8(sat[1])).astype(np.uint8) + img_lut = lut[img_c] + image_out += img_lut - self.img.setImage(image, autoLevels=False, lut=None) - self.img.setLevels(self.saturation[0][self.currentZ]) + image_out = np.clip(image_out, np.uint8(0), np.uint8(255)).astype(np.uint8) + self.img.setImage(image_out, autoLevels=False, lut=None) + # self.img.setLevels([self.saturation[i][self.currentZ] for i in range(3)]) # if self.active_channels == 'rgb': # self.img.setImage(image, autoLevels=False, lut=None) @@ -2172,17 +2191,23 @@ class ClickableColorLabel(QLabel): sigEnableToggled = QtCore.Signal(bool) - def __init__(self, *args, color_name=None,**kwargs): + def __init__(self, *args, color_name, **kwargs): super().__init__(*args, **kwargs) self._enabled = True self.color_name = color_name self.setStyleSheet(f"color: {self.color_name}") + with QtCore.QSignalBlocker(self): + self.colormap = self.color_name + def mousePressEvent(self, ev): if ev.button() == QtCore.Qt.LeftButton: self.toggle() if self._enabled and ev.button() == QtCore.Qt.RightButton: menu = QMenu(self) + menu.addAction('red') + menu.addAction('green') + menu.addAction('blue') menu.addAction('Grays') menu.addAction('Purples') menu.addAction('viridis') @@ -2190,7 +2215,8 @@ def mousePressEvent(self, ev): menu.addAction('cyan') action = menu.exec_(ev.globalPos()) if action: - self.sigColorMapChoose.emit(self.colormap(action.text())) + self.colormap = action.text() + self.sigColorMapChoose.emit(self.colormap) self.setText(action.text()+":") @property @@ -2216,6 +2242,11 @@ def toggle(self): self.enabled = True self.setStyleSheet(f"color: {self.color_name}") + @property + def colormap(self) -> np.ndarray: + return self._cmap + + @colormap.setter def colormap(self, name: str): if name in list(mpl.colormaps.keys()): cmap = mpl.colormaps[name](np.linspace(0, 1, 512))[:, :3] @@ -2224,11 +2255,19 @@ def colormap(self, name: str): elif name == 'magenta': cmap = np.linspace(0, 255, 512, dtype=np.uint8) cmap = np.stack([cmap, np.zeros(512, dtype=np.uint8), cmap], axis=1) + elif name == 'red': + cmap = np.linspace(0, 255, 512, dtype=np.uint8) + cmap = np.stack([cmap, np.zeros(512, dtype=np.uint8), np.zeros(512, dtype=np.uint8)], axis=1) + elif name == 'green': + cmap = np.linspace(0, 255, 512, dtype=np.uint8) + cmap = np.stack([np.zeros(512, dtype=np.uint8), cmap, np.zeros(512, dtype=np.uint8)], axis=1) + elif name == 'blue': + cmap = np.linspace(0, 255, 512, dtype=np.uint8) + cmap = np.stack([np.zeros(512, dtype=np.uint8), np.zeros(512, dtype=np.uint8), cmap], axis=1) elif name == 'cyan': cmap = np.linspace(0, 255, 512, dtype=np.uint8) cmap = np.stack([np.zeros(512, dtype=np.uint8), cmap, cmap], axis=1) else: - print("unrecognized cmap") - return - print(f'setting cmap to {name}') - return cmap \ No newline at end of file + raise ValueError(f'unrecognized cmap: {name}') + self._cmap = cmap + \ No newline at end of file