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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ wheels/
tmp/
zzz/
color_correction/asset/images/
playground/
assets/cc-1.jpg
assets/cc-19.png
37 changes: 36 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,41 @@
# Changelog

## [v0.0.1b1] - 2025-02-03
## [v0.0.1b3] - 2025-02-06
**Add Analyzer Report and Bug Fixes**

### πŸš€ Features
- Added comprehensive reporting functionality for color correction results
- New `ColorCorrectionAnalyzer` class for benchmarking different correction methods
- HTML report generation with interactive sorting and PDF export
- Visual comparison of before/after color patches
- Detailed Ξ”E metrics for patches and full images
- Enhanced image processing utilities
- Added base64 image conversion support
- Improved color difference calculation with rounded metrics
- Added HTML report generation templates and styling
- Responsive design with Plus Jakarta Sans font
- Interactive table sorting
- PDF export functionality
- Detailed column descriptions

### πŸ“ Documentation
- Added new Analyzer section in README
- Example usage code for ColorCorrectionAnalyzer
- Sample benchmark output visualization
- Updated version to 0.0.1b3

### πŸ”§ Technical Changes
- Renamed benchmark class to report for better clarity
- Added new utility modules:
- formater.py for value formatting
- report_generator.py for HTML generation
- Added new constants and method definitions

## [v0.0.1b2] - 2025-02-05
Fix naming from `color-correction-asdfghjkl` to `color-correction`


## [v0.0.1b1] - 2025-02-04
**Enhanced Color Correction with Improved Documentation and Evaluation**

### ✨ Features
Expand Down
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,41 @@ print(eval_result)

</details>

## πŸ”Ž Reporting
```python
import cv2

from color_correction import ColorCorrectionAnalyzer

# input_image_path = "assets/cc-19.png"
input_image_path = "assets/cc-1.jpg"

report = ColorCorrectionAnalyzer(
list_correction_methods=[
("least_squares", {}),
("linear_reg", {}),
("affine_reg", {}),
("polynomial", {"degree": 2}),
("polynomial", {"degree": 3}),
# ("polynomial", {"degree": 4}),
# ("polynomial", {"degree": 5}),
],
list_detection_methods=[
("yolov8", {"detection_conf_th": 0.25}),
],
)
report.run(
input_image=cv2.imread(input_image_path),
reference_image=None,
output_dir="report-output",
)
```
<details>
<summary>Sample Report Output</summary>

![Sample Benchmark Output](assets/sample-benchmark.png)
</details>

## πŸ“ˆ Benefits
- **Consistency**: Ensure uniform color correction across multiple images.
- **Accuracy**: Leverage the color correction matrix for precise color adjustments.
Expand Down
Binary file added assets/sample-benchmark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions color_correction/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
__version__ = "0.0.1a1"
__version__ = "0.0.1b3"

# fmt: off
from .constant.color_checker import reference_color_d50_bgr as REFERENCE_COLOR_D50_BGR # noqa: N812, I001
from .constant.color_checker import reference_color_d50_rgb as REFERENCE_COLOR_D50_RGB # noqa: N812, I001
from .core.card_detection.det_yv8_onnx import YOLOv8CardDetector
from .schemas.det_yv8 import DetectionResult as YOLOv8DetectionResult
from .services.color_correction import ColorCorrection

from .services.correction_analyzer import ColorCorrectionAnalyzer
# fmt: on

__all__ = [
"__version__",
"REFERENCE_COLOR_D50_BGR",
"REFERENCE_COLOR_D50_RGB",
"ColorCorrection",
"ColorCorrectionAnalyzer",
"YOLOv8CardDetector",
"YOLOv8DetectionResult",
]
Empty file.
10 changes: 10 additions & 0 deletions color_correction/constant/methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from typing import Literal

LiteralModelCorrection = Literal[
"least_squares",
"polynomial",
"linear_reg",
"affine_reg",
]

LiteralModelDetection = Literal["yolov8"]
5 changes: 5 additions & 0 deletions color_correction/schemas/images.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import numpy as np
from numpy.typing import NDArray

ColorPatchType = NDArray[np.uint8]
ImageType = NDArray[np.uint8]
19 changes: 7 additions & 12 deletions color_correction/services/color_correction.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import os
from typing import Literal

import cv2
import numpy as np
from numpy.typing import NDArray

from color_correction.constant.color_checker import reference_color_d50_bgr
from color_correction.constant.methods import (
LiteralModelCorrection,
LiteralModelDetection,
)
from color_correction.core.card_detection.det_yv8_onnx import (
YOLOv8CardDetector,
)
from color_correction.core.correction import CorrectionModelFactory
from color_correction.processor.det_yv8 import DetectionProcessor
from color_correction.schemas.images import ColorPatchType, ImageType
from color_correction.utils.image_patch import (
create_patch_tiled_image,
visualize_patch_comparison,
Expand All @@ -20,15 +24,6 @@
create_image_grid_visualization,
)

ColorPatchType = NDArray[np.uint8]
ImageType = NDArray[np.uint8]
LiteralModelCorrection = Literal[
"least_squares",
"polynomial",
"linear_reg",
"affine_reg",
]


class ColorCorrection:
"""Color correction handler using color card detection and correction models.
Expand All @@ -51,7 +46,7 @@ class ColorCorrection:
Reference image containing color checker card.
If None, uses standard D50 values.
use_gpu : bool, default=True
Whether to use GPU for card detection.
Whether to use GPU for card detection. False will use CPU.
**kwargs : dict
Additional parameters for the correction model.

Expand All @@ -67,7 +62,7 @@ class ColorCorrection:

def __init__(
self,
detection_model: Literal["yolov8"] = "yolov8",
detection_model: LiteralModelDetection = "yolov8",
detection_conf_th: float = 0.25,
correction_model: LiteralModelCorrection = "least_squares",
reference_image: ImageType | None = None,
Expand Down
172 changes: 172 additions & 0 deletions color_correction/services/correction_analyzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import os

import numpy as np
import pandas as pd

from color_correction.constant.methods import (
LiteralModelCorrection,
LiteralModelDetection,
)
from color_correction.services.color_correction import ColorCorrection
from color_correction.utils.image_patch import (
visualize_patch_comparison,
)
from color_correction.utils.image_processing import calc_color_diff
from color_correction.utils.report_generator import ReportGenerator


class ColorCorrectionAnalyzer:
def __init__(
self,
list_correction_methods: list[tuple[LiteralModelCorrection, dict]],
list_detection_methods: list[tuple[LiteralModelDetection, dict]],
use_gpu: bool = True,
) -> None:
self.list_correction_methods = list_correction_methods
self.list_detection_methods = list_detection_methods
self.use_gpu = use_gpu
self.rg = ReportGenerator()

def _run_single_exp(
self,
idx: int,
input_image: np.ndarray,
det_method: LiteralModelDetection,
det_params: dict,
cc_method: LiteralModelCorrection,
cc_params: dict,
reference_image: np.ndarray | None = None,
) -> dict:
cc = ColorCorrection(
correction_model=cc_method,
detection_model=det_method,
detection_conf_th=det_params.get("detection_conf_th", 0.25),
use_gpu=self.use_gpu,
**cc_params,
)

if reference_image is not None:
cc.set_reference_image(reference_image)
cc.set_input_patches(input_image, debug=True)
cc.fit()
corrected_image = cc.predict(input_image=input_image)
eval_results = cc.calc_color_diff_patches()

before_comparison = visualize_patch_comparison(
ls_mean_in=cc.input_patches,
ls_mean_ref=cc.reference_patches,
)
after_comparison = visualize_patch_comparison(
ls_mean_in=cc.corrected_patches,
ls_mean_ref=cc.reference_patches,
)

dE_image = calc_color_diff( # noqa: N806
image1=input_image,
image2=corrected_image,
)

one_row = {
"Index": idx,
"Detection Method": det_method,
"Detection Parameters": det_params,
"Drawed Preprocessing Input": cc.input_debug_image,
"Drawed Preprocessing Reference": cc.reference_debug_image,
"Correction Method": cc_method,
"Correction Parameters": cc_params,
"Color Patches - Before": before_comparison,
"Color Patches - After": after_comparison,
"Input Image": input_image,
"Corrected Image": corrected_image,
"Patch Ξ”E (Before) - Min": eval_results["initial"]["min"],
"Patch Ξ”E (Before) - Max": eval_results["initial"]["max"],
"Patch Ξ”E (Before) - Mean": eval_results["initial"]["mean"],
"Patch Ξ”E (Before) - Std": eval_results["initial"]["std"],
"Patch Ξ”E (After) - Min": eval_results["corrected"]["min"],
"Patch Ξ”E (After) - Max": eval_results["corrected"]["max"],
"Patch Ξ”E (After) - Mean": eval_results["corrected"]["mean"],
"Patch Ξ”E (After) - Std": eval_results["corrected"]["std"],
"Image Ξ”E - Min": dE_image["min"],
"Image Ξ”E - Max": dE_image["max"],
"Image Ξ”E - Mean": dE_image["mean"],
"Image Ξ”E - Std": dE_image["std"],
}
return one_row

def run(
self,
input_image: np.ndarray,
output_dir: str = "benchmark_debug",
reference_image: np.ndarray | None = None,
) -> pd.DataFrame:
"""
Fungsi ini menjalankan benchmark untuk model color correction.
"""
ls_data = []
idx = 1
for det_method, det_params in self.list_detection_methods:
for cc_method, cc_params in self.list_correction_methods:
print(
f"Running benchmark for {cc_method} method with {cc_params}",
)
data = self._run_single_exp(
idx=idx,
input_image=input_image,
det_method=det_method,
det_params=det_params,
cc_method=cc_method,
cc_params=cc_params,
reference_image=reference_image,
)
idx += 1
ls_data.append(data)
df_results = pd.DataFrame(ls_data)

# Generate HTML report path
os.makedirs(output_dir, exist_ok=True)
html_report_path = os.path.join(output_dir, "report.html")
pickel_report_path = os.path.join(output_dir, "report.pkl")

# Report Generator -----------------------------------------------------
self.rg.generate_html_report(df=df_results, path_html=html_report_path)
self.rg.save_dataframe(df=df_results, filepath=pickel_report_path)

# Save CSV report, but without image data
df_results.drop(
columns=[
"Drawed Preprocessing Input",
"Drawed Preprocessing Reference",
"Color Patches - Before",
"Color Patches - After",
"Corrected Image",
"Input Image",
],
).to_csv(os.path.join(output_dir, "report_no_image.csv"), index=False)

print("DataFrame shape:", df_results.shape)
print("\nDataFrame columns:", df_results.columns.tolist())


if __name__ == "__main__":
# Pastikan path image sesuai dengan lokasi image Anda
input_image_path = "asset/images/cc-19.png"

benchmark = ColorCorrectionAnalyzer(
list_correction_methods=[
("least_squares", {}),
("linear_reg", {}),
("affine_reg", {}),
("polynomial", {"degree": 2}),
("polynomial", {"degree": 3}),
("polynomial", {"degree": 4}),
],
list_detection_methods=[
("yolov8", {"detection_conf_th": 0.25}),
],
)

benchmark.run(
input_image_path,
reference_image=None,
output_dir="benchmark_debug",
)
28 changes: 28 additions & 0 deletions color_correction/templates/base_report.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<style>
{styles}
</style>
<script>
{scripts}
</script>
</head>
<body>
<div class="report-container">
<div class="report-header">
<h1>Detection and Correction Report</h1>
<div class="timestamp">Generated on: {current_time}</div>
</div>
<div class="table-container">
{body_report}
</div>
{column_descriptions}
</div>
<button onclick="exportToPDF()" class="export-btn">Export to PDF</button>
</body>
</html>
Loading