Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

55 Initialize UI Elements correctly for multichannel TIFF images #72

Merged
merged 6 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 154 additions & 43 deletions cellpose/gui/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from qtpy.QtWidgets import QScrollArea, QMainWindow, QApplication, QWidget, QScrollBar, QComboBox, QGridLayout, QPushButton, QFrame, QCheckBox, QLabel, QProgressBar, QLineEdit, QMessageBox, QGroupBox, QColorDialog
import pyqtgraph as pg
from qtpy.QtGui import QIcon, QColor
from PIL import Image

import numpy as np
from scipy.stats import mode
Expand Down Expand Up @@ -144,6 +145,17 @@ def make_cmap(cm=0):
cmap = pg.ColorMap(pos=np.linspace(0.0, 255, 256), color=color)
return cmap

def rgb_to_hex(rgb_tuple):
"""
Converts an RGB tuple to a hex color string.

Args:
rgb_tuple (tuple): The RGB tuple (e.g., (255, 0, 0) for red).

Returns:
str: The hex color string (e.g., '#ff0000' for red).
"""
return '#{:02x}{:02x}{:02x}'.format(rgb_tuple[0], rgb_tuple[1], rgb_tuple[2])

def run(image=None):
from ..io import logger_setup
Expand Down Expand Up @@ -360,6 +372,15 @@ def minimap_window(self):
self.minimap_window_instance = None

def color_initialization(self):
"""
Initializes the color stack for multi-channel images.

This method assigns initial colors to each layer in the grayscale image stack.
It uses a predefined list of colors and cycles through them if there are more layers
than colors available.

The colors are assigned in a cyclic manner to ensure that each layer has a color.
"""
colors = [
(255, 0, 0), # Red
(0, 255, 0), # Green
Expand Down Expand Up @@ -507,6 +528,8 @@ def onViewChanged(self):
# if an exception of any kind occurs, the specific exception is printed to the console
print(f"An error occurred while changing the view: {e}")



def make_buttons(self):
self.boldfont = QtGui.QFont("Arial", 11, QtGui.QFont.Bold)
self.boldmedfont = QtGui.QFont("Arial", 9, QtGui.QFont.Bold)
Expand Down Expand Up @@ -562,41 +585,45 @@ def make_buttons(self):
self.autobtn.setChecked(True)
self.satBoxG.addWidget(self.autobtn, b0, 1, 1, 8)


#--- Initialization of Non-Tiff cases ---#
c = 0 # position of the elements in the right side menu

self.sliders = []
# ---Create a list (extendable) of color/on-off buttons ---#
# Define color names and labels for non-TIFF images
colornames = ["red", "Chartreuse", "DodgerBlue"]
names = ["red", "green", "blue"]
colors = ["red", "green", "blue"]
self.marker_buttons = [self.create_color_button(color) for color in colors]
self.on_off_buttons = [self.create_on_off_button() for color in colors]

# Initialize sliders list
self.sliders = []

# Add labels and sliders for non-TIFF images
for r in range(3):
c += 1

label = QLabel(f'Marker {r + 1}') # create a label for each marker
color_button = self.marker_buttons[r] # get the corresponding color button
self.marker_buttons = [self.create_color_button(color) for color in colors]
on_off_button = self.on_off_buttons[r] # get the corresponding on-off button
label.setStyleSheet("color: white")
# Create a label for each color channel
if r == 0:
label = QLabel('<font color="gray">gray/</font><br>red')
else:
label = QLabel(names[r] + ":")
label.setStyleSheet(f"color: {colornames[r]}")
label.setFont(self.boldmedfont)
self.rightBoxLayout.addWidget(label, c, 0, 1, 1)
self.rightBoxLayout.addWidget(color_button, c, 9, 1, 1) # add the color button to the layout
self.rightBoxLayout.addWidget(on_off_button, c, 10, 1, 1) # add the on-off button to the layout
self.sliders.append(Slider(self, colors[r], None))
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"
)

self.sliders[-1].setFixedWidth(250)
self.rightBoxLayout.addWidget(self.sliders[-1], c, 2, 1, 7)
stretch_widget = QWidget()
self.rightBoxLayout.addWidget(stretch_widget)


self.rightBoxLayout.addWidget(label, c, 0, 1, 2)

# Create and configure the slider
slider = Slider(self, colors[r], None)
slider.setMinimum(-.1)
slider.setMaximum(255.1)
slider.setValue([0, 255])
slider.setToolTip("NOTE: manually changing the saturation bars does not affect normalization in segmentation")
slider.setFixedWidth(250)

# Add the slider to the layout
self.sliders.append(slider)
self.rightBoxLayout.addWidget(slider, c, 2, 1, 7)

# Add a stretch widget to the layout
stretch_widget = QWidget()
self.rightBoxLayout.addWidget(stretch_widget)
b += 1
self.drawBox = QGroupBox("Drawing")
self.drawBox.setFont(self.boldfont)
Expand Down Expand Up @@ -1050,33 +1077,115 @@ def make_buttons(self):

return b

def create_color_button(self, color):

def generate_multi_channel_ui(self, num_layers, is_tiff):
"""
Creates and initializes all the buttons and UI elements used in the GUI.
This includes buttons for changing views, drawing, segmentation, model
selection, and image restoration. Also initializes color buttons with
specific colors (red, green, blue) and on-off buttons.
Generates UI components for multi-channel images.

Returns:
int: The number of buttons and UI elements created.
"""
This method creates and initializes sliders, color buttons, and on/off buttons based on the number of layers
in the loaded multi-channel TIFF image. It also ensures that these components are displayed or hidden appropriately.

Args:
num_layers (int): The number of layers in the loaded multi-channel TIFF image.
is_tiff (bool): Flag indicating whether the image is a TIFF file.
"""
# Position of elements in the layout
c = 0

# Clear existing elements in the layout
for i in reversed(range(self.rightBoxLayout.count())):
widget = self.rightBoxLayout.itemAt(i).widget()
if widget is not None:
widget.setParent(None)

# Initialize lists for buttons and sliders
self.sliders = []
self.marker_buttons = []
self.on_off_buttons = []

if is_tiff:
# Create color buttons and on/off buttons for each layer
self.marker_buttons = [
self.create_color_button(rgb_to_hex(color))
for color in self.colors_stack[:num_layers]
]
self.on_off_buttons = [self.create_on_off_button() for _ in range(num_layers)]

for r in range(num_layers):
c += 1

# Create label for each marker
label = QLabel(f'Marker {r + 1}')
label.setStyleSheet("color: white")
label.setFont(self.boldmedfont)

# Get the corresponding color button and on/off button
color_button = self.marker_buttons[r]
on_off_button = self.on_off_buttons[r]

# Add components to the layout
self.rightBoxLayout.addWidget(label, c, 0, 1, 1)
self.rightBoxLayout.addWidget(color_button, c, 9, 1, 1)
self.rightBoxLayout.addWidget(on_off_button, c, 10, 1, 1)

# Create and add the slider
slider_name = r
slider_color = rgb_to_hex(self.colors_stack[r])
slider = Slider(self, slider_name, slider_color)
slider.setMinimum(-.1)
slider.setMaximum(255.1)
slider.setValue([0, 255])
slider.setToolTip("NOTE: manually changing the saturation bars does not affect normalization in segmentation")
slider.setFixedWidth(250)

self.sliders.append(slider)
self.rightBoxLayout.addWidget(slider, c, 2, 1, 7)


# Add a stretch widget to the layout
stretch_widget = QWidget()
self.rightBoxLayout.addWidget(stretch_widget)

# Show or hide color dialog and on/off buttons based on the is_tiff flag
if is_tiff and num_layers > 3:
for button in self.marker_buttons:
button.show()
for button in self.on_off_buttons:
button.show()
else:
for button in self.marker_buttons:
button.hide()
for button in self.on_off_buttons:
button.hide()

def create_color_button(self, hex_color):
"""
Creates a color button with the specified color.

Args:
hex_color (str): Hex color string.

Returns:
QPushButton: The created color button.
"""
color_button = QPushButton()
color_button.setStyleSheet(self.get_color_button_style(color))
color_button.setStyleSheet(self.get_color_button_style(hex_color))
color_button.clicked.connect(self.open_color_dialog)
print(f"Created button with color: {hex_color}")
return color_button

def create_on_off_button(self):
"""
Creates a new QPushButton for toggling on and off, with an initial "off" state,
Creates a new QPushButton for toggling on and off, with an initial "on" state,
and connects its clicked signal to the toggle_on_off method.

Returns:
QPushButton: The created on-off button.
"""
on_off_button = QPushButton()
on_off_button.setCheckable(True)
on_off_button.setChecked(False)
on_off_button.setIcon(QIcon("cellpose/resources/icon/visibility_off.png")) # Icon for "off" state
on_off_button.setChecked(True) # Initial state is "on"
on_off_button.setIcon(QIcon("cellpose/resources/icon/visibility_on.png")) # Icon for "on" state
on_off_button.setIconSize(QtCore.QSize(12, 12))
on_off_button.clicked.connect(self.toggle_on_off)
return on_off_button
Expand Down Expand Up @@ -1112,26 +1221,28 @@ def open_color_dialog(self):
if color.isValid():
self.sender().setStyleSheet(self.get_color_button_style(color.name()))

def get_color_button_style(self, color_name):
def get_color_button_style(self, hex_color):
"""
Returns a string with the CSS style for a QPushButton with the specified background color, a solid border, a border width of 1 pixel, and a size of 12x12 pixels.
Returns a string with the CSS style for a QPushButton with the specified background color,
a solid border, a border width of 1 pixel, and a size of 12x12 pixels.

Args:
color_name (str): The name of the color to use for the button's background.
hex_color (str): The hex color string to use for the button's background.

Returns:
str: The CSS style for the button.
"""
return f"""
QPushButton {{
background-color: {color_name};
background-color: {hex_color};
border-style: solid;
border-width: 1px;
height: 12px;
width: 12px;
}}
"""


def level_change(self, r):
r = ["red", "green", "blue"].index(r)
if self.loaded:
Expand Down
13 changes: 9 additions & 4 deletions cellpose/gui/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import tifffile
import logging
import fastremap

from PIL import Image

from ..io import imread, imsave, outlines_to_text, add_model, remove_model, save_rois, save_settings, save_features_csv
from ..models import normalize_default, MODEL_DIR, MODEL_LIST_PATH, get_user_models
Expand Down Expand Up @@ -110,21 +110,25 @@ def _load_image(parent, filename=None, load_seg=True, load_3D=False):
if filename is None:
name = QFileDialog.getOpenFileName(parent, "Load image")
filename = name[0]
print("keine tiff")

#checks if the file is a tiff
if filename and (filename.endswith('.tif') or filename.endswith('.tiff')):
successful_import, grayscale_image_stack = initialize_tiff_images(
filename)
print(successful_import)
if successful_import:
parent.grayscale_image_stack = grayscale_image_stack

# Initialize the colors and colored_image_stack attributes
parent.color_initialization()
parent.generate_color_image_stack()

# Initialize the Buttons and sliders
# parent.generate_multi_channel_ui()

# Initialize the Buttons and sliders for multi-layer TIFF
num_layers = len(grayscale_image_stack)
is_tiff = True
parent.generate_multi_channel_ui(num_layers, is_tiff)
print(f"GUI_INFO: Tiff loaded with {len(grayscale_image_stack)} layers")

manual_file = os.path.splitext(filename)[0] + "_seg.npy"
load_mask = False
Expand Down Expand Up @@ -798,4 +802,5 @@ def initialize_tiff_images(tiff_file_path):
return ret, processed_images
return ret, []
except Exception as e:
print(e)
return False, []
Loading