Implementing uint16 reading with tifffile
This commit is contained in:
@@ -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'
|
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user