Adding image_display widget
This commit is contained in:
@@ -12,16 +12,15 @@ from PySide6.QtWidgets import (
|
|||||||
QPushButton,
|
QPushButton,
|
||||||
QFileDialog,
|
QFileDialog,
|
||||||
QMessageBox,
|
QMessageBox,
|
||||||
QScrollArea,
|
|
||||||
)
|
)
|
||||||
from PySide6.QtGui import QPixmap, QImage
|
from PySide6.QtCore import Qt, QSettings
|
||||||
from PySide6.QtCore import Qt
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from src.database.db_manager import DatabaseManager
|
from src.database.db_manager import DatabaseManager
|
||||||
from src.utils.config_manager import ConfigManager
|
from src.utils.config_manager import ConfigManager
|
||||||
from src.utils.image import Image, ImageLoadError
|
from src.utils.image import Image, ImageLoadError
|
||||||
from src.utils.logger import get_logger
|
from src.utils.logger import get_logger
|
||||||
|
from src.gui.widgets import ImageDisplayWidget
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -68,20 +67,10 @@ class AnnotationTab(QWidget):
|
|||||||
display_group = QGroupBox("Image Display")
|
display_group = QGroupBox("Image Display")
|
||||||
display_layout = QVBoxLayout()
|
display_layout = QVBoxLayout()
|
||||||
|
|
||||||
# Scroll area for image
|
# Use the reusable ImageDisplayWidget
|
||||||
scroll_area = QScrollArea()
|
self.image_display_widget = ImageDisplayWidget()
|
||||||
scroll_area.setWidgetResizable(True)
|
self.image_display_widget.zoom_changed.connect(self._on_zoom_changed)
|
||||||
scroll_area.setMinimumHeight(400)
|
display_layout.addWidget(self.image_display_widget)
|
||||||
|
|
||||||
self.image_label = QLabel("No image loaded")
|
|
||||||
self.image_label.setAlignment(Qt.AlignCenter)
|
|
||||||
self.image_label.setStyleSheet(
|
|
||||||
"QLabel { background-color: #2b2b2b; color: #888; }"
|
|
||||||
)
|
|
||||||
self.image_label.setScaledContents(False)
|
|
||||||
|
|
||||||
scroll_area.setWidget(self.image_label)
|
|
||||||
display_layout.addWidget(scroll_area)
|
|
||||||
|
|
||||||
display_group.setLayout(display_layout)
|
display_group.setLayout(display_layout)
|
||||||
layout.addWidget(display_group)
|
layout.addWidget(display_group)
|
||||||
@@ -101,13 +90,26 @@ class AnnotationTab(QWidget):
|
|||||||
info_group.setLayout(info_layout)
|
info_group.setLayout(info_layout)
|
||||||
|
|
||||||
layout.addWidget(info_group)
|
layout.addWidget(info_group)
|
||||||
|
|
||||||
|
# Zoom controls info
|
||||||
|
zoom_info = QLabel("Zoom: Mouse wheel or +/- keys to zoom in/out")
|
||||||
|
zoom_info.setStyleSheet("QLabel { color: #888; font-style: italic; }")
|
||||||
|
layout.addWidget(zoom_info)
|
||||||
|
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
def _load_image(self):
|
def _load_image(self):
|
||||||
"""Load and display an image file."""
|
"""Load and display an image file."""
|
||||||
# Get image repository path or use home directory
|
# Get last opened directory from QSettings
|
||||||
repo_path = self.config_manager.get_image_repository_path()
|
settings = QSettings("microscopy_app", "object_detection")
|
||||||
start_dir = repo_path if repo_path else str(Path.home())
|
last_dir = settings.value("annotation_tab/last_directory", None)
|
||||||
|
|
||||||
|
# Fallback to image repository path or home directory
|
||||||
|
if last_dir and Path(last_dir).exists():
|
||||||
|
start_dir = last_dir
|
||||||
|
else:
|
||||||
|
repo_path = self.config_manager.get_image_repository_path()
|
||||||
|
start_dir = repo_path if repo_path else str(Path.home())
|
||||||
|
|
||||||
# Open file dialog
|
# Open file dialog
|
||||||
file_path, _ = QFileDialog.getOpenFileName(
|
file_path, _ = QFileDialog.getOpenFileName(
|
||||||
@@ -125,18 +127,16 @@ class AnnotationTab(QWidget):
|
|||||||
self.current_image = Image(file_path)
|
self.current_image = Image(file_path)
|
||||||
self.current_image_path = file_path
|
self.current_image_path = file_path
|
||||||
|
|
||||||
# Update info label
|
# Store the directory for next time
|
||||||
info_text = (
|
settings.setValue(
|
||||||
f"File: {Path(file_path).name}\n"
|
"annotation_tab/last_directory", str(Path(file_path).parent)
|
||||||
f"Size: {self.current_image.width}x{self.current_image.height} pixels\n"
|
|
||||||
f"Channels: {self.current_image.channels}\n"
|
|
||||||
f"Format: {self.current_image.format.upper()}\n"
|
|
||||||
f"File size: {self.current_image.size_mb:.2f} MB"
|
|
||||||
)
|
)
|
||||||
self.image_info_label.setText(info_text)
|
|
||||||
|
|
||||||
# Convert to QPixmap and display
|
# Display image using the ImageDisplayWidget
|
||||||
self._display_image()
|
self.image_display_widget.load_image(self.current_image)
|
||||||
|
|
||||||
|
# Update info label
|
||||||
|
self._update_image_info()
|
||||||
|
|
||||||
logger.info(f"Loaded image: {file_path}")
|
logger.info(f"Loaded image: {file_path}")
|
||||||
|
|
||||||
@@ -149,59 +149,27 @@ class AnnotationTab(QWidget):
|
|||||||
logger.error(f"Unexpected error loading image: {e}")
|
logger.error(f"Unexpected error loading image: {e}")
|
||||||
QMessageBox.critical(self, "Error", f"Unexpected error:\n{str(e)}")
|
QMessageBox.critical(self, "Error", f"Unexpected error:\n{str(e)}")
|
||||||
|
|
||||||
def _display_image(self):
|
def _update_image_info(self):
|
||||||
"""Display the current image in the image label."""
|
"""Update the image info label with current image details."""
|
||||||
if self.current_image is None:
|
if self.current_image is None:
|
||||||
|
self.image_info_label.setText("No image loaded")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
zoom_percentage = self.image_display_widget.get_zoom_percentage()
|
||||||
# Get RGB image data
|
info_text = (
|
||||||
rgb_data = self.current_image.get_rgb()
|
f"File: {Path(self.current_image_path).name}\n"
|
||||||
|
f"Size: {self.current_image.width}x{self.current_image.height} pixels\n"
|
||||||
|
f"Channels: {self.current_image.channels}\n"
|
||||||
|
f"Data type: {self.current_image.dtype}\n"
|
||||||
|
f"Format: {self.current_image.format.upper()}\n"
|
||||||
|
f"File size: {self.current_image.size_mb:.2f} MB\n"
|
||||||
|
f"Zoom: {zoom_percentage}%"
|
||||||
|
)
|
||||||
|
self.image_info_label.setText(info_text)
|
||||||
|
|
||||||
# Convert numpy array to QImage
|
def _on_zoom_changed(self, zoom_scale: float):
|
||||||
height, width, channels = rgb_data.shape
|
"""Handle zoom level changes from the image display widget."""
|
||||||
bytes_per_line = channels * width
|
self._update_image_info()
|
||||||
|
|
||||||
if channels == 3:
|
|
||||||
qimage = QImage(
|
|
||||||
rgb_data.data,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
bytes_per_line,
|
|
||||||
QImage.Format_RGB888,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Grayscale
|
|
||||||
qimage = QImage(
|
|
||||||
rgb_data.data,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
bytes_per_line,
|
|
||||||
QImage.Format_Grayscale8,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Convert to pixmap
|
|
||||||
pixmap = QPixmap.fromImage(qimage)
|
|
||||||
|
|
||||||
# Scale to fit display (max 800px width or height)
|
|
||||||
max_size = 800
|
|
||||||
if pixmap.width() > max_size or pixmap.height() > max_size:
|
|
||||||
pixmap = pixmap.scaled(
|
|
||||||
max_size,
|
|
||||||
max_size,
|
|
||||||
Qt.KeepAspectRatio,
|
|
||||||
Qt.SmoothTransformation,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Display in label
|
|
||||||
self.image_label.setPixmap(pixmap)
|
|
||||||
self.image_label.setScaledContents(False)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error displaying image: {e}")
|
|
||||||
QMessageBox.warning(
|
|
||||||
self, "Display Error", f"Failed to display image:\n{str(e)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
"""Refresh the tab."""
|
"""Refresh the tab."""
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
"""GUI widgets for the microscopy object detection application."""
|
||||||
|
|
||||||
|
from src.gui.widgets.image_display_widget import ImageDisplayWidget
|
||||||
|
|
||||||
|
__all__ = ["ImageDisplayWidget"]
|
||||||
|
|||||||
282
src/gui/widgets/image_display_widget.py
Normal file
282
src/gui/widgets/image_display_widget.py
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
"""
|
||||||
|
Image display widget with zoom functionality for the microscopy object detection application.
|
||||||
|
Reusable widget for displaying images with zoom controls.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QScrollArea
|
||||||
|
from PySide6.QtGui import QPixmap, QImage, QKeyEvent
|
||||||
|
from PySide6.QtCore import Qt, QEvent, Signal
|
||||||
|
from pathlib import Path
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from src.utils.image import Image, ImageLoadError
|
||||||
|
from src.utils.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ImageDisplayWidget(QWidget):
|
||||||
|
"""
|
||||||
|
Reusable widget for displaying images with zoom functionality.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Display images from Image objects
|
||||||
|
- Zoom in/out with mouse wheel
|
||||||
|
- Zoom in/out with +/- keyboard keys
|
||||||
|
- Reset zoom with Ctrl+0
|
||||||
|
- Scroll area for large images
|
||||||
|
|
||||||
|
Signals:
|
||||||
|
zoom_changed: Emitted when zoom level changes (float zoom_scale)
|
||||||
|
"""
|
||||||
|
|
||||||
|
zoom_changed = Signal(float) # Emitted when zoom level changes
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
"""
|
||||||
|
Initialize the image display widget.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent: Parent widget
|
||||||
|
"""
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.current_image = None
|
||||||
|
self.original_pixmap = None # Store original pixmap for zoom
|
||||||
|
self.zoom_scale = 1.0 # Current zoom scale
|
||||||
|
self.zoom_min = 0.1 # Minimum zoom (10%)
|
||||||
|
self.zoom_max = 10.0 # Maximum zoom (1000%)
|
||||||
|
self.zoom_step = 0.1 # Zoom step for +/- keys
|
||||||
|
self.zoom_wheel_step = 0.15 # Zoom step for mouse wheel
|
||||||
|
|
||||||
|
self._setup_ui()
|
||||||
|
|
||||||
|
def _setup_ui(self):
|
||||||
|
"""Setup user interface."""
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
# Scroll area for image
|
||||||
|
self.scroll_area = QScrollArea()
|
||||||
|
self.scroll_area.setWidgetResizable(True)
|
||||||
|
self.scroll_area.setMinimumHeight(400)
|
||||||
|
|
||||||
|
self.image_label = QLabel("No image loaded")
|
||||||
|
self.image_label.setAlignment(Qt.AlignCenter)
|
||||||
|
self.image_label.setStyleSheet(
|
||||||
|
"QLabel { background-color: #2b2b2b; color: #888; }"
|
||||||
|
)
|
||||||
|
self.image_label.setScaledContents(False)
|
||||||
|
|
||||||
|
# Enable mouse tracking for wheel events
|
||||||
|
self.image_label.setMouseTracking(True)
|
||||||
|
self.scroll_area.setWidget(self.image_label)
|
||||||
|
|
||||||
|
# Install event filter to capture wheel events on scroll area
|
||||||
|
self.scroll_area.viewport().installEventFilter(self)
|
||||||
|
|
||||||
|
layout.addWidget(self.scroll_area)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
# Set focus policy to receive keyboard events
|
||||||
|
self.setFocusPolicy(Qt.StrongFocus)
|
||||||
|
|
||||||
|
def load_image(self, image: Image):
|
||||||
|
"""
|
||||||
|
Load and display an image.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image: Image object to display
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ImageLoadError: If image cannot be displayed
|
||||||
|
"""
|
||||||
|
self.current_image = image
|
||||||
|
|
||||||
|
# Reset zoom when loading new image
|
||||||
|
self.zoom_scale = 1.0
|
||||||
|
|
||||||
|
# Convert to QPixmap and display
|
||||||
|
self._display_image()
|
||||||
|
|
||||||
|
logger.debug(f"Loaded image into display widget: {image.width}x{image.height}")
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""Clear the displayed image."""
|
||||||
|
self.current_image = None
|
||||||
|
self.original_pixmap = None
|
||||||
|
self.zoom_scale = 1.0
|
||||||
|
self.image_label.setText("No image loaded")
|
||||||
|
self.image_label.setPixmap(QPixmap())
|
||||||
|
logger.debug("Cleared image display")
|
||||||
|
|
||||||
|
def _display_image(self):
|
||||||
|
"""Display the current image in the image label."""
|
||||||
|
if self.current_image is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get RGB image data
|
||||||
|
if self.current_image.channels == 3:
|
||||||
|
image_data = self.current_image.get_rgb()
|
||||||
|
height, width, channels = image_data.shape
|
||||||
|
else:
|
||||||
|
image_data = self.current_image.get_grayscale()
|
||||||
|
height, width = image_data.shape
|
||||||
|
channels = 1
|
||||||
|
|
||||||
|
# Ensure data is contiguous for proper QImage display
|
||||||
|
image_data = np.ascontiguousarray(image_data)
|
||||||
|
|
||||||
|
# Use actual stride from numpy array for correct display
|
||||||
|
bytes_per_line = image_data.strides[0]
|
||||||
|
|
||||||
|
qimage = QImage(
|
||||||
|
image_data.data,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
bytes_per_line,
|
||||||
|
self.current_image.qtimage_format,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Convert to pixmap
|
||||||
|
pixmap = QPixmap.fromImage(qimage)
|
||||||
|
|
||||||
|
# Store original pixmap for zooming
|
||||||
|
self.original_pixmap = pixmap
|
||||||
|
|
||||||
|
# Apply zoom and display
|
||||||
|
self._apply_zoom()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error displaying image: {e}")
|
||||||
|
raise ImageLoadError(f"Failed to display image: {str(e)}")
|
||||||
|
|
||||||
|
def _apply_zoom(self):
|
||||||
|
"""Apply current zoom level to the displayed image."""
|
||||||
|
if self.original_pixmap is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Calculate scaled size
|
||||||
|
scaled_width = int(self.original_pixmap.width() * self.zoom_scale)
|
||||||
|
scaled_height = int(self.original_pixmap.height() * self.zoom_scale)
|
||||||
|
|
||||||
|
# Scale pixmap
|
||||||
|
scaled_pixmap = self.original_pixmap.scaled(
|
||||||
|
scaled_width,
|
||||||
|
scaled_height,
|
||||||
|
Qt.KeepAspectRatio,
|
||||||
|
(
|
||||||
|
Qt.SmoothTransformation
|
||||||
|
if self.zoom_scale >= 1.0
|
||||||
|
else Qt.FastTransformation
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Display in label
|
||||||
|
self.image_label.setPixmap(scaled_pixmap)
|
||||||
|
self.image_label.setScaledContents(False)
|
||||||
|
self.image_label.adjustSize()
|
||||||
|
|
||||||
|
# Emit zoom changed signal
|
||||||
|
self.zoom_changed.emit(self.zoom_scale)
|
||||||
|
|
||||||
|
def zoom_in(self):
|
||||||
|
"""Zoom in on the image."""
|
||||||
|
if self.original_pixmap is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
new_scale = self.zoom_scale + self.zoom_step
|
||||||
|
if new_scale <= self.zoom_max:
|
||||||
|
self.zoom_scale = new_scale
|
||||||
|
self._apply_zoom()
|
||||||
|
logger.debug(f"Zoomed in to {int(self.zoom_scale * 100)}%")
|
||||||
|
|
||||||
|
def zoom_out(self):
|
||||||
|
"""Zoom out from the image."""
|
||||||
|
if self.original_pixmap is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
new_scale = self.zoom_scale - self.zoom_step
|
||||||
|
if new_scale >= self.zoom_min:
|
||||||
|
self.zoom_scale = new_scale
|
||||||
|
self._apply_zoom()
|
||||||
|
logger.debug(f"Zoomed out to {int(self.zoom_scale * 100)}%")
|
||||||
|
|
||||||
|
def reset_zoom(self):
|
||||||
|
"""Reset zoom to 100%."""
|
||||||
|
if self.original_pixmap is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.zoom_scale = 1.0
|
||||||
|
self._apply_zoom()
|
||||||
|
logger.debug("Reset zoom to 100%")
|
||||||
|
|
||||||
|
def set_zoom(self, scale: float):
|
||||||
|
"""
|
||||||
|
Set zoom to a specific scale.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
scale: Zoom scale (1.0 = 100%)
|
||||||
|
"""
|
||||||
|
if self.original_pixmap is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Clamp to min/max
|
||||||
|
scale = max(self.zoom_min, min(self.zoom_max, scale))
|
||||||
|
|
||||||
|
self.zoom_scale = scale
|
||||||
|
self._apply_zoom()
|
||||||
|
logger.debug(f"Set zoom to {int(self.zoom_scale * 100)}%")
|
||||||
|
|
||||||
|
def get_zoom_percentage(self) -> int:
|
||||||
|
"""
|
||||||
|
Get current zoom level as percentage.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Zoom level as integer percentage (e.g., 100 for 100%)
|
||||||
|
"""
|
||||||
|
return int(self.zoom_scale * 100)
|
||||||
|
|
||||||
|
def keyPressEvent(self, event: QKeyEvent):
|
||||||
|
"""Handle keyboard events for zooming."""
|
||||||
|
if event.key() in (Qt.Key_Plus, Qt.Key_Equal):
|
||||||
|
# + or = key (= is the unshifted + on many keyboards)
|
||||||
|
self.zoom_in()
|
||||||
|
event.accept()
|
||||||
|
elif event.key() == Qt.Key_Minus:
|
||||||
|
# - key
|
||||||
|
self.zoom_out()
|
||||||
|
event.accept()
|
||||||
|
elif event.key() == Qt.Key_0 and event.modifiers() == Qt.ControlModifier:
|
||||||
|
# Ctrl+0 to reset zoom
|
||||||
|
self.reset_zoom()
|
||||||
|
event.accept()
|
||||||
|
else:
|
||||||
|
super().keyPressEvent(event)
|
||||||
|
|
||||||
|
def eventFilter(self, obj, event: QEvent) -> bool:
|
||||||
|
"""Event filter to capture wheel events for zooming."""
|
||||||
|
if event.type() == QEvent.Wheel:
|
||||||
|
wheel_event = event
|
||||||
|
if self.original_pixmap is not None:
|
||||||
|
# Get wheel angle delta
|
||||||
|
delta = wheel_event.angleDelta().y()
|
||||||
|
|
||||||
|
# Zoom in/out based on wheel direction
|
||||||
|
if delta > 0:
|
||||||
|
# Scroll up = zoom in
|
||||||
|
new_scale = self.zoom_scale + self.zoom_wheel_step
|
||||||
|
if new_scale <= self.zoom_max:
|
||||||
|
self.zoom_scale = new_scale
|
||||||
|
self._apply_zoom()
|
||||||
|
else:
|
||||||
|
# Scroll down = zoom out
|
||||||
|
new_scale = self.zoom_scale - self.zoom_wheel_step
|
||||||
|
if new_scale >= self.zoom_min:
|
||||||
|
self.zoom_scale = new_scale
|
||||||
|
self._apply_zoom()
|
||||||
|
|
||||||
|
return True # Event handled
|
||||||
|
|
||||||
|
return super().eventFilter(obj, event)
|
||||||
@@ -11,6 +11,8 @@ from PIL import Image as PILImage
|
|||||||
from src.utils.logger import get_logger
|
from src.utils.logger import get_logger
|
||||||
from src.utils.file_utils import validate_file_path, is_image_file
|
from src.utils.file_utils import validate_file_path, is_image_file
|
||||||
|
|
||||||
|
from PySide6.QtGui import QImage
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -58,6 +60,7 @@ class Image:
|
|||||||
self._channels: int = 0
|
self._channels: int = 0
|
||||||
self._format: str = ""
|
self._format: str = ""
|
||||||
self._size_bytes: int = 0
|
self._size_bytes: int = 0
|
||||||
|
self._dtype: Optional[np.dtype] = None
|
||||||
|
|
||||||
# Load the image
|
# Load the image
|
||||||
self._load()
|
self._load()
|
||||||
@@ -93,6 +96,7 @@ class Image:
|
|||||||
self._channels = self._data.shape[2] if len(self._data.shape) == 3 else 1
|
self._channels = self._data.shape[2] if len(self._data.shape) == 3 else 1
|
||||||
self._format = self.path.suffix.lower().lstrip(".")
|
self._format = self.path.suffix.lower().lstrip(".")
|
||||||
self._size_bytes = self.path.stat().st_size
|
self._size_bytes = self.path.stat().st_size
|
||||||
|
self._dtype = self._data.dtype
|
||||||
|
|
||||||
# Load PIL version for compatibility (convert BGR to RGB)
|
# Load PIL version for compatibility (convert BGR to RGB)
|
||||||
if self._channels == 3:
|
if self._channels == 3:
|
||||||
@@ -157,6 +161,7 @@ class Image:
|
|||||||
Returns:
|
Returns:
|
||||||
Tuple of (height, width, channels)
|
Tuple of (height, width, channels)
|
||||||
"""
|
"""
|
||||||
|
print("shape", self._height, self._width, self._channels)
|
||||||
return (self._height, self._width, self._channels)
|
return (self._height, self._width, self._channels)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -179,6 +184,33 @@ class Image:
|
|||||||
"""Get file size in megabytes."""
|
"""Get file size in megabytes."""
|
||||||
return self._size_bytes / (1024 * 1024)
|
return self._size_bytes / (1024 * 1024)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dtype(self) -> np.dtype:
|
||||||
|
"""Get the data type of the image array."""
|
||||||
|
if self._dtype is None:
|
||||||
|
raise ImageLoadError("Image dtype not available")
|
||||||
|
return self._dtype
|
||||||
|
|
||||||
|
@property
|
||||||
|
def qtimage_format(self) -> QImage.Format:
|
||||||
|
"""
|
||||||
|
Get the appropriate QImage format for the image.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
QImage.Format enum value
|
||||||
|
"""
|
||||||
|
if self._channels == 3:
|
||||||
|
return QImage.Format_RGB888
|
||||||
|
elif self._channels == 4:
|
||||||
|
return QImage.Format_RGBA8888
|
||||||
|
elif self._channels == 1:
|
||||||
|
if self._dtype == np.uint16:
|
||||||
|
return QImage.Format_Grayscale16
|
||||||
|
else:
|
||||||
|
return QImage.Format_Grayscale8
|
||||||
|
else:
|
||||||
|
raise ImageLoadError(f"Unsupported number of channels: {self._channels}")
|
||||||
|
|
||||||
def get_rgb(self) -> np.ndarray:
|
def get_rgb(self) -> np.ndarray:
|
||||||
"""
|
"""
|
||||||
Get image data as RGB numpy array.
|
Get image data as RGB numpy array.
|
||||||
|
|||||||
Reference in New Issue
Block a user