Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version:
- "3.10"
- "3.11"
- "3.12"

Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [Unreleased]

### Changed
- **Python Version Requirement**: Dropped support for Python 3.10. The package now requires Python 3.11 or higher.
- Updated CI/CD workflow to test only Python 3.11 and 3.12 across all platforms (Ubuntu, Windows, macOS).
- Updated `pyproject.toml` to reflect `requires-python = ">=3.11"`.
- Simplified dependency lock file (`uv.lock`) by removing Python 3.10-specific markers.

## [v0.0.1-rc4] - 2025-04-11
**Release Candidate with MCCardDetector and Segmentation Support**

Expand Down
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ This package is designed to perform color correction on images using the Color C

## 📦 Installation

**Requirements:**
- Python 3.11 or higher

```bash
pip install color-correction
```
Expand Down Expand Up @@ -108,6 +111,53 @@ print(eval_result)

</details>

## 🛡️ Error Handling

The package provides clear, actionable error messages through custom exceptions:

```python
from color_correction import ColorCorrection
from color_correction.exceptions import (
UnsupportedModelError,
PatchesNotSetError,
ModelNotFittedError,
InvalidImageError,
)

try:
# Initialize with invalid model
cc = ColorCorrection(detection_model="invalid_model")
except UnsupportedModelError as e:
print(f"Error: {e}")
# Output: "Unsupported model: 'invalid_model'. Supported models are: yolov8, mcc"

try:
cc = ColorCorrection()
# Forgot to set input patches
cc.fit()
except PatchesNotSetError as e:
print(f"Error: {e}")
# Output: "Input patches must be set before this operation. Call set_input_patches() first."

try:
cc = ColorCorrection()
# Forgot to fit the model
corrected = cc.predict(image)
except ModelNotFittedError as e:
print(f"Error: {e}")
# Output: "Model must be fitted before prediction. Call fit() first."

try:
cc = ColorCorrection()
# Invalid image format
cc.set_input_patches(grayscale_image) # 2D array instead of 3D
except InvalidImageError as e:
print(f"Error: {e}")
# Output: "Invalid image: image must have 3 dimensions (H, W, C), got 2"
```

For more details, see the [Exception Reference](https://agfianf.github.io/color-correction/reference/exceptions/).

## 🔎 Reporting
```python
import cv2
Expand Down
148 changes: 148 additions & 0 deletions color_correction/constant/grid_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"""Configuration constants for color checker card grid layout.

This module defines the standard layout for the X-Rite ColorChecker Classic
24-patch card, which has a 6x4 grid (6 columns, 4 rows = 24 patches total).
"""

from typing import Final

# ============================================================================
# Grid Dimensions
# ============================================================================

GRID_ROWS: Final[int] = 4
"""Number of rows in the color checker grid."""

GRID_COLS: Final[int] = 6
"""Number of columns in the color checker grid."""

TOTAL_PATCHES: Final[int] = GRID_ROWS * GRID_COLS # 24
"""Total number of patches in the color checker card."""


# ============================================================================
# Grid Position Indices
# ============================================================================

ROW_END_INDICES: Final[frozenset[int]] = frozenset([5, 11, 17, 23])
"""Indices of patches at the end of each row (rightmost column)."""

ROW_START_INDICES: Final[frozenset[int]] = frozenset([0, 6, 12, 18])
"""Indices of patches at the start of each row (leftmost column)."""

COL_END_INDICES: Final[frozenset[int]] = frozenset(range(18, 24))
"""Indices of patches in the last row (bottom row)."""

COL_START_INDICES: Final[frozenset[int]] = frozenset(range(0, 6))
"""Indices of patches in the first row (top row)."""


# ============================================================================
# Neighbor Offsets
# ============================================================================

NEIGHBOR_RIGHT_OFFSET: Final[int] = 1
"""Index offset to get right neighbor patch."""

NEIGHBOR_LEFT_OFFSET: Final[int] = -1
"""Index offset to get left neighbor patch."""

NEIGHBOR_BOTTOM_OFFSET: Final[int] = GRID_COLS # 6
"""Index offset to get bottom neighbor patch (next row)."""

NEIGHBOR_TOP_OFFSET: Final[int] = -GRID_COLS # -6
"""Index offset to get top neighbor patch (previous row)."""


# ============================================================================
# Visualization Defaults
# ============================================================================

DEFAULT_GRID_FIGSIZE_WIDTH: Final[int] = 15
"""Default figure width for grid visualizations."""

DEFAULT_GRID_FIGSIZE_HEIGHT_PER_ROW: Final[int] = 4
"""Default figure height per row for grid visualizations."""


# ============================================================================
# Detection Defaults
# ============================================================================

MIN_PATCHES_REQUIRED: Final[int] = 24
"""Minimum number of patches required for valid detection."""

DEFAULT_CONFIDENCE_THRESHOLD: Final[float] = 0.25
"""Default confidence threshold for card detection."""

DEFAULT_IOU_THRESHOLD: Final[float] = 0.7
"""Default Intersection over Union threshold for NMS."""


# ============================================================================
# Helper Functions
# ============================================================================


def is_row_end(index: int) -> bool:
"""Check if patch index is at row end (rightmost column).

Parameters
----------
index : int
Patch index (0-23)

Returns
-------
bool
True if patch is at row end
"""
return index in ROW_END_INDICES


def is_row_start(index: int) -> bool:
"""Check if patch index is at row start (leftmost column).

Parameters
----------
index : int
Patch index (0-23)

Returns
-------
bool
True if patch is at row start
"""
return index in ROW_START_INDICES


def get_row_number(index: int) -> int:
"""Get row number (0-3) for a given patch index.

Parameters
----------
index : int
Patch index (0-23)

Returns
-------
int
Row number (0 for top row, 3 for bottom row)
"""
return index // GRID_COLS


def get_col_number(index: int) -> int:
"""Get column number (0-5) for a given patch index.

Parameters
----------
index : int
Patch index (0-23)

Returns
-------
int
Column number (0 for leftmost, 5 for rightmost)
"""
return index % GRID_COLS
2 changes: 1 addition & 1 deletion color_correction/constant/methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
"affine_reg",
]

LiteralModelDetection = Literal["yolov8"]
LiteralModelDetection = Literal["yolov8", "mcc"]
Loading
Loading