chokkhu 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
chokkhu-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tamim Hossain
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
chokkhu-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: chokkhu
3
+ Version: 0.2.0
4
+ Summary: A small python image classification package
5
+ Home-page: https://github.com/tamimystic/Chokkhu-PyPi-Package
6
+ Author: tamimystic
7
+ Author-email: hossainsmtamim@gamil.com
8
+ License: MIT
9
+ Project-URL: Bug Tracker, https://github.com/tamimystic/Chokkhu-PyPi-Package/issues
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Requires-Python: >=3.8
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: tensorflow>=2.12
25
+ Requires-Dist: numpy>=1.23
26
+ Requires-Dist: Pillow>=9.0
27
+ Requires-Dist: matplotlib>=3.7
28
+ Requires-Dist: seaborn>=0.12
29
+ Requires-Dist: scikit-learn>=1.2
30
+ Requires-Dist: pandas>=2.0.0
31
+ Requires-Dist: opencv-python>=4.8.0
32
+ Requires-Dist: tqdm>=4.65.0
33
+ Provides-Extra: testing
34
+ Requires-Dist: pytest<9.0,>=7.2; extra == "testing"
35
+ Requires-Dist: tox<5.0,>=3.28; extra == "testing"
36
+ Requires-Dist: flake8<8.0,>=6.1; extra == "testing"
37
+ Requires-Dist: mypy<2.0,>=1.5; extra == "testing"
38
+ Provides-Extra: dev
39
+ Requires-Dist: black<25.0,>=23.3; extra == "dev"
40
+ Requires-Dist: isort<7.0,>=5.12; extra == "dev"
41
+ Requires-Dist: build<2.0,>=1.0; extra == "dev"
42
+ Requires-Dist: twine<6.0,>=4.0; extra == "dev"
43
+ Dynamic: author
44
+ Dynamic: author-email
45
+ Dynamic: description
46
+ Dynamic: description-content-type
47
+ Dynamic: home-page
48
+ Dynamic: license-file
49
+ Dynamic: project-url
50
+ Dynamic: summary
51
+
52
+ # Chokkhu
53
+
54
+ Chokkhu is a deep learning image dataset EDA and preprocessing toolkit designed to
55
+ prepare train-ready image data for TensorFlow / Keras–based image classification.
56
+ It helps users analyze datasets, preprocess images, handle class imbalance, and
57
+ train deep learning models using a clean and reproducible pipeline. The package
58
+ follows industry-grade Python packaging standards, supports CI/CD pipelines, and
59
+ works seamlessly in Google Colab and Jupyter Notebook environments.
60
+
61
+ INSTALLATION (IMPORTANT – FIRST STEP)
62
+
63
+ >>> pip install Chokkhu <<<
64
+
65
+ TensorFlow is installed automatically as a runtime dependency.
66
+
67
+ Chokkhu’s main responsibility is data preparation. It performs image exploratory
68
+ data analysis (EDA), class-wise distribution visualization, image size, aspect
69
+ ratio, RGB intensity and blur analysis, standard preprocessing (resize to 224×224
70
+ and normalization), stratified train/validation/test splitting, and automatic
71
+ class balancing using data augmentation. After this step, the dataset is fully
72
+ ready to be used for training any deep learning model.
73
+
74
+ Complete usage example showing the full workflow in one place:
75
+
76
+ ```python
77
+
78
+ from Chokkhu import ImageEDA, ImagePreProcessor
79
+
80
+
81
+ # Dataset EDA
82
+ eda = ImageEDA(dataset_path="your_dataset_path")
83
+
84
+ # Dataset preprocessing
85
+ processor = ImagePreProcessor(datapath="your_dataset_path")
86
+ (train_X, train_y), (val_X, val_y), (test_X, test_y) = processor.get_data()
87
+
88
+
89
+
90
+
91
+ After excecuting this, You can train your model like this.
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+ import tensorflow as tf
100
+ # Example 1: Custom CNN (from scratch)
101
+ custom_model = tf.keras.Sequential([
102
+ tf.keras.layers.Conv2D(32, 3, activation="relu", input_shape=(224,224,3)),
103
+ tf.keras.layers.MaxPooling2D(),
104
+ tf.keras.layers.Conv2D(64, 3, activation="relu"),
105
+ tf.keras.layers.MaxPooling2D(),
106
+ tf.keras.layers.Flatten(),
107
+ tf.keras.layers.Dense(128, activation="relu"),
108
+ tf.keras.layers.Dense(num_classes, activation="softmax")
109
+ ])
110
+
111
+ custom_model.compile(
112
+ optimizer="adam",
113
+ loss="sparse_categorical_crossentropy",
114
+ metrics=["accuracy"]
115
+ )
116
+
117
+ custom_model.fit(
118
+ train_X,
119
+ train_y,
120
+ validation_data=(val_X, val_y),
121
+ epochs=10
122
+ )
123
+
124
+
125
+
126
+
127
+ # Example 2: Transfer Learning with ConvNeXt-Tiny (frozen backbone)
128
+ import tensorflow as tf
129
+ base_model = tf.keras.applications.ConvNeXtTiny(
130
+ weights="imagenet",
131
+ include_top=False,
132
+ input_shape=(224,224,3)
133
+ )
134
+
135
+ base_model.trainable = False
136
+
137
+ transfer_model = tf.keras.Sequential([
138
+ base_model,
139
+ tf.keras.layers.GlobalAveragePooling2D(),
140
+ tf.keras.layers.Dense(256, activation="relu"),
141
+ tf.keras.layers.Dense(num_classes, activation="softmax")
142
+ ])
143
+
144
+ transfer_model.compile(
145
+ optimizer="adam",
146
+ loss="sparse_categorical_crossentropy",
147
+ metrics=["accuracy"]
148
+ )
149
+
150
+ transfer_model.fit(
151
+ train_X,
152
+ train_y,
153
+ validation_data=(val_X, val_y),
154
+ epochs=5
155
+ )
156
+
157
+
158
+
159
+
160
+ # Example 3: Fine-tuning ConvNeXt-Tiny (unfrozen backbone)
161
+ import tensorflow as tf
162
+ base_model.trainable = True
163
+
164
+ transfer_model.compile(
165
+ optimizer=tf.keras.optimizers.Adam(1e-5),
166
+ loss="sparse_categorical_crossentropy",
167
+ metrics=["accuracy"]
168
+ )
169
+
170
+ transfer_model.fit(
171
+ train_X,
172
+ train_y,
173
+ validation_data=(val_X, val_y),
174
+ epochs=5
175
+ )
@@ -0,0 +1,124 @@
1
+ # Chokkhu
2
+
3
+ Chokkhu is a deep learning image dataset EDA and preprocessing toolkit designed to
4
+ prepare train-ready image data for TensorFlow / Keras–based image classification.
5
+ It helps users analyze datasets, preprocess images, handle class imbalance, and
6
+ train deep learning models using a clean and reproducible pipeline. The package
7
+ follows industry-grade Python packaging standards, supports CI/CD pipelines, and
8
+ works seamlessly in Google Colab and Jupyter Notebook environments.
9
+
10
+ INSTALLATION (IMPORTANT – FIRST STEP)
11
+
12
+ >>> pip install Chokkhu <<<
13
+
14
+ TensorFlow is installed automatically as a runtime dependency.
15
+
16
+ Chokkhu’s main responsibility is data preparation. It performs image exploratory
17
+ data analysis (EDA), class-wise distribution visualization, image size, aspect
18
+ ratio, RGB intensity and blur analysis, standard preprocessing (resize to 224×224
19
+ and normalization), stratified train/validation/test splitting, and automatic
20
+ class balancing using data augmentation. After this step, the dataset is fully
21
+ ready to be used for training any deep learning model.
22
+
23
+ Complete usage example showing the full workflow in one place:
24
+
25
+ ```python
26
+
27
+ from Chokkhu import ImageEDA, ImagePreProcessor
28
+
29
+
30
+ # Dataset EDA
31
+ eda = ImageEDA(dataset_path="your_dataset_path")
32
+
33
+ # Dataset preprocessing
34
+ processor = ImagePreProcessor(datapath="your_dataset_path")
35
+ (train_X, train_y), (val_X, val_y), (test_X, test_y) = processor.get_data()
36
+
37
+
38
+
39
+
40
+ After excecuting this, You can train your model like this.
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+ import tensorflow as tf
49
+ # Example 1: Custom CNN (from scratch)
50
+ custom_model = tf.keras.Sequential([
51
+ tf.keras.layers.Conv2D(32, 3, activation="relu", input_shape=(224,224,3)),
52
+ tf.keras.layers.MaxPooling2D(),
53
+ tf.keras.layers.Conv2D(64, 3, activation="relu"),
54
+ tf.keras.layers.MaxPooling2D(),
55
+ tf.keras.layers.Flatten(),
56
+ tf.keras.layers.Dense(128, activation="relu"),
57
+ tf.keras.layers.Dense(num_classes, activation="softmax")
58
+ ])
59
+
60
+ custom_model.compile(
61
+ optimizer="adam",
62
+ loss="sparse_categorical_crossentropy",
63
+ metrics=["accuracy"]
64
+ )
65
+
66
+ custom_model.fit(
67
+ train_X,
68
+ train_y,
69
+ validation_data=(val_X, val_y),
70
+ epochs=10
71
+ )
72
+
73
+
74
+
75
+
76
+ # Example 2: Transfer Learning with ConvNeXt-Tiny (frozen backbone)
77
+ import tensorflow as tf
78
+ base_model = tf.keras.applications.ConvNeXtTiny(
79
+ weights="imagenet",
80
+ include_top=False,
81
+ input_shape=(224,224,3)
82
+ )
83
+
84
+ base_model.trainable = False
85
+
86
+ transfer_model = tf.keras.Sequential([
87
+ base_model,
88
+ tf.keras.layers.GlobalAveragePooling2D(),
89
+ tf.keras.layers.Dense(256, activation="relu"),
90
+ tf.keras.layers.Dense(num_classes, activation="softmax")
91
+ ])
92
+
93
+ transfer_model.compile(
94
+ optimizer="adam",
95
+ loss="sparse_categorical_crossentropy",
96
+ metrics=["accuracy"]
97
+ )
98
+
99
+ transfer_model.fit(
100
+ train_X,
101
+ train_y,
102
+ validation_data=(val_X, val_y),
103
+ epochs=5
104
+ )
105
+
106
+
107
+
108
+
109
+ # Example 3: Fine-tuning ConvNeXt-Tiny (unfrozen backbone)
110
+ import tensorflow as tf
111
+ base_model.trainable = True
112
+
113
+ transfer_model.compile(
114
+ optimizer=tf.keras.optimizers.Adam(1e-5),
115
+ loss="sparse_categorical_crossentropy",
116
+ metrics=["accuracy"]
117
+ )
118
+
119
+ transfer_model.fit(
120
+ train_X,
121
+ train_y,
122
+ validation_data=(val_X, val_y),
123
+ epochs=5
124
+ )
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+
6
+ [tool.pytest.ini_options]
7
+ testpaths = ["tests"]
8
+ addopts = "-ra -q"
9
+
10
+
11
+ [tool.mypy]
12
+ mypy_path = "src"
13
+ ignore_missing_imports = true
14
+ strict_optional = false
15
+ disallow_untyped_defs = false
16
+ check_untyped_defs = true
17
+
18
+
19
+ [tool.isort]
20
+ profile = "black"
21
+ line_length = 88
22
+ known_first_party = ["Chokkhu"]
23
+ src_paths = ["src"]
24
+
25
+
26
+ [tool.black]
27
+ line-length = 88
28
+ target-version = ["py38", "py39", "py310", "py311"]
@@ -0,0 +1,65 @@
1
+ [metadata]
2
+ license = MIT
3
+ license_file = LICENSE
4
+ url = https://github.com/tamimystic/Chokkhu-PyPi-Package
5
+ project_urls =
6
+ Bug Tracker = https://github.com/tamimystic/Chokkhu-PyPi-Package/issues
7
+ Source Code = https://github.com/tamimystic/Chokkhu-PyPi-Package
8
+ classifiers =
9
+ Development Status :: 2 - Pre-Alpha
10
+ Intended Audience :: Developers
11
+ Intended Audience :: Science/Research
12
+ License :: OSI Approved :: MIT License
13
+ Programming Language :: Python :: 3
14
+ Programming Language :: Python :: 3.8
15
+ Programming Language :: Python :: 3.9
16
+ Programming Language :: Python :: 3.10
17
+ Programming Language :: Python :: 3.11
18
+ Operating System :: OS Independent
19
+ Topic :: Scientific/Engineering :: Artificial Intelligence
20
+
21
+ [options]
22
+ package_dir =
23
+ = src
24
+ packages = find:
25
+ python_requires = >=3.8
26
+ install_requires =
27
+ tensorflow>=2.12
28
+ numpy>=1.23
29
+ Pillow>=9.0
30
+ matplotlib>=3.7
31
+ seaborn>=0.12
32
+ scikit-learn>=1.2
33
+ pandas>=2.0.0
34
+ opencv-python>=4.8.0
35
+ tqdm>=4.65.0
36
+
37
+ [options.packages.find]
38
+ where = src
39
+
40
+ [options.extras_require]
41
+ testing =
42
+ pytest>=7.2,<9.0
43
+ tox>=3.28,<5.0
44
+ flake8>=6.1,<8.0
45
+ mypy>=1.5,<2.0
46
+ dev =
47
+ black>=23.3,<25.0
48
+ isort>=5.12,<7.0
49
+ build>=1.0,<2.0
50
+ twine>=4.0,<6.0
51
+
52
+ [options.package_data]
53
+ chokkhu = py.typed
54
+
55
+ [flake8]
56
+ max-line-length = 160
57
+ exclude =
58
+ __init__.py
59
+ build
60
+ dist
61
+
62
+ [egg_info]
63
+ tag_build =
64
+ tag_date = 0
65
+
chokkhu-0.2.0/setup.py ADDED
@@ -0,0 +1,27 @@
1
+ import setuptools
2
+
3
+ with open("README.md", "r", encoding="utf-8") as f:
4
+ long_description=f.read()
5
+
6
+ __version__="0.2.0"
7
+
8
+ REPO_NAME="Chokkhu-PyPi-Package"
9
+ AUTHOR_USER_NAME="tamimystic"
10
+ AUTHOR_EMAIL="hossainsmtamim@gamil.com"
11
+ SRC_REPO="chokkhu"
12
+
13
+ setuptools.setup(
14
+ name=SRC_REPO,
15
+ version=__version__,
16
+ author=AUTHOR_USER_NAME,
17
+ author_email=AUTHOR_EMAIL,
18
+ description="A small python image classification package",
19
+ long_description=long_description,
20
+ long_description_content_type="text/markdown",
21
+ url=f"https://github.com/{AUTHOR_USER_NAME}/{REPO_NAME}",
22
+ project_urls={
23
+ "Bug Tracker": f"https://github.com/{AUTHOR_USER_NAME}/{REPO_NAME}/issues",
24
+ },
25
+ package_dir={"": "src"},
26
+ packages=setuptools.find_packages(where="src")
27
+ )
@@ -0,0 +1,22 @@
1
+ from .eda.image import ImageEDA
2
+ from .eda.tabular import TabularEDA
3
+ from .preprocessing.image import ImagePreProcessor
4
+
5
+ class EDAWrapper:
6
+ @staticmethod
7
+ def image(dataset_path: str, save_reports: bool = True, save_dir: str = "chokkhu_outputs/EDA_Reports") -> ImageEDA:
8
+ """
9
+ Runs the full Exploratory Data Analysis on the image dataset.
10
+ """
11
+ return ImageEDA(dataset_path=dataset_path, save_reports=save_reports, save_dir=save_dir)
12
+
13
+ @staticmethod
14
+ def tabular(dataset_path: str, save_reports: bool = True, save_dir: str = "chokkhu_outputs/EDA_Reports") -> TabularEDA:
15
+ """
16
+ Runs the full Exploratory Data Analysis on the tabular dataset.
17
+ """
18
+ return TabularEDA(dataset_path=dataset_path, save_reports=save_reports, save_dir=save_dir)
19
+
20
+ eda = EDAWrapper()
21
+
22
+ __all__ = ["ImageEDA", "TabularEDA", "ImagePreProcessor", "eda"]
@@ -0,0 +1,4 @@
1
+ from .image import ImageEDA
2
+ from .tabular import TabularEDA
3
+
4
+ __all__ = ["ImageEDA", "TabularEDA"]
@@ -0,0 +1,213 @@
1
+ import os
2
+ import warnings
3
+ from typing import Any, Dict, List
4
+
5
+ import cv2
6
+ import matplotlib.pyplot as plt
7
+ import numpy as np
8
+ import pandas as pd
9
+ import seaborn as sns
10
+ from PIL import Image
11
+ from tqdm import tqdm
12
+ from sklearn.decomposition import PCA
13
+
14
+ from .visualizer import PlotVisualizer
15
+
16
+ warnings.filterwarnings("ignore", category=FutureWarning)
17
+
18
+ class ImageEDA:
19
+ def __init__(
20
+ self,
21
+ dataset_path: str,
22
+ save_reports: bool = True,
23
+ save_dir: str = "chokkhu_outputs/EDA_Reports",
24
+ ):
25
+ """
26
+ Initializes the Ultra Pro Max ImageEDA class and triggers the analysis pipeline.
27
+ """
28
+ self.dataset_path: str = dataset_path
29
+ self.save_reports: bool = save_reports
30
+ self.save_dir: str = save_dir
31
+ self.results: Dict[str, Any] = {}
32
+ self.class_paths: List[str] = []
33
+
34
+ if self.save_reports:
35
+ os.makedirs(self.save_dir, exist_ok=True)
36
+
37
+ PlotVisualizer.setup_theme()
38
+ self._perform_eda()
39
+
40
+ def _perform_eda(self) -> None:
41
+ print(f"--- Executing Ultra Pro Max EDA for: {self.dataset_path} ---")
42
+ self._collect_paths()
43
+ if not self.class_paths:
44
+ print("Error: No valid images found in the specified path.")
45
+ return
46
+ self.results = self._analyze_data()
47
+ self._visual_reports()
48
+
49
+ def _collect_paths(self) -> None:
50
+ for root, _, files in os.walk(self.dataset_path):
51
+ if any(f.lower().endswith((".png", ".jpg", ".jpeg")) for f in files):
52
+ self.class_paths.append(root)
53
+
54
+ def _analyze_data(self) -> Dict[str, Any]:
55
+ exts = (".png", ".jpg", ".jpeg")
56
+ metrics_list = []
57
+ total_rgb_hist = np.zeros((256, 3))
58
+ processed_count = 0
59
+ pca_samples = []
60
+ pca_labels = []
61
+
62
+ # Max samples for PCA per class to avoid memory overload
63
+ MAX_PCA_SAMPLES = 200
64
+
65
+ for path in self.class_paths:
66
+ class_name = os.path.basename(path)
67
+ files = [f for f in os.listdir(path) if f.lower().endswith(exts)]
68
+
69
+ pca_sampled = 0
70
+ for img_name in tqdm(files, desc=f"Processing {class_name}"):
71
+ img_path = os.path.join(path, img_name)
72
+ img_bgr = cv2.imread(img_path)
73
+ if img_bgr is None:
74
+ continue
75
+
76
+ img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
77
+ gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
78
+
79
+ h, w, _ = img_rgb.shape
80
+
81
+ # Metrics Extraction
82
+ brightness = gray.mean()
83
+ contrast = gray.std()
84
+ blur = cv2.Laplacian(gray, cv2.CV_64F).var()
85
+ edges = cv2.Canny(gray, 100, 200).mean()
86
+
87
+ metrics_list.append({
88
+ "Class": class_name,
89
+ "Image": img_name,
90
+ "Width": w,
91
+ "Height": h,
92
+ "Aspect_Ratio": w / h if h > 0 else 0,
93
+ "Brightness": brightness,
94
+ "Contrast": contrast,
95
+ "Blur_Score": blur,
96
+ "Edge_Intensity": edges
97
+ })
98
+
99
+ # RGB Distribution
100
+ for i in range(3):
101
+ hist = cv2.calcHist([img_rgb], [i], None, [256], [0, 256])
102
+ total_rgb_hist[:, i] += hist.flatten() # type: ignore
103
+
104
+ # Sampling for PCA (Downscale to 32x32 for memory efficiency)
105
+ if pca_sampled < MAX_PCA_SAMPLES:
106
+ resized_for_pca = cv2.resize(gray, (32, 32)).flatten()
107
+ pca_samples.append(resized_for_pca)
108
+ pca_labels.append(class_name)
109
+ pca_sampled += 1
110
+
111
+ processed_count += 1
112
+
113
+ df_metrics = pd.DataFrame(metrics_list)
114
+ avg_hist = total_rgb_hist / processed_count if processed_count > 0 else total_rgb_hist
115
+
116
+ # PCA Computation
117
+ df_pca = None
118
+ if pca_samples:
119
+ pca = PCA(n_components=2)
120
+ pca_result = pca.fit_transform(np.array(pca_samples))
121
+ df_pca = pd.DataFrame({
122
+ "PCA1": pca_result[:, 0],
123
+ "PCA2": pca_result[:, 1],
124
+ "Class": pca_labels
125
+ })
126
+
127
+ return {
128
+ "df_metrics": df_metrics,
129
+ "avg_rgb_hist": avg_hist,
130
+ "df_pca": df_pca,
131
+ "total_classes": len(self.class_paths),
132
+ "total_images": processed_count,
133
+ }
134
+
135
+ def _visual_reports(self) -> None:
136
+ df = self.results["df_metrics"]
137
+
138
+ # 1. Class Distribution
139
+ fig, ax = plt.subplots(figsize=(10, 5))
140
+ class_counts = df["Class"].value_counts().reset_index()
141
+ class_counts.columns = ["Class", "Count"]
142
+ sns.barplot(data=class_counts, x="Class", y="Count", palette="viridis", ax=ax)
143
+ for p in ax.patches:
144
+ ax.annotate(f"{int(p.get_height())}", (p.get_x() + p.get_width() / 2., p.get_height()), ha='center', va='center', xytext=(0, 8), textcoords='offset points')
145
+ ax.set_title("Class-wise Image Distribution")
146
+ ax.tick_params(axis='x', rotation=45)
147
+ PlotVisualizer.save_and_show(fig, "1_class_distribution.png", self.save_dir, self.save_reports)
148
+
149
+ # 2. Dimensions & Aspect Ratio
150
+ fig, axes = plt.subplots(1, 3, figsize=(18, 5))
151
+ sns.histplot(data=df, x="Width", hue="Class", kde=False, element="step", ax=axes[0], palette="Set2")
152
+ axes[0].set_title("Width Distribution by Class")
153
+ sns.histplot(data=df, x="Height", hue="Class", kde=False, element="step", ax=axes[1], palette="Set2")
154
+ axes[1].set_title("Height Distribution by Class")
155
+ sns.boxplot(data=df, x="Class", y="Aspect_Ratio", ax=axes[2], palette="Set2")
156
+ axes[2].set_title("Aspect Ratio by Class")
157
+ axes[2].tick_params(axis='x', rotation=45)
158
+ PlotVisualizer.save_and_show(fig, "2_dimension_analysis.png", self.save_dir, self.save_reports)
159
+
160
+ # 3. Quality Metrics (Brightness, Contrast, Blur, Edges)
161
+ fig, axes = plt.subplots(2, 2, figsize=(16, 12))
162
+ sns.violinplot(data=df, x="Class", y="Brightness", palette="coolwarm", ax=axes[0, 0])
163
+ axes[0, 0].set_title("Brightness Distribution")
164
+ axes[0, 0].tick_params(axis='x', rotation=45)
165
+
166
+ sns.violinplot(data=df, x="Class", y="Contrast", palette="coolwarm", ax=axes[0, 1])
167
+ axes[0, 1].set_title("Contrast Distribution")
168
+ axes[0, 1].tick_params(axis='x', rotation=45)
169
+
170
+ sns.boxplot(data=df, x="Class", y="Blur_Score", palette="crest", ax=axes[1, 0])
171
+ axes[1, 0].set_title("Blur Score (Laplacian Variance)")
172
+ axes[1, 0].set_yscale('log')
173
+ axes[1, 0].tick_params(axis='x', rotation=45)
174
+
175
+ sns.boxplot(data=df, x="Class", y="Edge_Intensity", palette="crest", ax=axes[1, 1])
176
+ axes[1, 1].set_title("Edge Intensity (Canny)")
177
+ axes[1, 1].tick_params(axis='x', rotation=45)
178
+ PlotVisualizer.save_and_show(fig, "3_quality_metrics.png", self.save_dir, self.save_reports)
179
+
180
+ # 4. RGB Intensity Distribution
181
+ fig, ax = plt.subplots(figsize=(10, 6))
182
+ for i, col in enumerate(["red", "green", "blue"]):
183
+ ax.plot(self.results["avg_rgb_hist"][:, i], color=col, label=f"{col.upper()} Channel", linewidth=2)
184
+ ax.fill_between(range(256), self.results["avg_rgb_hist"][:, i], color=col, alpha=0.15)
185
+ ax.set_title("Global Average RGB Intensity Distribution")
186
+ ax.legend()
187
+ PlotVisualizer.save_and_show(fig, "4_rgb_intensity.png", self.save_dir, self.save_reports)
188
+
189
+ # 5. PCA Feature Space
190
+ df_pca = self.results.get("df_pca")
191
+ if df_pca is not None:
192
+ fig, ax = plt.subplots(figsize=(10, 8))
193
+ sns.scatterplot(data=df_pca, x="PCA1", y="PCA2", hue="Class", palette="tab10", alpha=0.7, ax=ax)
194
+ ax.set_title("PCA Feature Space (2D) - Are classes separable?")
195
+ PlotVisualizer.save_and_show(fig, "5_pca_feature_space.png", self.save_dir, self.save_reports)
196
+
197
+ # 6. Sample Grid
198
+ fig = plt.figure(figsize=(15, 10))
199
+ for i, path in enumerate(self.class_paths[:9]):
200
+ files = [f for f in os.listdir(path) if f.lower().endswith((".png", ".jpg", ".jpeg"))]
201
+ if files:
202
+ img = Image.open(os.path.join(path, files[0]))
203
+ ax = fig.add_subplot(3, 3, i + 1)
204
+ ax.imshow(img)
205
+ ax.set_title(os.path.basename(path))
206
+ ax.axis("off")
207
+ fig.suptitle("Sample Images per Class", fontsize=20)
208
+ PlotVisualizer.save_and_show(fig, "6_sample_grid.png", self.save_dir, self.save_reports)
209
+
210
+ # Save CSV Metrics
211
+ if self.save_reports:
212
+ df.to_csv(os.path.join(self.save_dir, "detailed_image_metrics.csv"), index=False)
213
+ print(f"\n[INFO] All reports and visualizations have been saved in: {self.save_dir}")