segment-toolkit 1.0.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.
- segment_toolkit-1.0.0/PKG-INFO +217 -0
- segment_toolkit-1.0.0/README.md +198 -0
- segment_toolkit-1.0.0/segment_toolkit/__init__.py +60 -0
- segment_toolkit-1.0.0/segment_toolkit/cli.py +192 -0
- segment_toolkit-1.0.0/segment_toolkit/helpers.py +201 -0
- segment_toolkit-1.0.0/segment_toolkit/source.py +566 -0
- segment_toolkit-1.0.0/segment_toolkit.egg-info/PKG-INFO +217 -0
- segment_toolkit-1.0.0/segment_toolkit.egg-info/SOURCES.txt +13 -0
- segment_toolkit-1.0.0/segment_toolkit.egg-info/dependency_links.txt +1 -0
- segment_toolkit-1.0.0/segment_toolkit.egg-info/entry_points.txt +2 -0
- segment_toolkit-1.0.0/segment_toolkit.egg-info/requires.txt +5 -0
- segment_toolkit-1.0.0/segment_toolkit.egg-info/top_level.txt +1 -0
- segment_toolkit-1.0.0/setup.cfg +4 -0
- segment_toolkit-1.0.0/setup.py +32 -0
- segment_toolkit-1.0.0/tests/test_converter.py +109 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: segment_toolkit
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A Python toolkit to convert between binary segmentation masks and YOLO labels
|
|
5
|
+
Author: Antigravity
|
|
6
|
+
Requires-Python: >=3.6
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: numpy
|
|
9
|
+
Requires-Dist: opencv-python
|
|
10
|
+
Requires-Dist: pillow
|
|
11
|
+
Requires-Dist: pandas
|
|
12
|
+
Requires-Dist: matplotlib
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: description
|
|
15
|
+
Dynamic: description-content-type
|
|
16
|
+
Dynamic: requires-dist
|
|
17
|
+
Dynamic: requires-python
|
|
18
|
+
Dynamic: summary
|
|
19
|
+
|
|
20
|
+
# Segment Toolkit 🛠️
|
|
21
|
+
|
|
22
|
+
A modern, robust, and premium Python package designed to bridge the gap between pixel-level **binary segmentation masks** and **YOLO bounding box labels**. It provides a bidirectional pipeline with exception handling, extensive logging, a command-line interface (CLI), and a Python API.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 📌 Features
|
|
27
|
+
|
|
28
|
+
- **Bidirectional Conversion**:
|
|
29
|
+
- **Forward Pipeline**: Convert binary masks to YOLO format labels (supports standard axis-aligned or advanced minimum area rotated bounding boxes).
|
|
30
|
+
- **Reverse Pipeline**: Reconstruct binary masks from YOLO labels.
|
|
31
|
+
- **Automatic Dependency Installer**: Missing required packages (`numpy`, `opencv-python`, `pillow`, `pandas`, `matplotlib`) are automatically detected and installed via `pip` upon package import or script execution.
|
|
32
|
+
- **Robust Exception Handling**: Try-catch blocks wrapped around file I/O, contour finding, and resizing to prevent application crashes on corrupted or missing files.
|
|
33
|
+
- **Dynamic Dataset Matching**: Read classification mappings (in **CSV** or **JSON** format) to automatically assign multi-class IDs matching standard dataset schemas (like the ISIC dataset).
|
|
34
|
+
- **YOLO Dataset Splitting**: Automatically shuffles and partitions images & labels into training and testing sets with customizable split ratios, creating standard `data.yaml` configs.
|
|
35
|
+
- **Overlay Visualizer**: Overlay bounding boxes and class indicators directly onto source images for annotation inspection.
|
|
36
|
+
- **Dual Interface**: Use as a command-line application (`segment-toolkit`) or import as a Python library (`import segment_toolkit`).
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 📂 Installation
|
|
41
|
+
|
|
42
|
+
To install the toolkit locally in editable mode (missing dependencies will install automatically):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install -e .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Manual Installation
|
|
49
|
+
If you prefer to install dependencies manually before installing the toolkit:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install -r requirements.txt
|
|
53
|
+
pip install .
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 🚀 Usage
|
|
59
|
+
|
|
60
|
+
### 1. Command Line Interface (CLI)
|
|
61
|
+
|
|
62
|
+
The package installs a console script called `segment-toolkit`.
|
|
63
|
+
|
|
64
|
+
#### Convert Masks to YOLO Labels
|
|
65
|
+
- **Single File Conversion**:
|
|
66
|
+
```bash
|
|
67
|
+
segment-toolkit mask-to-yolo \
|
|
68
|
+
--image images/ISIC_0024310.jpg \
|
|
69
|
+
--mask mask/ISIC_0024310_segmentation.png \
|
|
70
|
+
--output-txt labels/ISIC_0024310.txt \
|
|
71
|
+
--class-id 4
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- **Batch Directory Conversion**:
|
|
75
|
+
```bash
|
|
76
|
+
segment-toolkit mask-to-yolo \
|
|
77
|
+
--image-dir images/ \
|
|
78
|
+
--mask-dir mask/ \
|
|
79
|
+
--output-dir labels/ \
|
|
80
|
+
--ground-truth GroundTruth.csv
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- **Options**:
|
|
84
|
+
- `--rotated`: Use rotated minimum area rectangles (`cv2.minAreaRect`) instead of standard axis-aligned rectangles.
|
|
85
|
+
- `--resize WIDTH HEIGHT`: Set target size for image and mask resizing (default: `640 640`).
|
|
86
|
+
|
|
87
|
+
#### Convert YOLO Labels to Masks
|
|
88
|
+
- **Single File Conversion**:
|
|
89
|
+
```bash
|
|
90
|
+
segment-toolkit yolo-to-mask \
|
|
91
|
+
--label labels/ISIC_0024310.txt \
|
|
92
|
+
--output-mask masks_reconstructed/ISIC_0024310_segmentation.png
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- **Batch Directory Conversion**:
|
|
96
|
+
```bash
|
|
97
|
+
segment-toolkit yolo-to-mask \
|
|
98
|
+
--label-dir labels/ \
|
|
99
|
+
--output-dir masks_reconstructed/
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### Visualize Bounding Boxes
|
|
103
|
+
Draw YOLO labels on top of the original image:
|
|
104
|
+
```bash
|
|
105
|
+
segment-toolkit visualize \
|
|
106
|
+
--image images/ISIC_0024310.jpg \
|
|
107
|
+
--label labels/ISIC_0024310.txt \
|
|
108
|
+
--output visualization.png
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### Split Dataset
|
|
112
|
+
Organize folders into YOLO-compliant structure (`dataset/train` and `dataset/test` splits) and output `data.yaml`:
|
|
113
|
+
```bash
|
|
114
|
+
segment-toolkit split \
|
|
115
|
+
--images images/ \
|
|
116
|
+
--labels labels/ \
|
|
117
|
+
--output dataset/ \
|
|
118
|
+
--ratio 0.8 \
|
|
119
|
+
--seed 42
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### 2. Ground Truth Formats
|
|
125
|
+
|
|
126
|
+
The `--ground-truth` parameter in batch conversion supports both CSV and JSON formats.
|
|
127
|
+
|
|
128
|
+
#### CSV Format
|
|
129
|
+
Assumes the first column contains the image identifier/filename, and the subsequent columns represent binary indicator classes (where `1` indicates class presence).
|
|
130
|
+
Example `GroundTruth.csv`:
|
|
131
|
+
```csv
|
|
132
|
+
image,MEL,NV,BCC,AKIEC,BKL,DF,VASC
|
|
133
|
+
ISIC_0024306,0,1,0,0,0,0,0
|
|
134
|
+
ISIC_0024310,1,0,0,0,0,0,0
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### JSON Format
|
|
138
|
+
Supports three distinct schemas:
|
|
139
|
+
|
|
140
|
+
1. **Flat Dictionary (Format A)**:
|
|
141
|
+
Maps image IDs directly to class integers or class name strings.
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"ISIC_0024306": 5,
|
|
145
|
+
"ISIC_0024310": "MEL"
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
2. **Nested Indicators (Format B)**:
|
|
150
|
+
Maps image IDs to dictionaries of binary class indicators.
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"ISIC_0024306": { "MEL": 0, "NV": 1, "BCC": 0 },
|
|
154
|
+
"ISIC_0024310": { "MEL": 1, "NV": 0, "BCC": 0 }
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
3. **List of Records (Format C)**:
|
|
159
|
+
A list of objects containing image IDs and class descriptors.
|
|
160
|
+
```json
|
|
161
|
+
[
|
|
162
|
+
{ "image": "ISIC_0024306", "class_id": 5 },
|
|
163
|
+
{ "image": "ISIC_0024310", "MEL": 1, "NV": 0 }
|
|
164
|
+
]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
*Note: Class name strings (like `"MEL"`, `"NV"`) are automatically mapped to standard ISIC IDs (`AKIEC=0, BCC=1, BKL=2, DF=3, MEL=4, NV=5, VASC=6`). Custom column names default to index-based IDs.*
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### 3. Python API
|
|
172
|
+
|
|
173
|
+
Import classes directly into your code to programmatically build custom pipelines:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from segment_toolkit import MaskToYoloConverter, YoloToMaskConverter
|
|
177
|
+
|
|
178
|
+
# 1. Convert mask to YOLO label
|
|
179
|
+
yolo_conv = MaskToYoloConverter(target_size=(640, 640), bbox_type="standard")
|
|
180
|
+
yolo_conv.convert_single(
|
|
181
|
+
image_path="images/ISIC_0024310.jpg",
|
|
182
|
+
mask_path="mask/ISIC_0024310_segmentation.png",
|
|
183
|
+
output_txt_path="labels/ISIC_0024310.txt",
|
|
184
|
+
class_id=4
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# 2. Batch convert a folder of masks with a JSON ground truth
|
|
188
|
+
yolo_conv.convert_dataset(
|
|
189
|
+
images_dir="images",
|
|
190
|
+
masks_dir="mask",
|
|
191
|
+
output_labels_dir="labels",
|
|
192
|
+
ground_truth="GroundTruth.json"
|
|
193
|
+
)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 🧠 Technical Details
|
|
199
|
+
|
|
200
|
+
### Coordinate Conversion Math
|
|
201
|
+
|
|
202
|
+
#### Bounding Box Center Calculation (Pixel Space)
|
|
203
|
+
For standard bounding boxes, the pixel coordinates from `boundingRect` are $(x_{min}, y_{min}, w_{pixel}, h_{pixel})$.
|
|
204
|
+
$$\text{Center } X \quad x_{center} = x_{min} + \frac{w_{pixel}}{2.0}$$
|
|
205
|
+
$$\text{Center } Y \quad y_{center} = y_{min} + \frac{h_{pixel}}{2.0}$$
|
|
206
|
+
|
|
207
|
+
#### Coordinate Normalization (YOLO Format)
|
|
208
|
+
All coordinates are normalized to the range $[0.0, 1.0]$:
|
|
209
|
+
$$x_{norm} = \frac{x_{center}}{img\_width}, \quad y_{norm} = \frac{y_{center}}{img\_height}$$
|
|
210
|
+
$$w_{norm} = \frac{w_{pixel}}{img\_width}, \quad h_{norm} = \frac{h_{pixel}}{img\_height}$$
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 🧑💻 Author
|
|
215
|
+
**Zakria Gamal**
|
|
216
|
+
- Computer Vision & AI Engineer
|
|
217
|
+
- 🧠 LinkedIn: [Zakria Gamal](https://www.linkedin.com/in/zkaria-gamal-82b486267/)
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Segment Toolkit 🛠️
|
|
2
|
+
|
|
3
|
+
A modern, robust, and premium Python package designed to bridge the gap between pixel-level **binary segmentation masks** and **YOLO bounding box labels**. It provides a bidirectional pipeline with exception handling, extensive logging, a command-line interface (CLI), and a Python API.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📌 Features
|
|
8
|
+
|
|
9
|
+
- **Bidirectional Conversion**:
|
|
10
|
+
- **Forward Pipeline**: Convert binary masks to YOLO format labels (supports standard axis-aligned or advanced minimum area rotated bounding boxes).
|
|
11
|
+
- **Reverse Pipeline**: Reconstruct binary masks from YOLO labels.
|
|
12
|
+
- **Automatic Dependency Installer**: Missing required packages (`numpy`, `opencv-python`, `pillow`, `pandas`, `matplotlib`) are automatically detected and installed via `pip` upon package import or script execution.
|
|
13
|
+
- **Robust Exception Handling**: Try-catch blocks wrapped around file I/O, contour finding, and resizing to prevent application crashes on corrupted or missing files.
|
|
14
|
+
- **Dynamic Dataset Matching**: Read classification mappings (in **CSV** or **JSON** format) to automatically assign multi-class IDs matching standard dataset schemas (like the ISIC dataset).
|
|
15
|
+
- **YOLO Dataset Splitting**: Automatically shuffles and partitions images & labels into training and testing sets with customizable split ratios, creating standard `data.yaml` configs.
|
|
16
|
+
- **Overlay Visualizer**: Overlay bounding boxes and class indicators directly onto source images for annotation inspection.
|
|
17
|
+
- **Dual Interface**: Use as a command-line application (`segment-toolkit`) or import as a Python library (`import segment_toolkit`).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 📂 Installation
|
|
22
|
+
|
|
23
|
+
To install the toolkit locally in editable mode (missing dependencies will install automatically):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install -e .
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Manual Installation
|
|
30
|
+
If you prefer to install dependencies manually before installing the toolkit:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install -r requirements.txt
|
|
34
|
+
pip install .
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 🚀 Usage
|
|
40
|
+
|
|
41
|
+
### 1. Command Line Interface (CLI)
|
|
42
|
+
|
|
43
|
+
The package installs a console script called `segment-toolkit`.
|
|
44
|
+
|
|
45
|
+
#### Convert Masks to YOLO Labels
|
|
46
|
+
- **Single File Conversion**:
|
|
47
|
+
```bash
|
|
48
|
+
segment-toolkit mask-to-yolo \
|
|
49
|
+
--image images/ISIC_0024310.jpg \
|
|
50
|
+
--mask mask/ISIC_0024310_segmentation.png \
|
|
51
|
+
--output-txt labels/ISIC_0024310.txt \
|
|
52
|
+
--class-id 4
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- **Batch Directory Conversion**:
|
|
56
|
+
```bash
|
|
57
|
+
segment-toolkit mask-to-yolo \
|
|
58
|
+
--image-dir images/ \
|
|
59
|
+
--mask-dir mask/ \
|
|
60
|
+
--output-dir labels/ \
|
|
61
|
+
--ground-truth GroundTruth.csv
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- **Options**:
|
|
65
|
+
- `--rotated`: Use rotated minimum area rectangles (`cv2.minAreaRect`) instead of standard axis-aligned rectangles.
|
|
66
|
+
- `--resize WIDTH HEIGHT`: Set target size for image and mask resizing (default: `640 640`).
|
|
67
|
+
|
|
68
|
+
#### Convert YOLO Labels to Masks
|
|
69
|
+
- **Single File Conversion**:
|
|
70
|
+
```bash
|
|
71
|
+
segment-toolkit yolo-to-mask \
|
|
72
|
+
--label labels/ISIC_0024310.txt \
|
|
73
|
+
--output-mask masks_reconstructed/ISIC_0024310_segmentation.png
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- **Batch Directory Conversion**:
|
|
77
|
+
```bash
|
|
78
|
+
segment-toolkit yolo-to-mask \
|
|
79
|
+
--label-dir labels/ \
|
|
80
|
+
--output-dir masks_reconstructed/
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### Visualize Bounding Boxes
|
|
84
|
+
Draw YOLO labels on top of the original image:
|
|
85
|
+
```bash
|
|
86
|
+
segment-toolkit visualize \
|
|
87
|
+
--image images/ISIC_0024310.jpg \
|
|
88
|
+
--label labels/ISIC_0024310.txt \
|
|
89
|
+
--output visualization.png
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### Split Dataset
|
|
93
|
+
Organize folders into YOLO-compliant structure (`dataset/train` and `dataset/test` splits) and output `data.yaml`:
|
|
94
|
+
```bash
|
|
95
|
+
segment-toolkit split \
|
|
96
|
+
--images images/ \
|
|
97
|
+
--labels labels/ \
|
|
98
|
+
--output dataset/ \
|
|
99
|
+
--ratio 0.8 \
|
|
100
|
+
--seed 42
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### 2. Ground Truth Formats
|
|
106
|
+
|
|
107
|
+
The `--ground-truth` parameter in batch conversion supports both CSV and JSON formats.
|
|
108
|
+
|
|
109
|
+
#### CSV Format
|
|
110
|
+
Assumes the first column contains the image identifier/filename, and the subsequent columns represent binary indicator classes (where `1` indicates class presence).
|
|
111
|
+
Example `GroundTruth.csv`:
|
|
112
|
+
```csv
|
|
113
|
+
image,MEL,NV,BCC,AKIEC,BKL,DF,VASC
|
|
114
|
+
ISIC_0024306,0,1,0,0,0,0,0
|
|
115
|
+
ISIC_0024310,1,0,0,0,0,0,0
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### JSON Format
|
|
119
|
+
Supports three distinct schemas:
|
|
120
|
+
|
|
121
|
+
1. **Flat Dictionary (Format A)**:
|
|
122
|
+
Maps image IDs directly to class integers or class name strings.
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"ISIC_0024306": 5,
|
|
126
|
+
"ISIC_0024310": "MEL"
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
2. **Nested Indicators (Format B)**:
|
|
131
|
+
Maps image IDs to dictionaries of binary class indicators.
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"ISIC_0024306": { "MEL": 0, "NV": 1, "BCC": 0 },
|
|
135
|
+
"ISIC_0024310": { "MEL": 1, "NV": 0, "BCC": 0 }
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
3. **List of Records (Format C)**:
|
|
140
|
+
A list of objects containing image IDs and class descriptors.
|
|
141
|
+
```json
|
|
142
|
+
[
|
|
143
|
+
{ "image": "ISIC_0024306", "class_id": 5 },
|
|
144
|
+
{ "image": "ISIC_0024310", "MEL": 1, "NV": 0 }
|
|
145
|
+
]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
*Note: Class name strings (like `"MEL"`, `"NV"`) are automatically mapped to standard ISIC IDs (`AKIEC=0, BCC=1, BKL=2, DF=3, MEL=4, NV=5, VASC=6`). Custom column names default to index-based IDs.*
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
### 3. Python API
|
|
153
|
+
|
|
154
|
+
Import classes directly into your code to programmatically build custom pipelines:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from segment_toolkit import MaskToYoloConverter, YoloToMaskConverter
|
|
158
|
+
|
|
159
|
+
# 1. Convert mask to YOLO label
|
|
160
|
+
yolo_conv = MaskToYoloConverter(target_size=(640, 640), bbox_type="standard")
|
|
161
|
+
yolo_conv.convert_single(
|
|
162
|
+
image_path="images/ISIC_0024310.jpg",
|
|
163
|
+
mask_path="mask/ISIC_0024310_segmentation.png",
|
|
164
|
+
output_txt_path="labels/ISIC_0024310.txt",
|
|
165
|
+
class_id=4
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# 2. Batch convert a folder of masks with a JSON ground truth
|
|
169
|
+
yolo_conv.convert_dataset(
|
|
170
|
+
images_dir="images",
|
|
171
|
+
masks_dir="mask",
|
|
172
|
+
output_labels_dir="labels",
|
|
173
|
+
ground_truth="GroundTruth.json"
|
|
174
|
+
)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 🧠 Technical Details
|
|
180
|
+
|
|
181
|
+
### Coordinate Conversion Math
|
|
182
|
+
|
|
183
|
+
#### Bounding Box Center Calculation (Pixel Space)
|
|
184
|
+
For standard bounding boxes, the pixel coordinates from `boundingRect` are $(x_{min}, y_{min}, w_{pixel}, h_{pixel})$.
|
|
185
|
+
$$\text{Center } X \quad x_{center} = x_{min} + \frac{w_{pixel}}{2.0}$$
|
|
186
|
+
$$\text{Center } Y \quad y_{center} = y_{min} + \frac{h_{pixel}}{2.0}$$
|
|
187
|
+
|
|
188
|
+
#### Coordinate Normalization (YOLO Format)
|
|
189
|
+
All coordinates are normalized to the range $[0.0, 1.0]$:
|
|
190
|
+
$$x_{norm} = \frac{x_{center}}{img\_width}, \quad y_{norm} = \frac{y_{center}}{img\_height}$$
|
|
191
|
+
$$w_{norm} = \frac{w_{pixel}}{img\_width}, \quad h_{norm} = \frac{h_{pixel}}{img\_height}$$
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 🧑💻 Author
|
|
196
|
+
**Zakria Gamal**
|
|
197
|
+
- Computer Vision & AI Engineer
|
|
198
|
+
- 🧠 LinkedIn: [Zakria Gamal](https://www.linkedin.com/in/zkaria-gamal-82b486267/)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Segment Toolkit: A library and CLI tool for converting binary segmentation masks to YOLO labels and vice versa.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "1.0.0"
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import subprocess
|
|
9
|
+
|
|
10
|
+
def _ensure_dependencies():
|
|
11
|
+
"""
|
|
12
|
+
Checks for required external modules and attempts to install them via pip if missing.
|
|
13
|
+
"""
|
|
14
|
+
dependencies = {
|
|
15
|
+
"numpy": "numpy",
|
|
16
|
+
"cv2": "opencv-python",
|
|
17
|
+
"PIL": "pillow",
|
|
18
|
+
"pandas": "pandas",
|
|
19
|
+
"matplotlib": "matplotlib"
|
|
20
|
+
}
|
|
21
|
+
missing = []
|
|
22
|
+
for module, pip_name in dependencies.items():
|
|
23
|
+
try:
|
|
24
|
+
__import__(module)
|
|
25
|
+
except ImportError:
|
|
26
|
+
missing.append(pip_name)
|
|
27
|
+
|
|
28
|
+
if missing:
|
|
29
|
+
print(f"[segment_toolkit] Missing required package(s): {', '.join(missing)}", file=sys.stderr)
|
|
30
|
+
print("[segment_toolkit] Attempting to auto-install dependencies...", file=sys.stderr)
|
|
31
|
+
try:
|
|
32
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", *missing])
|
|
33
|
+
print("[segment_toolkit] Dependencies installed successfully.", file=sys.stderr)
|
|
34
|
+
except Exception as err:
|
|
35
|
+
print(f"[segment_toolkit] Error: Auto-installation of dependencies failed: {err}", file=sys.stderr)
|
|
36
|
+
print("[segment_toolkit] Please install them manually using: pip install -r requirements.txt", file=sys.stderr)
|
|
37
|
+
|
|
38
|
+
# Check and install dependencies before importing other submodules
|
|
39
|
+
_ensure_dependencies()
|
|
40
|
+
|
|
41
|
+
from .source import MaskToYoloConverter, YoloToMaskConverter
|
|
42
|
+
from .helpers import (
|
|
43
|
+
safe_read_image,
|
|
44
|
+
preprocess_image,
|
|
45
|
+
get_largest_contour,
|
|
46
|
+
calculate_bbox,
|
|
47
|
+
normalize_coordinates,
|
|
48
|
+
denormalize_coordinates,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
"MaskToYoloConverter",
|
|
53
|
+
"YoloToMaskConverter",
|
|
54
|
+
"safe_read_image",
|
|
55
|
+
"preprocess_image",
|
|
56
|
+
"get_largest_contour",
|
|
57
|
+
"calculate_bbox",
|
|
58
|
+
"normalize_coordinates",
|
|
59
|
+
"denormalize_coordinates",
|
|
60
|
+
]
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command Line Interface (CLI) for segment_toolkit.
|
|
3
|
+
Exposes mask-to-yolo, yolo-to-mask, split, and visualize commands.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import sys
|
|
8
|
+
import logging
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
from .source import MaskToYoloConverter, YoloToMaskConverter
|
|
12
|
+
|
|
13
|
+
# Configure basic logging
|
|
14
|
+
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def parse_args(args: List[str]) -> argparse.Namespace:
|
|
19
|
+
"""
|
|
20
|
+
Parses command-line arguments.
|
|
21
|
+
"""
|
|
22
|
+
parser = argparse.ArgumentParser(
|
|
23
|
+
description="Segment Toolkit: Convert segmentation masks to/from YOLO format annotations."
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
subparsers = parser.add_subparsers(dest="command", required=True, help="Subcommands")
|
|
27
|
+
|
|
28
|
+
# subcommand: mask-to-yolo
|
|
29
|
+
m2y_parser = subparsers.add_parser(
|
|
30
|
+
"mask-to-yolo", help="Convert binary mask(s) to YOLO format labels."
|
|
31
|
+
)
|
|
32
|
+
m2y_parser.add_argument("--image", type=str, help="Path to a single input image.")
|
|
33
|
+
m2y_parser.add_argument("--mask", type=str, help="Path to a single input binary mask.")
|
|
34
|
+
m2y_parser.add_argument("--output-txt", type=str, help="Output file path for single YOLO label txt.")
|
|
35
|
+
|
|
36
|
+
m2y_parser.add_argument("--image-dir", type=str, help="Directory containing input images.")
|
|
37
|
+
m2y_parser.add_argument("--mask-dir", type=str, help="Directory containing input masks.")
|
|
38
|
+
m2y_parser.add_argument("--output-dir", type=str, help="Directory to save generated YOLO label files.")
|
|
39
|
+
|
|
40
|
+
m2y_parser.add_argument("--class-id", type=int, default=0, help="Default Class ID to write (default: 0).")
|
|
41
|
+
m2y_parser.add_argument(
|
|
42
|
+
"--ground-truth",
|
|
43
|
+
type=str,
|
|
44
|
+
help="Path to GroundTruth.csv mapping images to multi-class columns.",
|
|
45
|
+
)
|
|
46
|
+
m2y_parser.add_argument(
|
|
47
|
+
"--rotated",
|
|
48
|
+
action="store_true",
|
|
49
|
+
help="Use rotated minimum area rectangle (minAreaRect) instead of axis-aligned.",
|
|
50
|
+
)
|
|
51
|
+
m2y_parser.add_argument(
|
|
52
|
+
"--resize",
|
|
53
|
+
type=int,
|
|
54
|
+
nargs=2,
|
|
55
|
+
default=[640, 640],
|
|
56
|
+
metavar=("WIDTH", "HEIGHT"),
|
|
57
|
+
help="Target dimensions for resizing (default: 640 640).",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# subcommand: yolo-to-mask
|
|
61
|
+
y2m_parser = subparsers.add_parser(
|
|
62
|
+
"yolo-to-mask", help="Convert YOLO format label(s) to binary mask(s)."
|
|
63
|
+
)
|
|
64
|
+
y2m_parser.add_argument("--label", type=str, help="Path to a single input YOLO label txt file.")
|
|
65
|
+
y2m_parser.add_argument("--output-mask", type=str, help="Output path for single binary mask png.")
|
|
66
|
+
|
|
67
|
+
y2m_parser.add_argument("--label-dir", type=str, help="Directory containing YOLO label txt files.")
|
|
68
|
+
y2m_parser.add_argument("--output-dir", type=str, help="Directory to save generated binary mask png files.")
|
|
69
|
+
|
|
70
|
+
y2m_parser.add_argument(
|
|
71
|
+
"--resize",
|
|
72
|
+
type=int,
|
|
73
|
+
nargs=2,
|
|
74
|
+
default=[640, 640],
|
|
75
|
+
metavar=("WIDTH", "HEIGHT"),
|
|
76
|
+
help="Output mask dimensions (default: 640 640).",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# subcommand: split
|
|
80
|
+
split_parser = subparsers.add_parser(
|
|
81
|
+
"split", help="Randomly split a dataset of images and labels into train/test subfolders."
|
|
82
|
+
)
|
|
83
|
+
split_parser.add_argument("--images", type=str, required=True, help="Directory of source images.")
|
|
84
|
+
split_parser.add_argument("--labels", type=str, required=True, help="Directory of source YOLO label txt files.")
|
|
85
|
+
split_parser.add_argument("--output", type=str, required=True, help="Root directory for split dataset outputs.")
|
|
86
|
+
split_parser.add_argument(
|
|
87
|
+
"--ratio", type=float, default=0.8, help="Split ratio for training partition (default: 0.8)."
|
|
88
|
+
)
|
|
89
|
+
split_parser.add_argument("--seed", type=int, default=42, help="Seed value for reproduction (default: 42).")
|
|
90
|
+
|
|
91
|
+
# subcommand: visualize
|
|
92
|
+
vis_parser = subparsers.add_parser(
|
|
93
|
+
"visualize", help="Draw bounding boxes from a YOLO label file onto the source image."
|
|
94
|
+
)
|
|
95
|
+
vis_parser.add_argument("--image", type=str, required=True, help="Path to the source image.")
|
|
96
|
+
vis_parser.add_argument("--label", type=str, required=True, help="Path to the YOLO label file.")
|
|
97
|
+
vis_parser.add_argument("--output", type=str, required=True, help="Path to save output visualization image.")
|
|
98
|
+
vis_parser.add_argument(
|
|
99
|
+
"--resize",
|
|
100
|
+
type=int,
|
|
101
|
+
nargs=2,
|
|
102
|
+
default=[640, 640],
|
|
103
|
+
metavar=("WIDTH", "HEIGHT"),
|
|
104
|
+
help="Resize image for visualization (default: 640 640).",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return parser.parse_args(args)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def main(args: Optional[List[str]] = None) -> int:
|
|
111
|
+
"""
|
|
112
|
+
Main entry point for command-line execution.
|
|
113
|
+
"""
|
|
114
|
+
if args is None:
|
|
115
|
+
args = sys.argv[1:]
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
parsed = parse_args(args)
|
|
119
|
+
|
|
120
|
+
if parsed.command == "mask-to-yolo":
|
|
121
|
+
converter = MaskToYoloConverter(
|
|
122
|
+
target_size=(parsed.resize[0], parsed.resize[1]),
|
|
123
|
+
bbox_type="rotated" if parsed.rotated else "standard",
|
|
124
|
+
)
|
|
125
|
+
# Check if processing single file or folder
|
|
126
|
+
if parsed.image or parsed.mask or parsed.output_txt:
|
|
127
|
+
if not (parsed.image and parsed.mask and parsed.output_txt):
|
|
128
|
+
logger.error("Error: --image, --mask, and --output-txt must all be specified for single conversion.")
|
|
129
|
+
return 1
|
|
130
|
+
success = converter.convert_single(
|
|
131
|
+
parsed.image, parsed.mask, parsed.output_txt, class_id=parsed.class_id
|
|
132
|
+
)
|
|
133
|
+
return 0 if success else 1
|
|
134
|
+
elif parsed.image_dir or parsed.mask_dir or parsed.output_dir:
|
|
135
|
+
if not (parsed.image_dir and parsed.mask_dir and parsed.output_dir):
|
|
136
|
+
logger.error("Error: --image-dir, --mask-dir, and --output-dir must all be specified for folder conversion.")
|
|
137
|
+
return 1
|
|
138
|
+
converter.convert_dataset(
|
|
139
|
+
parsed.image_dir,
|
|
140
|
+
parsed.mask_dir,
|
|
141
|
+
parsed.output_dir,
|
|
142
|
+
default_class_id=parsed.class_id,
|
|
143
|
+
ground_truth=parsed.ground_truth,
|
|
144
|
+
)
|
|
145
|
+
return 0
|
|
146
|
+
else:
|
|
147
|
+
logger.error("Error: Must specify either single file arguments (--image, --mask, --output-txt) or directory arguments (--image-dir, --mask-dir, --output-dir).")
|
|
148
|
+
return 1
|
|
149
|
+
|
|
150
|
+
elif parsed.command == "yolo-to-mask":
|
|
151
|
+
converter = YoloToMaskConverter(target_size=(parsed.resize[0], parsed.resize[1]))
|
|
152
|
+
if parsed.label or parsed.output_mask:
|
|
153
|
+
if not (parsed.label and parsed.output_mask):
|
|
154
|
+
logger.error("Error: Both --label and --output-mask must be specified for single conversion.")
|
|
155
|
+
return 1
|
|
156
|
+
success = converter.convert_single(parsed.label, parsed.output_mask)
|
|
157
|
+
return 0 if success else 1
|
|
158
|
+
elif parsed.label_dir or parsed.output_dir:
|
|
159
|
+
if not (parsed.label_dir and parsed.output_dir):
|
|
160
|
+
logger.error("Error: Both --label-dir and --output-dir must be specified for folder conversion.")
|
|
161
|
+
return 1
|
|
162
|
+
converter.convert_dataset(parsed.label_dir, parsed.output_dir)
|
|
163
|
+
return 0
|
|
164
|
+
else:
|
|
165
|
+
logger.error("Error: Must specify either single file arguments (--label, --output-mask) or directory arguments (--label-dir, --output-dir).")
|
|
166
|
+
return 1
|
|
167
|
+
|
|
168
|
+
elif parsed.command == "split":
|
|
169
|
+
converter = MaskToYoloConverter()
|
|
170
|
+
converter.split_dataset(
|
|
171
|
+
images_dir=parsed.images,
|
|
172
|
+
labels_dir=parsed.labels,
|
|
173
|
+
output_dataset_dir=parsed.output,
|
|
174
|
+
split_ratio=parsed.ratio,
|
|
175
|
+
seed=parsed.seed,
|
|
176
|
+
)
|
|
177
|
+
return 0
|
|
178
|
+
|
|
179
|
+
elif parsed.command == "visualize":
|
|
180
|
+
converter = YoloToMaskConverter(target_size=(parsed.resize[0], parsed.resize[1]))
|
|
181
|
+
success = converter.visualize_label(parsed.image, parsed.label, parsed.output)
|
|
182
|
+
return 0 if success else 1
|
|
183
|
+
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.error(f"Execution failed: {str(e)}")
|
|
186
|
+
return 1
|
|
187
|
+
|
|
188
|
+
return 0
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
sys.exit(main())
|