Skip to content

Commit 08b60e2

Browse files
authored
Merge pull request #7 from agfianf/feat/benchmark
Color Correction Reporting System
2 parents 7ce7d41 + 103ed4b commit 08b60e2

21 files changed

+844
-22
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,6 @@ wheels/
2121
tmp/
2222
zzz/
2323
color_correction/asset/images/
24+
playground/
25+
assets/cc-1.jpg
26+
assets/cc-19.png

CHANGELOG.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,41 @@
11
# Changelog
22

3-
## [v0.0.1b1] - 2025-02-03
3+
## [v0.0.1b3] - 2025-02-06
4+
**Add Analyzer Report and Bug Fixes**
5+
6+
### 🚀 Features
7+
- Added comprehensive reporting functionality for color correction results
8+
- New `ColorCorrectionAnalyzer` class for benchmarking different correction methods
9+
- HTML report generation with interactive sorting and PDF export
10+
- Visual comparison of before/after color patches
11+
- Detailed ΔE metrics for patches and full images
12+
- Enhanced image processing utilities
13+
- Added base64 image conversion support
14+
- Improved color difference calculation with rounded metrics
15+
- Added HTML report generation templates and styling
16+
- Responsive design with Plus Jakarta Sans font
17+
- Interactive table sorting
18+
- PDF export functionality
19+
- Detailed column descriptions
20+
21+
### 📝 Documentation
22+
- Added new Analyzer section in README
23+
- Example usage code for ColorCorrectionAnalyzer
24+
- Sample benchmark output visualization
25+
- Updated version to 0.0.1b3
26+
27+
### 🔧 Technical Changes
28+
- Renamed benchmark class to report for better clarity
29+
- Added new utility modules:
30+
- formater.py for value formatting
31+
- report_generator.py for HTML generation
32+
- Added new constants and method definitions
33+
34+
## [v0.0.1b2] - 2025-02-05
35+
Fix naming from `color-correction-asdfghjkl` to `color-correction`
36+
37+
38+
## [v0.0.1b1] - 2025-02-04
439
**Enhanced Color Correction with Improved Documentation and Evaluation**
540

641
### ✨ Features

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,41 @@ print(eval_result)
9797

9898
</details>
9999

100+
## 🔎 Reporting
101+
```python
102+
import cv2
103+
104+
from color_correction import ColorCorrectionAnalyzer
105+
106+
# input_image_path = "assets/cc-19.png"
107+
input_image_path = "assets/cc-1.jpg"
108+
109+
report = ColorCorrectionAnalyzer(
110+
list_correction_methods=[
111+
("least_squares", {}),
112+
("linear_reg", {}),
113+
("affine_reg", {}),
114+
("polynomial", {"degree": 2}),
115+
("polynomial", {"degree": 3}),
116+
# ("polynomial", {"degree": 4}),
117+
# ("polynomial", {"degree": 5}),
118+
],
119+
list_detection_methods=[
120+
("yolov8", {"detection_conf_th": 0.25}),
121+
],
122+
)
123+
report.run(
124+
input_image=cv2.imread(input_image_path),
125+
reference_image=None,
126+
output_dir="report-output",
127+
)
128+
```
129+
<details>
130+
<summary>Sample Report Output</summary>
131+
132+
![Sample Benchmark Output](assets/sample-benchmark.png)
133+
</details>
134+
100135
## 📈 Benefits
101136
- **Consistency**: Ensure uniform color correction across multiple images.
102137
- **Accuracy**: Leverage the color correction matrix for precise color adjustments.

assets/sample-benchmark.png

430 KB
Loading

color_correction/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
1-
__version__ = "0.0.1a1"
1+
__version__ = "0.0.1b3"
22

33
# fmt: off
44
from .constant.color_checker import reference_color_d50_bgr as REFERENCE_COLOR_D50_BGR # noqa: N812, I001
55
from .constant.color_checker import reference_color_d50_rgb as REFERENCE_COLOR_D50_RGB # noqa: N812, I001
66
from .core.card_detection.det_yv8_onnx import YOLOv8CardDetector
77
from .schemas.det_yv8 import DetectionResult as YOLOv8DetectionResult
88
from .services.color_correction import ColorCorrection
9-
9+
from .services.correction_analyzer import ColorCorrectionAnalyzer
1010
# fmt: on
1111

1212
__all__ = [
1313
"__version__",
1414
"REFERENCE_COLOR_D50_BGR",
1515
"REFERENCE_COLOR_D50_RGB",
1616
"ColorCorrection",
17+
"ColorCorrectionAnalyzer",
1718
"YOLOv8CardDetector",
1819
"YOLOv8DetectionResult",
1920
]

color_correction/constant/__init__.py

Whitespace-only changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from typing import Literal
2+
3+
LiteralModelCorrection = Literal[
4+
"least_squares",
5+
"polynomial",
6+
"linear_reg",
7+
"affine_reg",
8+
]
9+
10+
LiteralModelDetection = Literal["yolov8"]

color_correction/schemas/images.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import numpy as np
2+
from numpy.typing import NDArray
3+
4+
ColorPatchType = NDArray[np.uint8]
5+
ImageType = NDArray[np.uint8]

color_correction/services/color_correction.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import os
2-
from typing import Literal
32

43
import cv2
54
import numpy as np
65
from numpy.typing import NDArray
76

87
from color_correction.constant.color_checker import reference_color_d50_bgr
8+
from color_correction.constant.methods import (
9+
LiteralModelCorrection,
10+
LiteralModelDetection,
11+
)
912
from color_correction.core.card_detection.det_yv8_onnx import (
1013
YOLOv8CardDetector,
1114
)
1215
from color_correction.core.correction import CorrectionModelFactory
1316
from color_correction.processor.det_yv8 import DetectionProcessor
17+
from color_correction.schemas.images import ColorPatchType, ImageType
1418
from color_correction.utils.image_patch import (
1519
create_patch_tiled_image,
1620
visualize_patch_comparison,
@@ -20,15 +24,6 @@
2024
create_image_grid_visualization,
2125
)
2226

23-
ColorPatchType = NDArray[np.uint8]
24-
ImageType = NDArray[np.uint8]
25-
LiteralModelCorrection = Literal[
26-
"least_squares",
27-
"polynomial",
28-
"linear_reg",
29-
"affine_reg",
30-
]
31-
3227

3328
class ColorCorrection:
3429
"""Color correction handler using color card detection and correction models.
@@ -51,7 +46,7 @@ class ColorCorrection:
5146
Reference image containing color checker card.
5247
If None, uses standard D50 values.
5348
use_gpu : bool, default=True
54-
Whether to use GPU for card detection.
49+
Whether to use GPU for card detection. False will use CPU.
5550
**kwargs : dict
5651
Additional parameters for the correction model.
5752
@@ -67,7 +62,7 @@ class ColorCorrection:
6762

6863
def __init__(
6964
self,
70-
detection_model: Literal["yolov8"] = "yolov8",
65+
detection_model: LiteralModelDetection = "yolov8",
7166
detection_conf_th: float = 0.25,
7267
correction_model: LiteralModelCorrection = "least_squares",
7368
reference_image: ImageType | None = None,
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import os
2+
3+
import numpy as np
4+
import pandas as pd
5+
6+
from color_correction.constant.methods import (
7+
LiteralModelCorrection,
8+
LiteralModelDetection,
9+
)
10+
from color_correction.services.color_correction import ColorCorrection
11+
from color_correction.utils.image_patch import (
12+
visualize_patch_comparison,
13+
)
14+
from color_correction.utils.image_processing import calc_color_diff
15+
from color_correction.utils.report_generator import ReportGenerator
16+
17+
18+
class ColorCorrectionAnalyzer:
19+
def __init__(
20+
self,
21+
list_correction_methods: list[tuple[LiteralModelCorrection, dict]],
22+
list_detection_methods: list[tuple[LiteralModelDetection, dict]],
23+
use_gpu: bool = True,
24+
) -> None:
25+
self.list_correction_methods = list_correction_methods
26+
self.list_detection_methods = list_detection_methods
27+
self.use_gpu = use_gpu
28+
self.rg = ReportGenerator()
29+
30+
def _run_single_exp(
31+
self,
32+
idx: int,
33+
input_image: np.ndarray,
34+
det_method: LiteralModelDetection,
35+
det_params: dict,
36+
cc_method: LiteralModelCorrection,
37+
cc_params: dict,
38+
reference_image: np.ndarray | None = None,
39+
) -> dict:
40+
cc = ColorCorrection(
41+
correction_model=cc_method,
42+
detection_model=det_method,
43+
detection_conf_th=det_params.get("detection_conf_th", 0.25),
44+
use_gpu=self.use_gpu,
45+
**cc_params,
46+
)
47+
48+
if reference_image is not None:
49+
cc.set_reference_image(reference_image)
50+
cc.set_input_patches(input_image, debug=True)
51+
cc.fit()
52+
corrected_image = cc.predict(input_image=input_image)
53+
eval_results = cc.calc_color_diff_patches()
54+
55+
before_comparison = visualize_patch_comparison(
56+
ls_mean_in=cc.input_patches,
57+
ls_mean_ref=cc.reference_patches,
58+
)
59+
after_comparison = visualize_patch_comparison(
60+
ls_mean_in=cc.corrected_patches,
61+
ls_mean_ref=cc.reference_patches,
62+
)
63+
64+
dE_image = calc_color_diff( # noqa: N806
65+
image1=input_image,
66+
image2=corrected_image,
67+
)
68+
69+
one_row = {
70+
"Index": idx,
71+
"Detection Method": det_method,
72+
"Detection Parameters": det_params,
73+
"Drawed Preprocessing Input": cc.input_debug_image,
74+
"Drawed Preprocessing Reference": cc.reference_debug_image,
75+
"Correction Method": cc_method,
76+
"Correction Parameters": cc_params,
77+
"Color Patches - Before": before_comparison,
78+
"Color Patches - After": after_comparison,
79+
"Input Image": input_image,
80+
"Corrected Image": corrected_image,
81+
"Patch ΔE (Before) - Min": eval_results["initial"]["min"],
82+
"Patch ΔE (Before) - Max": eval_results["initial"]["max"],
83+
"Patch ΔE (Before) - Mean": eval_results["initial"]["mean"],
84+
"Patch ΔE (Before) - Std": eval_results["initial"]["std"],
85+
"Patch ΔE (After) - Min": eval_results["corrected"]["min"],
86+
"Patch ΔE (After) - Max": eval_results["corrected"]["max"],
87+
"Patch ΔE (After) - Mean": eval_results["corrected"]["mean"],
88+
"Patch ΔE (After) - Std": eval_results["corrected"]["std"],
89+
"Image ΔE - Min": dE_image["min"],
90+
"Image ΔE - Max": dE_image["max"],
91+
"Image ΔE - Mean": dE_image["mean"],
92+
"Image ΔE - Std": dE_image["std"],
93+
}
94+
return one_row
95+
96+
def run(
97+
self,
98+
input_image: np.ndarray,
99+
output_dir: str = "benchmark_debug",
100+
reference_image: np.ndarray | None = None,
101+
) -> pd.DataFrame:
102+
"""
103+
Fungsi ini menjalankan benchmark untuk model color correction.
104+
"""
105+
ls_data = []
106+
idx = 1
107+
for det_method, det_params in self.list_detection_methods:
108+
for cc_method, cc_params in self.list_correction_methods:
109+
print(
110+
f"Running benchmark for {cc_method} method with {cc_params}",
111+
)
112+
data = self._run_single_exp(
113+
idx=idx,
114+
input_image=input_image,
115+
det_method=det_method,
116+
det_params=det_params,
117+
cc_method=cc_method,
118+
cc_params=cc_params,
119+
reference_image=reference_image,
120+
)
121+
idx += 1
122+
ls_data.append(data)
123+
df_results = pd.DataFrame(ls_data)
124+
125+
# Generate HTML report path
126+
os.makedirs(output_dir, exist_ok=True)
127+
html_report_path = os.path.join(output_dir, "report.html")
128+
pickel_report_path = os.path.join(output_dir, "report.pkl")
129+
130+
# Report Generator -----------------------------------------------------
131+
self.rg.generate_html_report(df=df_results, path_html=html_report_path)
132+
self.rg.save_dataframe(df=df_results, filepath=pickel_report_path)
133+
134+
# Save CSV report, but without image data
135+
df_results.drop(
136+
columns=[
137+
"Drawed Preprocessing Input",
138+
"Drawed Preprocessing Reference",
139+
"Color Patches - Before",
140+
"Color Patches - After",
141+
"Corrected Image",
142+
"Input Image",
143+
],
144+
).to_csv(os.path.join(output_dir, "report_no_image.csv"), index=False)
145+
146+
print("DataFrame shape:", df_results.shape)
147+
print("\nDataFrame columns:", df_results.columns.tolist())
148+
149+
150+
if __name__ == "__main__":
151+
# Pastikan path image sesuai dengan lokasi image Anda
152+
input_image_path = "asset/images/cc-19.png"
153+
154+
benchmark = ColorCorrectionAnalyzer(
155+
list_correction_methods=[
156+
("least_squares", {}),
157+
("linear_reg", {}),
158+
("affine_reg", {}),
159+
("polynomial", {"degree": 2}),
160+
("polynomial", {"degree": 3}),
161+
("polynomial", {"degree": 4}),
162+
],
163+
list_detection_methods=[
164+
("yolov8", {"detection_conf_th": 0.25}),
165+
],
166+
)
167+
168+
benchmark.run(
169+
input_image_path,
170+
reference_image=None,
171+
output_dir="benchmark_debug",
172+
)

0 commit comments

Comments
 (0)