Skip to content
Snippets Groups Projects
Commit 2dc8ba0e authored by s224362's avatar s224362
Browse files

Documenting code

parents bda90527 4cfed8cb
No related branches found
No related tags found
No related merge requests found
Showing
with 336 additions and 0 deletions
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
from PyQt5.QtWidgets import (
QPushButton, QVBoxLayout, QWidget,
QSlider, QLabel, QGridLayout, QSizePolicy
)
from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtCore import Qt
import numpy as np
class AdvancedSettingsWidget(QWidget):
"""
Shows toggle rainbow, circle editor, line smoothing slider, contrast slider,
plus two image previews (contrasted-blurred and cost).
The images should maintain aspect ratio upon resize.
"""
def __init__(self, main_window, parent=None):
super().__init__(parent)
self._main_window = main_window
self._last_cb_pix = None # store QPixmap for contrasted-blurred
self._last_cost_pix = None # store QPixmap for cost
main_layout = QVBoxLayout()
self.setLayout(main_layout)
# A small grid for controls
controls_layout = QGridLayout()
# 1) Rainbow toggle
self.btn_toggle_rainbow = QPushButton("Toggle Rainbow")
self.btn_toggle_rainbow.clicked.connect(self._on_toggle_rainbow)
controls_layout.addWidget(self.btn_toggle_rainbow, 0, 0)
# 2) Circle editor
self.btn_circle_editor = QPushButton("Calibrate Kernel Size")
self.btn_circle_editor.clicked.connect(self._main_window.open_circle_editor)
controls_layout.addWidget(self.btn_circle_editor, 0, 1)
# 3) Line smoothing slider + label
self._lab_smoothing = QLabel("Line smoothing (3)")
controls_layout.addWidget(self._lab_smoothing, 1, 0)
self.line_smoothing_slider = QSlider(Qt.Horizontal)
self.line_smoothing_slider.setRange(3, 51)
self.line_smoothing_slider.setValue(3)
self.line_smoothing_slider.valueChanged.connect(self._on_line_smoothing_slider)
controls_layout.addWidget(self.line_smoothing_slider, 1, 1)
# 4) Contrast slider + label
self._lab_contrast = QLabel("Contrast (0.01)")
controls_layout.addWidget(self._lab_contrast, 2, 0)
self.contrast_slider = QSlider(Qt.Horizontal)
self.contrast_slider.setRange(1, 20)
self.contrast_slider.setValue(1) # i.e. 0.01
self.contrast_slider.setSingleStep(1)
self.contrast_slider.valueChanged.connect(self._on_contrast_slider)
controls_layout.addWidget(self.contrast_slider, 2, 1)
main_layout.addLayout(controls_layout)
# We'll set a minimum width so that the main window expands
# rather than overlapping the image
self.setMinimumWidth(350)
# Now a vertical layout for the two images, each with a label above it
images_layout = QVBoxLayout()
# 1) Contrasted-blurred label + image
self.label_cb_title = QLabel("Contrasted Blurred Image")
self.label_cb_title.setAlignment(Qt.AlignCenter)
images_layout.addWidget(self.label_cb_title)
self.label_contrasted_blurred = QLabel()
self.label_contrasted_blurred.setAlignment(Qt.AlignCenter)
self.label_contrasted_blurred.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
images_layout.addWidget(self.label_contrasted_blurred)
# 2) Cost image label + image
self.label_cost_title = QLabel("Current COST IMAGE")
self.label_cost_title.setAlignment(Qt.AlignCenter)
images_layout.addWidget(self.label_cost_title)
self.label_cost_image = QLabel()
self.label_cost_image.setAlignment(Qt.AlignCenter)
self.label_cost_image.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
images_layout.addWidget(self.label_cost_image)
main_layout.addLayout(images_layout)
def showEvent(self, event):
""" When shown, ask parent to resize to accommodate. """
super().showEvent(event)
if self.parentWidget():
self.parentWidget().adjustSize()
def resizeEvent(self, event):
"""
Keep the images at correct aspect ratio by re-scaling
our stored pixmaps to the new label sizes.
"""
super().resizeEvent(event)
self._update_labels()
def _update_labels(self):
if self._last_cb_pix is not None:
scaled_cb = self._last_cb_pix.scaled(
self.label_contrasted_blurred.size(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
self.label_contrasted_blurred.setPixmap(scaled_cb)
if self._last_cost_pix is not None:
scaled_cost = self._last_cost_pix.scaled(
self.label_cost_image.size(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
self.label_cost_image.setPixmap(scaled_cost)
def _on_toggle_rainbow(self):
self._main_window.toggle_rainbow()
def _on_line_smoothing_slider(self, value):
self._lab_smoothing.setText(f"Line smoothing ({value})")
self._main_window.image_view.set_savgol_window_length(value)
def _on_contrast_slider(self, value):
clip_limit = value / 100.0
self._lab_contrast.setText(f"Contrast ({clip_limit:.2f})")
self._main_window.update_contrast(clip_limit)
def update_displays(self, contrasted_img_np, cost_img_np):
"""
Called by main_window to refresh the two images in the advanced panel.
We'll store them as QPixmaps, then do the re-scale in _update_labels().
"""
cb_pix = self._np_array_to_qpixmap(contrasted_img_np)
cost_pix = self._np_array_to_qpixmap(cost_img_np, normalize=True)
self._last_cb_pix = cb_pix
self._last_cost_pix = cost_pix
self._update_labels()
def _np_array_to_qpixmap(self, arr, normalize=False):
if arr is None:
return None
arr_ = arr.copy()
if normalize:
mn, mx = arr_.min(), arr_.max()
if abs(mx - mn) < 1e-12:
arr_[:] = 0
else:
arr_ = (arr_ - mn) / (mx - mn)
arr_ = np.clip(arr_, 0, 1)
arr_255 = (arr_ * 255).astype(np.uint8)
h, w = arr_255.shape
qimage = QImage(arr_255.data, w, h, w, QImage.Format_Grayscale8)
return QPixmap.fromImage(qimage)
from PyQt5.QtWidgets import QGraphicsView
from panZoomGraphicsView import PanZoomGraphicsView
from PyQt5.QtCore import Qt
from draggableCircleItem import DraggableCircleItem
# A specialized PanZoomGraphicsView for the circle editor
class CircleEditorGraphicsView(PanZoomGraphicsView):
def __init__(self, circle_editor_widget, parent=None):
super().__init__(parent)
self._circle_editor_widget = circle_editor_widget
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
# Check if user clicked on the circle item
clicked_item = self.itemAt(event.pos())
if clicked_item is not None:
# climb up parent chain
it = clicked_item
while it is not None and not hasattr(it, "boundingRect"):
it = it.parentItem()
if isinstance(it, DraggableCircleItem):
# Let normal item-dragging occur, no pan
return QGraphicsView.mousePressEvent(self, event)
super().mousePressEvent(event)
def wheelEvent(self, event):
"""
If the mouse is hovering over the circle, we adjust the circle's radius
instead of zooming the image.
"""
pos_in_widget = event.pos()
item_under = self.itemAt(pos_in_widget)
if item_under is not None:
it = item_under
while it is not None and not hasattr(it, "boundingRect"):
it = it.parentItem()
if isinstance(it, DraggableCircleItem):
delta = event.angleDelta().y()
step = 1 if delta > 0 else -1
old_r = it.radius()
new_r = max(1, old_r + step)
it.set_radius(new_r)
self._circle_editor_widget.update_slider_value(new_r)
event.accept()
return
super().wheelEvent(event)
from PyQt5.QtWidgets import (
QGraphicsScene, QGraphicsPixmapItem, QPushButton,
QHBoxLayout, QVBoxLayout, QWidget, QSlider, QLabel
)
from PyQt5.QtGui import QFont
from PyQt5.QtCore import Qt, QRectF, QSize
from circleEditorGraphicsView import CircleEditorGraphicsView
from draggableCircleItem import DraggableCircleItem
class CircleEditorWidget(QWidget):
def __init__(self, pixmap, init_radius=20, done_callback=None, parent=None):
super().__init__(parent)
self._pixmap = pixmap
self._done_callback = done_callback
self._init_radius = init_radius
layout = QVBoxLayout(self)
self.setLayout(layout)
#
# 1) ADD A CENTERED LABEL ABOVE THE IMAGE, WITH BIGGER FONT
#
label_instructions = QLabel("Scale the dot to be of the size of your ridge")
label_instructions.setAlignment(Qt.AlignCenter)
big_font = QFont("Arial", 20)
big_font.setBold(True)
label_instructions.setFont(big_font)
layout.addWidget(label_instructions)
#
# 2) THE SPECIALIZED GRAPHICS VIEW THAT SHOWS THE IMAGE
#
self._graphics_view = CircleEditorGraphicsView(circle_editor_widget=self)
self._scene = QGraphicsScene(self)
self._graphics_view.setScene(self._scene)
layout.addWidget(self._graphics_view)
# Show the image
self._image_item = QGraphicsPixmapItem(self._pixmap)
self._scene.addItem(self._image_item)
# Put circle in center
cx = self._pixmap.width() / 2
cy = self._pixmap.height() / 2
self._circle_item = DraggableCircleItem(cx, cy, radius=self._init_radius, color=Qt.red)
self._scene.addItem(self._circle_item)
# Fit in view
self._graphics_view.setSceneRect(QRectF(self._pixmap.rect()))
self._graphics_view.fitInView(self._image_item, Qt.KeepAspectRatio)
#
# 3) CONTROLS BELOW
#
bottom_layout = QHBoxLayout()
layout.addLayout(bottom_layout)
# label + slider
self._lbl_size = QLabel(f"size ({self._init_radius})")
bottom_layout.addWidget(self._lbl_size)
self._slider = QSlider(Qt.Horizontal)
self._slider.setRange(1, 200)
self._slider.setValue(self._init_radius)
bottom_layout.addWidget(self._slider)
# done button
self._btn_done = QPushButton("Done")
bottom_layout.addWidget(self._btn_done)
# Connect signals
self._slider.valueChanged.connect(self._on_slider_changed)
self._btn_done.clicked.connect(self._on_done_clicked)
def _on_slider_changed(self, value):
self._circle_item.set_radius(value)
self._lbl_size.setText(f"size ({value})")
def _on_done_clicked(self):
final_radius = self._circle_item.radius()
if self._done_callback is not None:
self._done_callback(final_radius)
def update_slider_value(self, new_radius):
self._slider.blockSignals(True)
self._slider.setValue(new_radius)
self._slider.blockSignals(False)
self._lbl_size.setText(f"size ({new_radius})")
def sizeHint(self):
return QSize(800, 600)
import numpy as np
def circle_edge_kernel(k_size=5, radius=None):
"""
Create a k_size x k_size array whose values increase linearly
from 0 at the center to 1 at the circle boundary (radius).
Parameters
----------
k_size : int
The size (width and height) of the kernel array.
radius : float, optional
The circle's radius. By default, set to (k_size-1)/2.
Returns
-------
kernel : 2D numpy array of shape (k_size, k_size)
The circle-edge-weighted kernel.
"""
if radius is None:
# By default, let the radius be half the kernel size
radius = (k_size - 1) / 2
# Create an empty kernel
kernel = np.zeros((k_size, k_size), dtype=float)
# Coordinates of the center
center = radius # same as (k_size-1)/2 if radius is default
# Fill the kernel
for y in range(k_size):
for x in range(k_size):
dist = np.sqrt((x - center)**2 + (y - center)**2)
if dist <= radius:
# Weight = distance / radius => 0 at center, 1 at boundary
kernel[y, x] = dist / radius
return kernel
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment