Implementing uint16 reading with tifffile

This commit is contained in:
2025-12-16 23:02:45 +02:00
parent e5036c10cf
commit e364d06217
4 changed files with 35 additions and 62 deletions

View File

@@ -1,57 +0,0 @@
database:
path: data/detections.db
image_repository:
base_path: ''
allowed_extensions:
- .jpg
- .jpeg
- .png
- .tif
- .tiff
- .bmp
models:
default_base_model: yolov8s-seg.pt
models_directory: data/models
base_model_choices:
- yolov8s-seg.pt
- yolo11s-seg.pt
training:
default_epochs: 100
default_batch_size: 16
default_imgsz: 1024
default_patience: 50
default_lr0: 0.01
two_stage:
enabled: false
stage1:
epochs: 20
lr0: 0.0005
patience: 10
freeze: 10
stage2:
epochs: 150
lr0: 0.0003
patience: 30
last_dataset_yaml: /home/martin/code/object_detection/data/datasets/data.yaml
last_dataset_dir: /home/martin/code/object_detection/data/datasets
detection:
default_confidence: 0.25
default_iou: 0.45
max_batch_size: 100
visualization:
bbox_colors:
organelle: '#FF6B6B'
membrane_branch: '#4ECDC4'
default: '#00FF00'
bbox_thickness: 2
font_size: 12
export:
formats:
- csv
- json
- excel
default_format: csv
logging:
level: INFO
file: logs/app.log
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'

View File

@@ -1303,6 +1303,14 @@ class TrainingTab(QWidget):
sample_image = self._find_first_image(images_dir) sample_image = self._find_first_image(images_dir)
if not sample_image: if not sample_image:
return False return False
# Do not force an RGB cache for TIFF datasets.
# We handle grayscale/16-bit TIFFs via runtime Ultralytics patches that:
# - load TIFFs with `tifffile`
# - replicate grayscale to 3 channels without quantization
# - normalize uint16 correctly during training
if sample_image.suffix.lower() in {".tif", ".tiff"}:
return False
try: try:
img = Image(sample_image) img = Image(sample_image)
return img.pil_image.mode.upper() != "RGB" return img.pil_image.mode.upper() != "RGB"

View File

@@ -1,9 +1,13 @@
""" """YOLO model wrapper for the microscopy object detection application.
YOLO model wrapper for the microscopy object detection application.
Provides a clean interface to YOLOv8 for training, validation, and inference. Notes on 16-bit TIFF support:
- Ultralytics training defaults assume 8-bit images and normalize by dividing by 255.
- This project can patch Ultralytics at runtime to decode TIFFs via `tifffile` and
normalize `uint16` correctly.
See [`apply_ultralytics_16bit_tiff_patches()`](src/utils/ultralytics_16bit_patch.py:1).
""" """
from ultralytics import YOLO
from pathlib import Path from pathlib import Path
from typing import Optional, List, Dict, Callable, Any from typing import Optional, List, Dict, Callable, Any
import torch import torch
@@ -11,6 +15,7 @@ import tempfile
import os import os
from src.utils.image import Image from src.utils.image import Image
from src.utils.logger import get_logger from src.utils.logger import get_logger
from src.utils.ultralytics_16bit_patch import apply_ultralytics_16bit_tiff_patches
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -31,6 +36,9 @@ class YOLOWrapper:
self.device = "cuda" if torch.cuda.is_available() else "cpu" self.device = "cuda" if torch.cuda.is_available() else "cpu"
logger.info(f"YOLOWrapper initialized with device: {self.device}") logger.info(f"YOLOWrapper initialized with device: {self.device}")
# Apply Ultralytics runtime patches early (before first import/instantiation of YOLO datasets/trainers).
apply_ultralytics_16bit_tiff_patches()
def load_model(self) -> bool: def load_model(self) -> bool:
""" """
Load YOLO model from path. Load YOLO model from path.
@@ -40,6 +48,9 @@ class YOLOWrapper:
""" """
try: try:
logger.info(f"Loading YOLO model from {self.model_path}") logger.info(f"Loading YOLO model from {self.model_path}")
# Import YOLO lazily to ensure runtime patches are applied first.
from ultralytics import YOLO
self.model = YOLO(self.model_path) self.model = YOLO(self.model_path)
self.model.to(self.device) self.model.to(self.device)
logger.info("Model loaded successfully") logger.info("Model loaded successfully")
@@ -89,6 +100,16 @@ class YOLOWrapper:
f"Data: {data_yaml}, Epochs: {epochs}, Batch: {batch}, ImgSz: {imgsz}" f"Data: {data_yaml}, Epochs: {epochs}, Batch: {batch}, ImgSz: {imgsz}"
) )
# Defaults for 16-bit safety: disable augmentations that force uint8 and HSV ops that assume 0..255.
# Users can override by passing explicit kwargs.
kwargs.setdefault("mosaic", 0.0)
kwargs.setdefault("mixup", 0.0)
kwargs.setdefault("cutmix", 0.0)
kwargs.setdefault("copy_paste", 0.0)
kwargs.setdefault("hsv_h", 0.0)
kwargs.setdefault("hsv_s", 0.0)
kwargs.setdefault("hsv_v", 0.0)
# Train the model # Train the model
results = self.model.train( results = self.model.train(
data=data_yaml, data=data_yaml,

View File

@@ -313,7 +313,8 @@ class Image:
"""String representation of the Image object.""" """String representation of the Image object."""
return ( return (
f"Image(path='{self.path.name}', " f"Image(path='{self.path.name}', "
f"shape=({self._width}x{self._height}x{self._channels}), " # Display as HxWxC to match the conventional NumPy shape semantics.
f"shape=({self._height}x{self._width}x{self._channels}), "
f"format={self._format}, " f"format={self._format}, "
f"size={self.size_mb:.2f}MB)" f"size={self.size_mb:.2f}MB)"
) )