modifiedOtsu 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,39 @@
1
+ """
2
+ Local 2D Otsu Thresholding for 3D Images
3
+ ==========================================
4
+
5
+ A high-performance implementation of local 2D Otsu thresholding for 3D grayscale images
6
+ using Numba acceleration.
7
+
8
+ Main Functions
9
+ --------------
10
+ getBinary : Apply local Otsu thresholding to get binary mask or thresholds
11
+ getThreshold : Compute Otsu threshold for 1D intensity values
12
+
13
+ Example
14
+ -------
15
+ >>> import numpy as np
16
+ >>> from modifiedOtsu import getMask
17
+ >>>
18
+ >>> # Create sample 3D image
19
+ >>> img = np.random.randint(0, 256, size=(50, 50, 50), dtype=np.uint8)
20
+ >>>
21
+ >>> # Get threshold mask and binary image
22
+ >>> threshold, binary = getMask(img, window_size=(3, 3, 3), delta=0.2)
23
+ """
24
+
25
+ from .core import getMask, getThreshold
26
+
27
+ # Package metadata
28
+ __version__ = "0.1.0"
29
+ __author__ = "John Rick Manzanares"
30
+ __email__ = "jdolormanzanares@impan.pl"
31
+
32
+ # Define what gets imported with "from modifiedOtsu import *"
33
+ __all__ = [
34
+ "getBinary",
35
+ "getThreshold",
36
+ ]
37
+
38
+ # Optional: Add convenience functions or constants
39
+ DEFAULT_WINDOW_SIZE = (3, 3, 3)
modifiedOtsu/core.py ADDED
@@ -0,0 +1,291 @@
1
+ """
2
+ Core implementation of modified Otsu thresholding.
3
+ """
4
+
5
+ import numpy as np
6
+ from numba import njit, prange
7
+ from typing import Tuple
8
+
9
+
10
+ # ============================================================================
11
+ # Helper Functions (Internal)
12
+ # ============================================================================
13
+
14
+
15
+ @njit
16
+ def _padReflect(img: np.ndarray, pad_width: Tuple[int, int, int]) -> np.ndarray:
17
+ """
18
+ Manual reflect padding for 3D arrays (Numba-compatible).
19
+
20
+ Parameters
21
+ ----------
22
+ img : np.ndarray
23
+ 3D input array
24
+ pad_width : tuple of int
25
+ Padding size for each dimension (z_pad, y_pad, x_pad)
26
+
27
+ Returns
28
+ -------
29
+ np.ndarray
30
+ Padded array with reflect mode
31
+ """
32
+ z_pad, y_pad, x_pad = pad_width
33
+ z_max, y_max, x_max = img.shape
34
+
35
+ padded = np.zeros(
36
+ (z_max + 2 * z_pad, y_max + 2 * y_pad, x_max + 2 * x_pad), dtype=img.dtype
37
+ )
38
+
39
+ # Copy center
40
+ padded[z_pad : z_pad + z_max, y_pad : y_pad + y_max, x_pad : x_pad + x_max] = img
41
+
42
+ # Reflect padding for Z
43
+ for i in range(z_pad):
44
+ padded[z_pad - 1 - i, y_pad : y_pad + y_max, x_pad : x_pad + x_max] = img[
45
+ i, :, :
46
+ ]
47
+ padded[z_pad + z_max + i, y_pad : y_pad + y_max, x_pad : x_pad + x_max] = img[
48
+ z_max - 1 - i, :, :
49
+ ]
50
+
51
+ # Reflect padding for Y
52
+ for j in range(y_pad):
53
+ padded[:, y_pad - 1 - j, x_pad : x_pad + x_max] = padded[
54
+ :, y_pad + j, x_pad : x_pad + x_max
55
+ ]
56
+ padded[:, y_pad + y_max + j, x_pad : x_pad + x_max] = padded[
57
+ :, y_pad + y_max - 1 - j, x_pad : x_pad + x_max
58
+ ]
59
+
60
+ # Reflect padding for X
61
+ for k in range(x_pad):
62
+ padded[:, :, x_pad - 1 - k] = padded[:, :, x_pad + k]
63
+ padded[:, :, x_pad + x_max + k] = padded[:, :, x_pad + x_max - 1 - k]
64
+
65
+ return padded
66
+
67
+
68
+ @njit(parallel=True)
69
+ def _getMask(
70
+ img: np.ndarray, window_size: Tuple[int, int, int], delta: float
71
+ ) -> np.ndarray:
72
+ """
73
+ Internal Numba-compiled implementation.
74
+ No validation - assumes inputs are correct.
75
+ """
76
+ z_win, y_win, x_win = window_size
77
+ z_pad, y_pad, x_pad = z_win // 2, y_win // 2, x_win // 2
78
+
79
+ padded = _padReflect(img, (z_pad, y_pad, x_pad))
80
+ mask = np.zeros_like(img, dtype=np.int32)
81
+ z_max, y_max, x_max = img.shape
82
+
83
+ for z in prange(z_max):
84
+ for y in range(y_max):
85
+ for x in range(x_max):
86
+ window = padded[z : z + z_win, y : y + y_win, x : x + x_win].ravel()
87
+ mask[z, y, x] = getThreshold(window, delta)
88
+
89
+ return mask, (img > mask)
90
+
91
+ # ============================================================================
92
+ # Public API
93
+ # ============================================================================
94
+
95
+
96
+ @njit
97
+ def getThreshold(values: np.ndarray, delta: float = 0.2) -> int:
98
+ """
99
+ Compute Otsu threshold for a 1D array of intensity values.
100
+
101
+ This implementation assumes integer input values. The algorithm bins values
102
+ by their integer representation and computes the optimal threshold that
103
+ maximizes between-class variance.
104
+
105
+ Parameters
106
+ ----------
107
+ values : np.ndarray
108
+ 1D array of integer intensity values
109
+ delta : float, default=0.2
110
+ Contrast threshold. If the relative difference between foreground
111
+ and background means is below this threshold, the maximum value + 1
112
+ is returned (indicating insufficient contrast).
113
+
114
+ Returns
115
+ -------
116
+ int
117
+ The optimal threshold value, or max(values) + 1 if contrast is too low
118
+
119
+ Notes
120
+ -----
121
+ The algorithm:
122
+ 1. Creates a histogram of input values
123
+ 2. Iterates through possible thresholds
124
+ 3. Computes between-class variance for each threshold
125
+ 4. Returns the threshold with maximum variance
126
+ 5. If contrast is too low (|mB - mF| / mB <= delta), returns max + 1
127
+
128
+ Examples
129
+ --------
130
+ >>> values = np.array([10, 10, 10, 20, 20, 30, 30, 30], dtype=np.int32)
131
+ >>> threshold = getThreshold(values, delta=0.2)
132
+ """
133
+ mn = values.min()
134
+ mx = values.max()
135
+
136
+ if mx == mn:
137
+ return int(mx)
138
+
139
+ binned_values = (values - mn).astype(np.int32)
140
+ hist = np.bincount(binned_values, minlength=mx - mn + 1)
141
+ bin_edges = np.arange(len(hist)) + mn
142
+
143
+ total = values.size
144
+ sum_total = np.sum(bin_edges * hist)
145
+ sumB = 0.0
146
+ wB = 0
147
+ maximum = 0.0
148
+ threshold = 0
149
+ best_mB, best_mF = 0.0, 0.0
150
+
151
+ for i in range(len(hist)):
152
+ wB += hist[i]
153
+ if wB == 0:
154
+ continue
155
+ wF = total - wB
156
+ if wF == 0:
157
+ break
158
+
159
+ sumB += bin_edges[i] * hist[i]
160
+ mB = sumB / wB
161
+ mF = (sum_total - sumB) / wF
162
+ between = wB * wF * (mB - mF) ** 2
163
+
164
+ if between > maximum:
165
+ maximum = between
166
+ threshold = bin_edges[i]
167
+ best_mB = mB
168
+ best_mF = mF
169
+
170
+ # Force threshold to max if contrast is too low
171
+ if best_mB == 0 or abs(best_mB - best_mF) / best_mB <= delta:
172
+ threshold = mx + 1
173
+
174
+ return int(threshold)
175
+
176
+
177
+ def getMask(
178
+ img: np.ndarray,
179
+ window_size: Tuple[int, int, int] = (3, 3, 3),
180
+ delta: float = 0.2
181
+ ) -> np.ndarray:
182
+ """
183
+ Apply local Otsu thresholding to a 3D grayscale image.
184
+
185
+ For each pixel, computes an optimal threshold based on the intensity
186
+ distribution in a local neighborhood window, then either returns the
187
+ threshold values or a binary mask.
188
+
189
+ Parameters
190
+ ----------
191
+ img : np.ndarray
192
+ 3D input image with integer dtype (e.g., uint8, uint16, int32).
193
+ Shape should be (z, y, x).
194
+ window_size : tuple of int
195
+ Local neighborhood size as (z_size, y_size, x_size).
196
+ Should be positive odd integers for centered windows.
197
+ delta : float, default=0.2
198
+ Contrast parameter for Otsu thresholding. Lower values are more
199
+ permissive, higher values require stronger contrast.
200
+
201
+ Returns
202
+ -------
203
+ np.ndarray
204
+ If binarize=True: Binary mask with same shape as input (dtype=uint8)
205
+ If binarize=False: Threshold map with same shape as input (dtype=int32)
206
+
207
+ Raises
208
+ ------
209
+ ValueError
210
+ If input validation fails (wrong dimensions, dtype, window_size, etc.)
211
+
212
+ Examples
213
+ --------
214
+ >>> import numpy as np
215
+ >>> from modifiedOtsu import getMask
216
+ >>>
217
+ >>> # Create sample 3D grayscale image
218
+ >>> img = np.random.randint(0, 256, size=(50, 50, 50), dtype=np.uint8)
219
+ >>>
220
+ >>> # Get binary mask
221
+ >>> mask = getMask(img, window_size=(5, 5, 5), delta=0.2, binarize=True)
222
+ >>> print(mask.shape, mask.dtype)
223
+ (50, 50, 50) uint8
224
+ >>>
225
+ >>> # Get threshold values
226
+ >>> thresholds = getMask(img, window_size=(5, 5, 5), delta=0.2, binarize=False)
227
+ >>> print(thresholds.shape, thresholds.dtype)
228
+ (50, 50, 50) int32
229
+
230
+ Notes
231
+ -----
232
+ - Uses reflect padding at image boundaries
233
+ - Parallelized using Numba for high performance
234
+ - Input must be integer dtype (float images should be converted first)
235
+ """
236
+ # Validation
237
+ if img.ndim != 3:
238
+ raise ValueError(
239
+ f"Expected 3D image, got {img.ndim}D array with shape {img.shape}"
240
+ )
241
+
242
+ if not np.issubdtype(img.dtype, np.integer):
243
+ raise ValueError(
244
+ f"Expected integer dtype (e.g., uint8, uint16, int32), got {img.dtype}. "
245
+ f"Convert using img.astype(np.uint8) or similar."
246
+ )
247
+
248
+ if not isinstance(window_size, (tuple, list)):
249
+ raise ValueError(
250
+ f"window_size must be a tuple or list, got {type(window_size)}"
251
+ )
252
+
253
+ if len(window_size) != 3:
254
+ raise ValueError(
255
+ f"window_size must have 3 elements (z, y, x), got {len(window_size)}"
256
+ )
257
+
258
+ if not all(isinstance(w, (int, np.integer)) for w in window_size):
259
+ raise ValueError(f"window_size elements must be integers, got {window_size}")
260
+
261
+ if any(w <= 0 for w in window_size):
262
+ raise ValueError(f"window_size elements must be positive, got {window_size}")
263
+
264
+ if any(w % 2 == 0 for w in window_size):
265
+ z_win, y_win, x_win = window_size
266
+ raise ValueError(
267
+ f"window_size elements should be odd numbers for centered windows, "
268
+ f"got {window_size}. Consider using "
269
+ f"({z_win+1 if z_win%2==0 else z_win}, "
270
+ f"{y_win+1 if y_win%2==0 else y_win}, "
271
+ f"{x_win+1 if x_win%2==0 else x_win}) instead."
272
+ )
273
+
274
+ z_max, y_max, x_max = img.shape
275
+ z_win, y_win, x_win = window_size
276
+ if z_win > z_max or y_win > y_max or x_win > x_max:
277
+ raise ValueError(
278
+ f"window_size {window_size} is larger than image dimensions {img.shape}"
279
+ )
280
+
281
+ if not isinstance(delta, (int, float)):
282
+ raise ValueError(f"delta must be a number, got {type(delta)}")
283
+
284
+ if delta < 0:
285
+ raise ValueError(f"delta must be non-negative, got {delta}")
286
+
287
+ if img.size == 0:
288
+ raise ValueError("Input image is empty")
289
+
290
+ # Call Numba implementation
291
+ return _getMask(img, window_size, delta)
modifiedOtsu/runner.py ADDED
@@ -0,0 +1,179 @@
1
+ # ============================================================================
2
+ # Command Line Runner for Modified Otsu Thresholding
3
+ # ============================================================================
4
+
5
+ import argparse
6
+ import numpy as np
7
+ import os
8
+ import matplotlib.pyplot as plt
9
+ from modifiedOtsu import getMask
10
+
11
+
12
+ def load_image(image_path: str) -> np.ndarray:
13
+ """
14
+ Load a 3D image from a file (.npy or .tif/.tiff).
15
+ """
16
+ if not os.path.exists(image_path):
17
+ raise FileNotFoundError(f"Image file not found: {image_path}")
18
+
19
+ ext = os.path.splitext(image_path)[1].lower()
20
+
21
+ if ext == ".npy":
22
+ img = np.load(image_path)
23
+ elif ext in [".tif", ".tiff"]:
24
+ try:
25
+ import tifffile
26
+ except ImportError:
27
+ raise ImportError(
28
+ "tifffile is required to read TIFF images. "
29
+ "Install it via 'pip install tifffile'."
30
+ )
31
+ img = tifffile.imread(image_path)
32
+ else:
33
+ raise ValueError(
34
+ f"Unsupported file format: {ext}. Use .npy or .tif/.tiff files."
35
+ )
36
+
37
+ if img.ndim != 3:
38
+ raise ValueError(f"Expected a 3D image, got shape {img.shape}")
39
+
40
+ if not np.issubdtype(img.dtype, np.integer):
41
+ img = img.astype(np.uint8)
42
+
43
+ return img
44
+
45
+
46
+ def save_output(threshold: np.ndarray, binary: np.ndarray, image_path: str, outdir: str, fmt: str):
47
+ """
48
+ Save threshold and binary arrays to the specified directory and format.
49
+ Filenames follow: {input_filename}_{threshold/binary}.{format}
50
+ """
51
+ os.makedirs(outdir, exist_ok=True)
52
+
53
+ # Extract base name (without extension)
54
+ base_name = os.path.splitext(os.path.basename(image_path))[0]
55
+
56
+ th_path = os.path.join(outdir, f"{base_name}_threshold.{fmt}")
57
+ bin_path = os.path.join(outdir, f"{base_name}_binary.{fmt}")
58
+
59
+ if fmt == "npy":
60
+ np.save(th_path, threshold)
61
+ np.save(bin_path, binary)
62
+ print(f"Saved NPY files:\n {th_path}\n {bin_path}")
63
+
64
+ elif fmt in ["tif", "tiff"]:
65
+ try:
66
+ import tifffile
67
+ except ImportError:
68
+ print("tifffile not installed — cannot save as TIFF. Saving as NPY instead.")
69
+ np.save(th_path.replace(".tiff", ".npy"), threshold)
70
+ np.save(bin_path.replace(".tiff", ".npy"), binary)
71
+ return
72
+ tifffile.imwrite(th_path, threshold.astype(np.uint16))
73
+ tifffile.imwrite(bin_path, binary.astype(np.uint8))
74
+ print(f"Saved TIFF files:\n {th_path}\n {bin_path}")
75
+
76
+ else:
77
+ raise ValueError(f"Unsupported format: {fmt}. Choose 'npy' or 'tiff'.")
78
+
79
+
80
+ def show_results(img: np.ndarray, threshold: np.ndarray, binary: np.ndarray, z_index: int = None):
81
+ """
82
+ Show a chosen or middle slice for threshold and binary output.
83
+ """
84
+ if z_index is None:
85
+ z_index = img.shape[0] // 2
86
+
87
+ if z_index < 0 or z_index >= img.shape[0]:
88
+ raise ValueError(f"Invalid z-index {z_index}. Must be in range [0, {img.shape[0]-1}]")
89
+
90
+ fig, axes = plt.subplots(1, 3, figsize=(12, 4))
91
+ axes[0].imshow(img[z_index], cmap="gray")
92
+ axes[0].set_title(f"Original (z={z_index})")
93
+ axes[1].imshow(threshold[z_index], cmap="gray")
94
+ axes[1].set_title("Local Thresholds")
95
+ axes[2].imshow(binary[z_index], cmap="gray")
96
+ axes[2].set_title("Binary Mask")
97
+ for ax in axes:
98
+ ax.axis("off")
99
+
100
+ plt.tight_layout()
101
+ plt.show()
102
+
103
+
104
+ def main():
105
+ parser = argparse.ArgumentParser(
106
+ description="Run Modified Otsu Thresholding on a 3D image."
107
+ )
108
+ parser.add_argument(
109
+ "--image",
110
+ type=str,
111
+ required=True,
112
+ help="Path to a 3D image file (.npy or .tif/.tiff)",
113
+ )
114
+ parser.add_argument(
115
+ "--window",
116
+ type=int,
117
+ nargs=3,
118
+ default=[3, 3, 3],
119
+ help="Local window size (z y x). Default: 3 3 3",
120
+ )
121
+ parser.add_argument(
122
+ "--delta",
123
+ type=float,
124
+ default=0.2,
125
+ help="Contrast delta parameter (default: 0.2)",
126
+ )
127
+ parser.add_argument(
128
+ "--outdir",
129
+ type=str,
130
+ default="outputs",
131
+ help="Directory to save outputs (default: ./outputs)",
132
+ )
133
+ parser.add_argument(
134
+ "--format",
135
+ type=str,
136
+ default="npy",
137
+ choices=["npy", "tif", "tiff"],
138
+ help="Output file format (default: npy)",
139
+ )
140
+ parser.add_argument(
141
+ "--show",
142
+ action="store_true",
143
+ help="Display threshold and binary slices using matplotlib",
144
+ )
145
+ parser.add_argument(
146
+ "--slice",
147
+ type=int,
148
+ default=None,
149
+ help="Optional z-index to visualize (default: middle slice)",
150
+ )
151
+
152
+ args = parser.parse_args()
153
+
154
+ # Load image
155
+ print(f"Loading image: {args.image}")
156
+ img = load_image(args.image)
157
+ print(f"Image loaded with shape {img.shape} and dtype {img.dtype}")
158
+
159
+ # Run Modified Otsu Thresholding
160
+ print("Running modified Otsu thresholding...")
161
+ threshold, binary = getMask(img, window_size=tuple(args.window), delta=args.delta)
162
+
163
+ # Print results
164
+ print("------------------------------------------------------------")
165
+ print(f"Threshold map shape: {threshold.shape}")
166
+ print(f"Binary mask shape: {binary.shape}")
167
+
168
+ # Save outputs
169
+ save_output(threshold, binary, args.image, args.outdir, args.format)
170
+
171
+ # Show outputs if requested
172
+ if args.show:
173
+ show_results(img, threshold, binary, args.slice)
174
+
175
+ print("Done.")
176
+
177
+
178
+ if __name__ == "__main__":
179
+ main()
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: modifiedOtsu
3
+ Version: 0.1.0
4
+ Summary: Numba-accelerated local modified Otsu thresholding for 3D images.
5
+ Author-email: John Rick Manzanares <jdolormanzanares@impan.pl>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/jhnrckmnznrs/modifiedOtsu
8
+ Project-URL: Issues, https://github.com/jhnrckmnznrs/modifiedOtsu/issues
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: numpy
13
+ Requires-Dist: numba
14
+ Provides-Extra: tiff
15
+ Requires-Dist: tifffile; extra == "tiff"
16
+ Requires-Dist: matplotlib; extra == "tiff"
17
+ Dynamic: license-file
18
+
19
+ # 🧠 Local Modified Otsu Thresholding
20
+
21
+ **Local Modified Otsu Thresholding** is a **Numba-accelerated** implementation of a *local, contrast-adaptive* Otsu thresholding method for 3D images.
22
+
23
+ This algorithm adapts the Otsu threshold within a sliding 3D window, enabling robust segmentation in datasets with spatially varying intensity distributions.
24
+
25
+ The method is inspired by the segmentation approach described in **Subsection 2.2.2** of the article:
26
+
27
+ > *Segmentation-based tracking of macrophages in 2D + time microscopy movies inside a living animal*
28
+ >
29
+ > *Computers in Biology and Medicine, 153 (2023), 106499*
30
+ >
31
+ > [https://doi.org/10.1016/j.compbiomed.2022.106499](https://doi.org/10.1016/j.compbiomed.2022.106499)
32
+
33
+ If you use this implementation in your research, please cite the original work that inspired this algorithm.
34
+
35
+ ---
36
+
37
+ ## ⚙️ Installation
38
+
39
+ Install the latest stable version from PyPI:
40
+
41
+ ```bash
42
+ pip install modifiedOtsu
43
+ ```
44
+
45
+ ## 🚀 Example Usage
46
+
47
+ ```python
48
+ import numpy as np
49
+ from modifiedOtsu import getMask
50
+
51
+ # Create sample 3D image
52
+ img = np.random.randint(0, 256, size=(10, 10, 10), dtype=np.uint8)
53
+
54
+ # Get threshold map and binary image
55
+ threshold, binary = getMask(img, window_size=(3, 3, 3), delta=0.2)
56
+ ```
57
+
58
+ ## 🖥️ Command Line Usage
59
+
60
+ You can execute the script with custom parameters:
61
+
62
+ ```bash
63
+ python runner.py --shape 10 10 10 --window 3 3 3 --delta 0.2
64
+ ```
65
+
66
+ ## 📦 Dependencies
67
+
68
+ - NumPy
69
+ - Numba
70
+ - Matplotlib
71
+
72
+ Install dependencies with:
73
+
74
+ ```bash
75
+ pip install numpy numba matplotlib
76
+ ```
77
+
78
+ ## 📜 License
79
+
80
+ This project is licensed under the MIT License. See the LICENSE file for details.
81
+
82
+ ## 🤝 Contributing
83
+
84
+ Contributions are welcome! If you'd like to fix a bug, add a feature, or improve performance, please open a pull request or contact the maintainers.
85
+
86
+ ## 💬 Contact
87
+
88
+ For questions, issues, or feedback, open an issue on GitHub.
@@ -0,0 +1,9 @@
1
+ modifiedOtsu/__init__.py,sha256=4RwEI6MGz0hNpPRQ3Fh9xGgQqmtfGPT3pf-ErYzEXe4,1032
2
+ modifiedOtsu/core.py,sha256=2iJNrTAwpxJCPQfS3WR4cOLih3Q4Z7l_R1mzawchDBE,9029
3
+ modifiedOtsu/runner.py,sha256=BFXSiqm7L7l0GZAuDygZ_R8PY_odAoZypLuaVanqEe8,5475
4
+ modifiedotsu-0.1.0.dist-info/licenses/LICENSE,sha256=nf6nNGBSlKqD389gsU-_DPce5IOf-lk_MceXNyoDpMg,1078
5
+ modifiedotsu-0.1.0.dist-info/METADATA,sha256=xbPoLCMlJP42CGF1qkwCZe-8RfH8xCVTKfJOBQ5WiBM,2484
6
+ modifiedotsu-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ modifiedotsu-0.1.0.dist-info/entry_points.txt,sha256=8PFdo6g2IrSC_Nid05rnpq7IWxkMBtlfyTQEdWJQbnk,59
8
+ modifiedotsu-0.1.0.dist-info/top_level.txt,sha256=kF51f_OBYTVQtFubEaNsrNW9R7HNUWKP_UYTAag-spM,13
9
+ modifiedotsu-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ modified-otsu = modifiedOtsu.runner:main
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 John Rick Manzanares
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.
22
+
@@ -0,0 +1 @@
1
+ modifiedOtsu