Skip to content

Commit

Permalink
Merge branch 'main' into 53_custom_color_new
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaSchlucke committed Aug 9, 2024
2 parents 0fd85af + 91371b5 commit 0935262
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 23 deletions.
119 changes: 102 additions & 17 deletions cellpose/gui/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ def __init__(self, parent, name, color):
""")
self.show()



def levelChanged(self, parent):
parent.level_change(self.name)

Expand Down Expand Up @@ -147,6 +145,15 @@ def make_cmap(cm=0):
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])


Expand Down Expand Up @@ -308,6 +315,9 @@ def __init__(self, image=None, logger=None):
self.reset()
self.minimap_window_instance = None

# if the view of the image is changed, the method onViewChanged is called
self.p0.sigRangeChanged.connect(self.onViewChanged)

# Custom multi-page tiff image stack
self.grayscale_image_stack = []
self.colors_stack = []
Expand Down Expand Up @@ -364,8 +374,6 @@ def minimap_window(self):
self.minimap_window_instance.deleteLater()
self.minimap_window_instance = None



def color_initialization(self):
"""
Initializes the color stack by assigning every layer a standard color by repeating colors in the list.
Expand Down Expand Up @@ -514,6 +522,55 @@ def center_view_on_position(self, normalized_x, normalized_y):
self.p0.setXRange(*new_x_range, padding=0)
self.p0.setYRange(*new_y_range, padding=0)

def onViewChanged(self):
"""
This function is called whenever the view of the image in the view box is changed.
This includes it being zoomed in or zoomed out, as well as it being moved around.
It normalizes coordinates and dimensions of the view box in relation to the current image.
Returns:
Normalized coordinates of the view box (normalized_x, normalized_y)
Normalized dimensions of the view box (normalized_width, normalized_height)
"""

# Access the positional values of the view box p0 in form of a rectangle using viewRect()
view_rect = self.p0.viewRect()

# Extract the x and y coordinates of the view box
x_coordinates = [view_rect.left(), view_rect.right()]
y_coordinates = [view_rect.top(), view_rect.bottom()]

# Extract the dimensions of the view box
width = view_rect.width()
height = view_rect.height()

try:

# Get the size of the image
img_height = self.img.image.shape[0]
img_width = self.img.image.shape[1]

# Calculate the normalized coordinates in relation to the image size
normalized_x = tuple(
coordinate / img_width for coordinate in x_coordinates)
normalized_y = tuple(
coordinate / img_height for coordinate in y_coordinates)

# Calculate the normalized dimensions
normalized_width = width / img_width
normalized_height = height / img_height

# Set the highlight area in the minimap window
self.minimap_window_instance.set_highlight_area(normalized_x[0], normalized_y[0], normalized_width, normalized_height)

return normalized_x, normalized_y, normalized_width, normalized_height

except Exception as e:

# 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 generate_multi_channel_ui(self, n):
c = 0 # Position der Elemente im Layout
Expand Down Expand Up @@ -617,25 +674,32 @@ 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

# Define color names and labels for non-TIFF images
colornames = ["red", "Chartreuse", "DodgerBlue"]
names = ["red", "green", "blue"]
colors = ["red", "green", "blue"]

# Initialize sliders list
self.sliders = []
# ---Create a list (extendable) of color/on-off buttons ---#
colors = ["red", "green", "blue"]
# self.marker_buttons = [self.create_color_button(color, None) for color in colors]
# self.on_off_buttons = [self.create_on_off_button(i) for i in range self.]

# 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, None) for color in colors]
# on_off_button = self.on_off_buttons[r] # get the corresponding on-off button
label.setStyleSheet("color: white")
label.setFont(self.boldmedfont)
# 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]}")
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))
Expand Down Expand Up @@ -1230,7 +1294,7 @@ def get_color_button_style(self, color_name):
height: 12px;
width: 12px;
}}
"""
"""


def set_image_opacity(self, image, opacity):
Expand Down Expand Up @@ -1298,12 +1362,12 @@ def adjust_channel_bounds(self, channel, bounds):


def level_change(self, r):
if self.tiff_loaded:
if int(r) < len(self.sliders):
if self.tiff_loaded: # if tiff is loaded, sliders adjust contrast of each layer
if int(r) < len(self.sliders): # make sure the list of sliders already filled
r_index = r
if self.on_off_buttons[r].isChecked():
self.adjust_channel_bounds(r_index, self.sliders[r_index].value())
self.update_plot()
self.adjust_channel_bounds(r_index, self.sliders[r_index].value()) # adjust contrast of layer
self.update_plot() # update plot

else:
r = ["red", "green", "blue"].index(r)
Expand Down Expand Up @@ -1504,6 +1568,7 @@ def disable_buttons_removeROIs(self):
self.saveSet.setEnabled(False)
self.savePNG.setEnabled(False)
self.saveFlows.setEnabled(False)
self.saveFeaturesCsv.setEnabled(False)
self.saveOutlines.setEnabled(False)
self.saveROIs.setEnabled(False)
self.minimapWindow.setEnabled(False)
Expand Down Expand Up @@ -1532,6 +1597,26 @@ def toggle_saving(self):
self.saveOutlines.setEnabled(False)
self.saveROIs.setEnabled(False)

def toggle_save_features_csv(self):
"""
Toggles the save features csv button based on the image file type.
If the image is a tiff or tif file, the button is enabled. Otherwise, it is disabled.
This method is called after a segmentation has taken place.
Args:
None
Returns:
None
"""

filetype = imghdr.what(self.filename)

if filetype in ['tiff', 'tif']:
self.saveFeaturesCsv.setEnabled(True)
else:
self.saveFeaturesCsv.setEnabled(False)

def toggle_removals(self):
if self.ncells > 0:
self.ClearButton.setEnabled(True)
Expand Down
53 changes: 53 additions & 0 deletions cellpose/gui/guiparts.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,20 @@ def __init__(self, parent=None):
# This marks the menu button as checked
parent.minimapWindow.setChecked(True)

# Create and add a highlight rectangle to the minimap with initial position [0, 0] and size [100, 100], outlined
# in white with a 3-pixel width.
self.highlight_area = pg.RectROI([0, 0], [100, 100], pen=pg.mkPen('w', width=3), resizable=False, movable=False)
self.highlight_area.hoverEvent = lambda event: None
# Remove all resize handles after initialization
QtCore.QTimer.singleShot(0, lambda: [self.highlight_area.removeHandle(handle) for handle in
self.highlight_area.getHandles()])


# Set the highlight area to cover the entire image by default
self.set_highlight_area(0, 0, 1, 1)
# Add the highlight area to the viewbox
self.viewbox.addItem(self.highlight_area)

def closeEvent(self, event: QEvent):
"""
Method to uncheck the button in the menu if the window is closed.
Expand Down Expand Up @@ -501,6 +515,45 @@ def update_minimap(self, parent):
else:
self.setFixedSize(self.minimapSize, self.minimapSize)

def set_highlight_area(self, normalized_x, normalized_y, normalized_width, normalized_height):
"""
Method to set the highlight area on the minimap.
The position and size of the rectangle are set based on the calculated normalized coordinates from the
onViewChanged method.
Parameters:
normalized_x (float): Normalized x-coordinate for the position.
normalized_y (float): Normalized y-coordinate for the position.
normalized_width (float): Normalized width for the size.
normalized_height (float): Normalized height for the size.
Returns:
tuple: The calculated (x, y, width, height) coordinates.
"""

if self.parent().img.image is not None:
# Retrieve the height and width of the image
img_height = self.parent().img.image.shape[0]
img_width = self.parent().img.image.shape[1]

# Calculate the position and size of the highlight area based on the normalized coordinates
x = normalized_x * img_width
y = normalized_y * img_height
width = normalized_width * img_width
height = normalized_height * img_height

# Set the position of the rectangle area on the minimap
# Move the rectangle to the calculated position
self.highlight_area.setPos(x, y)

# Set the size of the rectangle on the minimap
# Adjust the rectangle's size to the calculated width and height
self.highlight_area.setSize([width, height])

else:
print("Error: No image loaded in parent.")


def sliderValueChanged(self, value):
"""
Method to change the size of the minimap based on the slider value.
Expand Down
16 changes: 15 additions & 1 deletion cellpose/gui/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ def _load_image(parent, filename=None, load_seg=True, load_3D=False):
parent.minimap_window_instance = guiparts.MinimapWindow(parent)
parent.minimap_window_instance.show()

parent.saveFeaturesCsv.setEnabled(False)


def _initialize_images(parent, image, load_3D=False):
""" format image for GUI """
Expand Down Expand Up @@ -503,6 +505,8 @@ def _load_masks(parent, filename=None):
if parent.ncells > 0:
parent.draw_layer()
parent.toggle_mask_ops()
# features can only be saved if masks are loaded
parent.toggle_save_features_csv()
del masks
gc.collect()
parent.update_layer()
Expand Down Expand Up @@ -582,6 +586,8 @@ def _masks_to_gui(parent, masks, outlines=None, colors=None):
if parent.ncells > 0:
parent.draw_layer()
parent.toggle_mask_ops()
# features can only be saved if masks are loaded
parent.toggle_save_features_csv()
parent.ismanual = np.zeros(parent.ncells, bool)
parent.zdraw = list(-1 * np.ones(parent.ncells, np.int16))

Expand All @@ -591,6 +597,7 @@ def _masks_to_gui(parent, masks, outlines=None, colors=None):
else:
parent.ViewDropDown.setCurrentIndex(0)


def _save_features_csv(parent):
"""
Saves features to CSV if dataset is 2D.
Expand All @@ -605,11 +612,18 @@ def _save_features_csv(parent):
return

filename = parent.filename

base = os.path.splitext(filename)[0] + "_features.csv"

# check if the dataset is 2D (NZ == 1 implies a single z-layer)
if parent.NZ == 1:
print("GUI_INFO: saving features to CSV file")
save_features_csv(parent.filename)
# this gives us the channels that are currently loaded by converting the images to an array
stacked_images = parent.convert_images_to_array(parent.grayscale_image_stack)
# reduction to only the relevant light intensity channel
channels = stacked_images[:, :, :, 1]
# save the features to a CSV file using method in cellpose.io
save_features_csv(parent.filename, parent.cellpix, channels)
else:
print("ERROR: cannot save features")

Expand Down
3 changes: 2 additions & 1 deletion cellpose/gui/menus.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,13 @@ def mainmenu(parent):
This adds a new menu item for saving features as a .csv file.
The user can activate this function to export specific data directly from the GUI.
The function `_save_features_as_csv` from the `io` module is called when the user clicks on the menu item.
It is disabled by default and only activated if an image is segmented and of the type tif/tiff.
"""
parent.saveFeaturesCsv = QAction("Save Features as .&csv", parent)
parent.saveFeaturesCsv.setShortcut("Ctrl+Shift+C")
parent.saveFeaturesCsv.triggered.connect(lambda: io._save_features_csv(parent))
file_menu.addAction(parent.saveFeaturesCsv)
parent.saveFeaturesCsv.setEnabled(True)
parent.saveFeaturesCsv.setEnabled(False)

"""
This creates a new menu item for the minimap that the user can activate.
Expand Down
Loading

0 comments on commit 0935262

Please sign in to comment.