pixrr 0.1.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.
pixrr-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) <2025> <Hrishikesh Tiwari>
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.
pixrr-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,128 @@
1
+ Metadata-Version: 2.4
2
+ Name: pixrr
3
+ Version: 0.1.0
4
+ Summary: A lightweight image processing toolkit for Python
5
+ Author-email: Hrishikesh Tiwari <tiwarihrishikesh686@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) <2025> <Hrishikesh Tiwari>
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/Hrishi11572/pixrr
29
+ Project-URL: Documentation, https://github.com/Hrishi11572/pixrr/tree/main/docs
30
+ Keywords: image-processing,computer-vision,education,filters,numpy
31
+ Classifier: Programming Language :: Python :: 3
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Operating System :: OS Independent
34
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
35
+ Classifier: Intended Audience :: Education
36
+ Requires-Python: >=3.9
37
+ Description-Content-Type: text/markdown
38
+ License-File: LICENSE
39
+ Requires-Dist: numpy>=2.3.5
40
+ Requires-Dist: scipy>=1.16.3
41
+ Requires-Dist: matplotlib>=3.10.7
42
+ Requires-Dist: pillow>=12.0.0
43
+ Requires-Dist: numba>=0.63.1
44
+ Dynamic: license-file
45
+
46
+ # **pixrr : A lightweight image processing toolkit for python**
47
+
48
+ `pixrr` is a lightweight, beginner-friendly image processing library built for fast experimentation and teaching.
49
+ It focuses on simplicity, clean function names, and easy-to-understand code, making it useful both for quick image tasks and for pedagogical environments such as introductory image processing courses.
50
+
51
+ ---
52
+
53
+ ## Features (Planned & Implemented)
54
+
55
+ ### **Core Utilities**
56
+
57
+ * Convert color images to grayscale and binary
58
+ * Basic I/O helpers (read, write, display)
59
+ * Plot and analyze histograms
60
+ * Image thresholding
61
+ * Image Cropping and Rotation (planned)
62
+
63
+ ### **Filtering & Enhancement**
64
+
65
+ * Convolution using masks
66
+ * Convolution using FFT (planned)
67
+ * Contrast enhancement
68
+ * Histogram equalization
69
+ * Smoothing filters (Gaussian, median, weighted median)
70
+ * Sharpening and Laplacian operators
71
+
72
+ ### **Edge and Gradient Detection**
73
+
74
+ * Prewitt and Sobel (horizontal & vertical)
75
+ * Second-order derivatives
76
+ * Canny edge detector (planned)
77
+
78
+ ### **Segmentation & Classification**
79
+
80
+ * Otsu thresholding
81
+ * K-means clustering for image segmentation
82
+ * Mean-Shift clustering (planned)
83
+ * EM/Bayesian pixel classification (planned)
84
+
85
+ ---
86
+
87
+ ## Project Roadmap
88
+
89
+ | Stage | Goal |
90
+ | ------ | ------------------------------------------------------- |
91
+ | Part 1 | Basic conversions, contour extraction, histogram tools |
92
+ | Part 2 | Convolution, enhancement techniques, gradient operators |
93
+ | Part 3 | Automatic segmentation & clustering |
94
+ | Part 4 | Advanced edge detection and EM classification |
95
+
96
+ ---
97
+
98
+ ## Installation (when it’s live)
99
+
100
+ ```bash
101
+ pip install pixrr
102
+ ```
103
+
104
+ *(Currently under development. Not on PyPI yet.)*
105
+
106
+ ---
107
+
108
+ ## Usage Example
109
+
110
+ ```python
111
+ import pixrr as pix
112
+
113
+ # Example
114
+ img = pix.handle_image("peda_img/test_images/test2.png")
115
+ grey = pix.convert_to_gray(img)
116
+ # Applying gaussian
117
+ pix.show_image(pix.gaussian_smoothing(grey, kernel_size=3))
118
+ ```
119
+
120
+ ---
121
+
122
+ ## Contribution
123
+
124
+ This library is early-stage but open to improvements, bug reports, and algorithm implementations.
125
+ Ideal for students learning image processing or developers wanting simple utilities without heavy dependencies.
126
+
127
+ ---
128
+
pixrr-0.1.0/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # **pixrr : A lightweight image processing toolkit for python**
2
+
3
+ `pixrr` is a lightweight, beginner-friendly image processing library built for fast experimentation and teaching.
4
+ It focuses on simplicity, clean function names, and easy-to-understand code, making it useful both for quick image tasks and for pedagogical environments such as introductory image processing courses.
5
+
6
+ ---
7
+
8
+ ## Features (Planned & Implemented)
9
+
10
+ ### **Core Utilities**
11
+
12
+ * Convert color images to grayscale and binary
13
+ * Basic I/O helpers (read, write, display)
14
+ * Plot and analyze histograms
15
+ * Image thresholding
16
+ * Image Cropping and Rotation (planned)
17
+
18
+ ### **Filtering & Enhancement**
19
+
20
+ * Convolution using masks
21
+ * Convolution using FFT (planned)
22
+ * Contrast enhancement
23
+ * Histogram equalization
24
+ * Smoothing filters (Gaussian, median, weighted median)
25
+ * Sharpening and Laplacian operators
26
+
27
+ ### **Edge and Gradient Detection**
28
+
29
+ * Prewitt and Sobel (horizontal & vertical)
30
+ * Second-order derivatives
31
+ * Canny edge detector (planned)
32
+
33
+ ### **Segmentation & Classification**
34
+
35
+ * Otsu thresholding
36
+ * K-means clustering for image segmentation
37
+ * Mean-Shift clustering (planned)
38
+ * EM/Bayesian pixel classification (planned)
39
+
40
+ ---
41
+
42
+ ## Project Roadmap
43
+
44
+ | Stage | Goal |
45
+ | ------ | ------------------------------------------------------- |
46
+ | Part 1 | Basic conversions, contour extraction, histogram tools |
47
+ | Part 2 | Convolution, enhancement techniques, gradient operators |
48
+ | Part 3 | Automatic segmentation & clustering |
49
+ | Part 4 | Advanced edge detection and EM classification |
50
+
51
+ ---
52
+
53
+ ## Installation (when it’s live)
54
+
55
+ ```bash
56
+ pip install pixrr
57
+ ```
58
+
59
+ *(Currently under development. Not on PyPI yet.)*
60
+
61
+ ---
62
+
63
+ ## Usage Example
64
+
65
+ ```python
66
+ import pixrr as pix
67
+
68
+ # Example
69
+ img = pix.handle_image("peda_img/test_images/test2.png")
70
+ grey = pix.convert_to_gray(img)
71
+ # Applying gaussian
72
+ pix.show_image(pix.gaussian_smoothing(grey, kernel_size=3))
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Contribution
78
+
79
+ This library is early-stage but open to improvements, bug reports, and algorithm implementations.
80
+ Ideal for students learning image processing or developers wanting simple utilities without heavy dependencies.
81
+
82
+ ---
83
+
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+
6
+ [project]
7
+ name = "pixrr"
8
+ version = "0.1.0"
9
+ description = "A lightweight image processing toolkit for Python"
10
+ readme = "README.md"
11
+ license = { file = "LICENSE" }
12
+ authors = [
13
+ { name = "Hrishikesh Tiwari", email = "tiwarihrishikesh686@gmail.com" }
14
+ ]
15
+
16
+ requires-python = ">=3.9"
17
+
18
+ dependencies = [
19
+ "numpy>=2.3.5",
20
+ "scipy>=1.16.3",
21
+ "matplotlib>=3.10.7",
22
+ "pillow>=12.0.0",
23
+ "numba>=0.63.1"
24
+ ]
25
+
26
+ keywords = ["image-processing", "computer-vision", "education", "filters", "numpy"]
27
+
28
+ classifiers = [
29
+ "Programming Language :: Python :: 3",
30
+ "License :: OSI Approved :: MIT License",
31
+ "Operating System :: OS Independent",
32
+ "Topic :: Scientific/Engineering :: Image Processing",
33
+ "Intended Audience :: Education",
34
+ ]
35
+
36
+
37
+ [project.urls]
38
+ "Homepage" = "https://github.com/Hrishi11572/pixrr"
39
+ "Documentation" = "https://github.com/Hrishi11572/pixrr/tree/main/docs"
40
+
pixrr-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,40 @@
1
+ # pixrr/__init__.py
2
+
3
+ from .edges import gradient_prewitt, gradient_sobel, contour_extractor
4
+ from .enhance import linear_contrast_enhancement, histogram_equalization
5
+ from .filters import padd_image, conv2D, laplacian, sharpen_image, gaussian_filter, gaussian_smoothing
6
+ from .io import handle_image, convert_to_gray, show_image, save_image, plot_img_hist
7
+ from .threshold import threshold_image, otsu_thresholding
8
+ from .segmentation import kmeans_segmentation
9
+ from .utils import crop_image
10
+
11
+ # 2. DEFINING EXPORTS
12
+ # This list controls what happens if someone types "from peda_img import *"
13
+ # It also cleans up the namespace so IDEs know what is public.
14
+
15
+ __all__ = [
16
+ 'gradient_prewitt',
17
+ 'gradient_sobel',
18
+ 'contour_extractor',
19
+ 'linear_contrast_enhancement',
20
+ 'histogram_equalization',
21
+ 'padd_image',
22
+ 'conv2D',
23
+ 'laplacian',
24
+ 'sharpen_image',
25
+ 'gaussian_filter',
26
+ 'gaussian_smoothing',
27
+ 'handle_image',
28
+ 'convert_to_gray',
29
+ 'show_image',
30
+ 'save_image',
31
+ 'plot_img_hist',
32
+ 'threshold_image',
33
+ 'kmeans_segmentation',
34
+ 'otsu_thresholding',
35
+ 'crop_image'
36
+ ]
37
+
38
+ # Optional: Library Metadata
39
+ __version__ = "0.1.0"
40
+ __author__ = "Hrishikesh Tiwari"
@@ -0,0 +1,205 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+ from .io import convert_to_gray
4
+ from .filters import conv2D
5
+ import os
6
+
7
+ def gradient_prewitt(img: np.ndarray,
8
+ kernel_size : int = 3,
9
+ direction: str = "both",
10
+ hstep: int = 1,
11
+ vstep :int = 1) -> np.ndarray :
12
+ '''
13
+ Docstring for gradient_prewitt
14
+
15
+ :param img : input the image as a numpy array
16
+ :type img : np.ndarray
17
+ :param kernel_size : the size of the kernel, should be always odd
18
+ :type kernel_size : int (default = 3)
19
+ :param direction : in which direction you want to use the kernel : "both", "h", "v"
20
+ :type direction : str (Default = "both")
21
+ :param hstep : horizontal stride
22
+ :type hstep : int (Default = 1)
23
+ :param vstep : vertical stride
24
+ :type vstep : int (Default = 1)
25
+ :return: the convolved image, as numpy array
26
+ :rtype: ndarray[_AnyShape, dtype[Any]]
27
+ '''
28
+
29
+ if img.ndim == 3:
30
+ img = convert_to_gray(img)
31
+
32
+ if kernel_size % 2 == 0:
33
+ raise ValueError("Mask should be of the following form (odd, odd)")
34
+
35
+ # create the prewitt kernel of the given size
36
+ prewitt_horizontal = np.zeros((kernel_size, kernel_size-2))
37
+ prewitt_horizontal = np.hstack((prewitt_horizontal, np.full((kernel_size,1), -1)))
38
+ prewitt_horizontal = np.hstack((prewitt_horizontal[:,::-1], np.full((kernel_size,1), fill_value=1)))
39
+
40
+ prewitt_vertical = prewitt_horizontal.T[::-1,:]
41
+
42
+ if direction == "h":
43
+ horizontal_gradient = conv2D(img, prewitt_horizontal, hstep , vstep)
44
+ return horizontal_gradient
45
+
46
+ elif direction == "v":
47
+ vertical_gradient = conv2D(img, prewitt_vertical, hstep, vstep)
48
+ return vertical_gradient
49
+
50
+ elif direction == "both":
51
+ horizontal_gradient = conv2D(img, prewitt_horizontal, hstep, vstep)
52
+ vertical_gradient = conv2D(img, prewitt_vertical, hstep, vstep)
53
+ grad= np.sqrt(horizontal_gradient **2 + vertical_gradient **2)
54
+ grad = (grad / grad.max() * 255)
55
+ return grad.astype(np.uint8)
56
+
57
+
58
+
59
+ def gradient_sobel(img: np.ndarray,
60
+ kernel_size : int = 3,
61
+ direction: str = "both",
62
+ hstep: int = 1,
63
+ vstep :int = 1) -> np.ndarray :
64
+ '''
65
+ Docstring for gradient_sobel
66
+
67
+ :param img : input the image as a numpy array
68
+ :type img : np.ndarray
69
+ :param kernel_size : the size of the kernel, should be always odd
70
+ :type kernel_size : int (default = 3)
71
+ :param direction : the direction where you want to use the kernel : "both", "h" , "v"
72
+ :type direction : str (Default = "both")
73
+ :param hstep : horizontal stride
74
+ :type hstep : int (Default = 1)
75
+ :param vstep : vertical stride
76
+ :type vstep : int (Default = 1)
77
+ :return: the convolved image, as numpy array
78
+ :rtype: ndarray[_AnyShape, dtype[Any]]
79
+ '''
80
+ if img.ndim == 3:
81
+ img = convert_to_gray(img)
82
+
83
+ if kernel_size % 2 == 0:
84
+ raise ValueError("Mask should be of the following form (odd, odd)")
85
+
86
+ # create the prewitt kernel of the given size
87
+ def get_sobel_kernels(size: int):
88
+ if size % 2 == 0 or size < 3:
89
+ raise ValueError("Size must be odd and at least 3")
90
+
91
+ # 1. Generate Pascal's Triangle row for smoothing
92
+ def get_pascal_row(n):
93
+ row = [1]
94
+ for k in range(n):
95
+ row.append(row[k] * (n - k) // (k + 1))
96
+ return np.array(row, dtype=np.float32)
97
+
98
+ # Smoothing vector (s)
99
+ s = get_pascal_row(size - 1)
100
+
101
+ # Derivative vector (d)
102
+ # Logic: difference of the Pascal row one degree smaller
103
+ d_prev = get_pascal_row(size - 2)
104
+ d = np.zeros(size)
105
+ d[:-1] += d_prev
106
+ d[1:] -= d_prev
107
+
108
+ # 2. Create Kernels using Outer Product
109
+ # Gx (Horizontal) detects vertical edges
110
+ sobel_x = np.outer(s, d)
111
+
112
+ # Gy (Vertical) detects horizontal edges
113
+ sobel_y = np.outer(d, s)
114
+
115
+ return sobel_x, sobel_y
116
+
117
+ sobel_horizontal, sobel_vertical = get_sobel_kernels(kernel_size)
118
+
119
+ if direction == "h":
120
+ horizontal_gradient = conv2D(img, sobel_horizontal, hstep , vstep)
121
+ return horizontal_gradient
122
+
123
+ elif direction == "v":
124
+ vertical_gradient = conv2D(img, sobel_vertical, hstep, vstep)
125
+ return vertical_gradient
126
+
127
+ elif direction == "both":
128
+ horizontal_gradient = conv2D(img, sobel_horizontal, hstep, vstep)
129
+ vertical_gradient = conv2D(img, sobel_vertical, hstep, vstep)
130
+ grad= np.sqrt(horizontal_gradient **2 + vertical_gradient **2)
131
+ grad = (grad / grad.max() * 255)
132
+ return grad.astype(np.uint8)
133
+
134
+
135
+ def contour_extractor(img : np.ndarray = None,
136
+ save : bool = False,
137
+ directory : str = None,
138
+ filename : str | None = "default.png")->np.ndarray:
139
+ '''
140
+ Docstring for contour_extractor :
141
+
142
+ extract the contour of the thresholded image, where interior is white and background is black
143
+
144
+ :param img: image as np.ndarray
145
+ :type img: np.ndarray
146
+ :param save : boolean variable, asking whether you want to save the output image
147
+ :type save : bool
148
+ :param directory : the path where you want to save the image
149
+ :type directory : str (Default = None)
150
+ :return: contour image
151
+ :rtype: ndarray[_AnyShape, dtype[Any]]
152
+ '''
153
+
154
+ if img.ndim == 3:
155
+ raise ValueError("Please enter a binary image only")
156
+
157
+ dx = [-1,-1,0,1,1,1,0,-1]
158
+ dy = [0,-1,-1,-1,0,1,1,1]
159
+
160
+ height, width = img.shape
161
+ padded_arr = np.pad(img, pad_width=1, mode='constant', constant_values=255)
162
+ has_zero_neighbor = np.zeros((height, width), dtype=bool)
163
+
164
+ for k in range(8):
165
+ r_start = 1 + dx[k]
166
+ r_end = 1 + dx[k] + height
167
+ c_start = 1 + dy[k]
168
+ c_end = 1 + dy[k] + width
169
+
170
+ neighbor_view = padded_arr[r_start:r_end, c_start:c_end]
171
+ has_zero_neighbor |= (neighbor_view == 0)
172
+
173
+ contour_mask = (img == 255) & has_zero_neighbor
174
+ contour = np.argwhere(contour_mask)
175
+ contour_list = [tuple(x) for x in contour]
176
+
177
+ '''Display the contour and save it, if asked'''
178
+
179
+ fig, ax = plt.subplots()
180
+ ax.imshow(img, cmap="gray")
181
+ row, col = zip(*contour_list)
182
+ ax.scatter(col, row, s=2, c="red")
183
+ ax.axis("off")
184
+ plt.show()
185
+
186
+
187
+ contour_img = np.zeros((img.shape[0], img.shape[1]),dtype=np.uint8);
188
+ for (i, j) in contour_list:
189
+ contour_img[i, j] = 255
190
+
191
+ if save:
192
+ # creating new gray scale figure for saving
193
+ fig2, ax2 = plt.subplots()
194
+ ax2.imshow(contour_img, cmap="gray")
195
+ ax2.axis("off")
196
+
197
+ if directory is None or not os.path.exists(directory) :
198
+ directory = os.getcwd()
199
+
200
+ file_path = os.path.join(directory, filename)
201
+
202
+ fig2.savefig(file_path, dpi=600, bbox_inches="tight", pad_inches=0)
203
+ plt.close(fig2)
204
+
205
+ return contour_img
@@ -0,0 +1,178 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+ from .io import convert_to_gray
4
+ import os
5
+
6
+ def linear_contrast_enhancement(img : np.ndarray,
7
+ low: int = 0,
8
+ high : int = 255 ,
9
+ directory: str | None = None,
10
+ save : bool = False,
11
+ filename : str | None = None)->np.ndarray:
12
+
13
+ '''
14
+ Docstring for linear_contrast_enhancement
15
+
16
+ :param img : the input image as a numpy array
17
+ :type img : np.ndarray
18
+ :param low : the lowest intensity value
19
+ :type low : int
20
+ :param high : the highest intensity value
21
+ :type high : int
22
+ :param save : whether to save the resulting image or not
23
+ :type save : bool
24
+ :param directory : the path where you want to save the image
25
+ :type directory : str | None
26
+ :param filename : the name with which the image would be saved
27
+ :type filename : str
28
+ '''
29
+
30
+ if img.ndim == 3:
31
+ # convert colored to gray scale
32
+ img = convert_to_gray(img)
33
+
34
+ if max(low, high) > 255 or min(low, high) < 0:
35
+ raise ValueError("{low} and {high} should be between 0 and 255")
36
+
37
+ new_img = np.asarray(img)
38
+
39
+ min_intensity = img.min()
40
+ max_intensity = img.max()
41
+
42
+ print(f"Min intensity in original image : {min_intensity}")
43
+ print(f"Max intensity in original image : {max_intensity}")
44
+
45
+ # Edge case (Zero denominator)
46
+ if max_intensity == min_intensity:
47
+ return np.full_like(img, low, dtype=np.uint8)
48
+
49
+ new_img = ((high - low)/(max_intensity - min_intensity)) * (img - min_intensity) + low
50
+ new_img = new_img.astype(np.uint8)
51
+
52
+ fig, ax = plt.subplot_mosaic([
53
+ ['original', 'enhanced']
54
+ ], figsize=(7, 3.5))
55
+
56
+ ax["original"].imshow(img, cmap="gray")
57
+ ax["original"].axis("off")
58
+ ax["original"].set_title("Original Image")
59
+
60
+ ax["enhanced"].imshow(new_img, cmap="gray")
61
+ ax["enhanced"].axis("off")
62
+ ax["enhanced"].set_title("Enhanced Image")
63
+ plt.show()
64
+
65
+ if save:
66
+ # 1. Create a new figure and axes for saving
67
+ fig2, ax2 = plt.subplots()
68
+
69
+ # 2. Draw the new image onto the *saving* axes
70
+ ax2.imshow(new_img, cmap="gray")
71
+
72
+ # 3. Configure the axes
73
+ ax2.axis("off")
74
+
75
+ HIGH_RES_DPI = 600
76
+
77
+ if directory is None or not os.path.exists(directory) :
78
+ directory = os.getcwd()
79
+
80
+ file_path = os.path.join(directory, filename)
81
+
82
+ # **The key line for high resolution is here:**
83
+ fig2.savefig(
84
+ file_path,
85
+ dpi=HIGH_RES_DPI, # Sets the resolution
86
+ bbox_inches="tight", # Crops unnecessary white space
87
+ pad_inches=0 # Removes padding
88
+ )
89
+ plt.close(fig2)
90
+
91
+ return new_img
92
+
93
+
94
+
95
+ def histogram_equalization(img : np.ndarray,
96
+ low: int = 0,
97
+ high : int = 255,
98
+ save : bool = False,
99
+ directory : str | None = None,
100
+ filename : str | None = None)->np.ndarray:
101
+
102
+ '''
103
+ Docstring for histogram_equalization
104
+
105
+ :param img : the input image as a numpy array
106
+ :type img : np.ndarray
107
+ :param low : the lowest intensity value
108
+ :type low : int
109
+ :param high : the highest intensity value
110
+ :type high : int
111
+ :param save : whether to save the resulting image or not
112
+ :type save : bool
113
+ :param directory : the path where you want to save the image
114
+ :type directory : str | None
115
+ :param filename : the name with which the image would be saved
116
+ :type filename : str
117
+
118
+ '''
119
+
120
+ # convert to gray scale if necessary
121
+ if img.ndim == 3:
122
+ img = convert_to_gray(img)
123
+
124
+ if not (0 <= low < high <= 255):
125
+ raise ValueError(f"{low} and {high} must satisfy 0 <= low < high <= 255")
126
+
127
+ hist , _ = np.histogram(img, bins=256, range=(0,256))
128
+ hist = hist/hist.sum()
129
+
130
+ cdf_original = np.cumsum(hist)
131
+
132
+ new_img = np.round(cdf_original[img] * (high - low) + low).astype(np.uint8)
133
+
134
+ # display the enhanced image
135
+ fig, ax = plt.subplot_mosaic([
136
+ ['original', 'enhanced']
137
+ ], figsize=(7, 3.5))
138
+
139
+ ax["original"].imshow(img, cmap="gray")
140
+ ax["original"].axis("off")
141
+ ax["original"].set_title("Original Image")
142
+
143
+
144
+ ax["enhanced"].imshow(new_img, cmap="gray")
145
+ ax["enhanced"].axis("off")
146
+ ax["enhanced"].set_title("Enhanced Image")
147
+ plt.show()
148
+ plt.close(fig)
149
+
150
+ # save the new image if asked to do so
151
+ if save:
152
+
153
+ # 1. Create a new figure and axes for saving
154
+ fig2, ax2 = plt.subplots()
155
+
156
+ # 2. Draw the new image onto the *saving* axes
157
+ ax2.imshow(new_img, cmap="gray")
158
+
159
+ # 3. Configure the axes
160
+ ax2.axis("off")
161
+
162
+ HIGH_RES_DPI = 600
163
+
164
+ if directory is None or not os.path.exists(directory) :
165
+ directory = os.getcwd()
166
+
167
+ file_path = os.path.join(directory, filename)
168
+
169
+ # **The key line for high resolution is here:**
170
+ fig2.savefig(
171
+ file_path,
172
+ dpi=HIGH_RES_DPI, # Sets the resolution
173
+ bbox_inches="tight", # Crops unnecessary white space
174
+ pad_inches=0 # Removes padding
175
+ )
176
+ plt.close(fig2)
177
+
178
+ return new_img