Spaces:
Running
Running
add esc and cnt to poly
Browse files
comic_panel_extractor/static/annotator.html
CHANGED
|
@@ -504,8 +504,8 @@
|
|
| 504 |
<div class="section-title">Annotation Mode</div>
|
| 505 |
<div class="form-field">
|
| 506 |
<select class="form-select" id="annotationMode">
|
| 507 |
-
<option value="bbox">Bounding Box (YOLO)</option>
|
| 508 |
<option value="segmentation">Segmentation (YOLO-seg)</option>
|
|
|
|
| 509 |
</select>
|
| 510 |
</div>
|
| 511 |
<div class="form-field">
|
|
@@ -634,7 +634,7 @@
|
|
| 634 |
this.currentBox = null;
|
| 635 |
|
| 636 |
// Segmentation state
|
| 637 |
-
this.annotationMode = '
|
| 638 |
this.currentPolygon = null;
|
| 639 |
this.isDrawingPolygon = false;
|
| 640 |
this.polygonPoints = [];
|
|
@@ -1684,6 +1684,28 @@
|
|
| 1684 |
|
| 1685 |
// Modified onKeyDown method with new resize functionality
|
| 1686 |
onKeyDown(e) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1687 |
if (!this.currentImage || this.selectedBoxIndex < 0) return;
|
| 1688 |
|
| 1689 |
const resizeDistance = 5; // Fixed resize distance
|
|
@@ -1750,6 +1772,25 @@
|
|
| 1750 |
break;
|
| 1751 |
}
|
| 1752 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1753 |
|
| 1754 |
// New method to resize selected box
|
| 1755 |
resizeSelectedBox(deltaWidth, deltaHeight) {
|
|
|
|
| 504 |
<div class="section-title">Annotation Mode</div>
|
| 505 |
<div class="form-field">
|
| 506 |
<select class="form-select" id="annotationMode">
|
|
|
|
| 507 |
<option value="segmentation">Segmentation (YOLO-seg)</option>
|
| 508 |
+
<option value="bbox">Bounding Box (YOLO)</option>
|
| 509 |
</select>
|
| 510 |
</div>
|
| 511 |
<div class="form-field">
|
|
|
|
| 634 |
this.currentBox = null;
|
| 635 |
|
| 636 |
// Segmentation state
|
| 637 |
+
this.annotationMode = 'segmentation'; // 'bbox' or 'segmentation'
|
| 638 |
this.currentPolygon = null;
|
| 639 |
this.isDrawingPolygon = false;
|
| 640 |
this.polygonPoints = [];
|
|
|
|
| 1684 |
|
| 1685 |
// Modified onKeyDown method with new resize functionality
|
| 1686 |
onKeyDown(e) {
|
| 1687 |
+
// Handle escape key for canceling drawing
|
| 1688 |
+
if (e.key === 'Escape') {
|
| 1689 |
+
e.preventDefault();
|
| 1690 |
+
|
| 1691 |
+
// Cancel polygon drawing if in progress
|
| 1692 |
+
if (this.annotationMode === 'segmentation' && this.isDrawingPolygon) {
|
| 1693 |
+
this.cancelPolygonDrawing();
|
| 1694 |
+
return;
|
| 1695 |
+
}
|
| 1696 |
+
|
| 1697 |
+
// Cancel bbox drawing if in progress
|
| 1698 |
+
if (this.annotationMode === 'bbox' && this.isDrawing) {
|
| 1699 |
+
this.cancelBboxDrawing();
|
| 1700 |
+
return;
|
| 1701 |
+
}
|
| 1702 |
+
|
| 1703 |
+
// Clear selection if nothing is being drawn
|
| 1704 |
+
this.selectedBoxIndex = -1;
|
| 1705 |
+
this.updateSelectedBoxInfo();
|
| 1706 |
+
this.drawCanvas();
|
| 1707 |
+
return;
|
| 1708 |
+
}
|
| 1709 |
if (!this.currentImage || this.selectedBoxIndex < 0) return;
|
| 1710 |
|
| 1711 |
const resizeDistance = 5; // Fixed resize distance
|
|
|
|
| 1772 |
break;
|
| 1773 |
}
|
| 1774 |
}
|
| 1775 |
+
cancelPolygonDrawing() {
|
| 1776 |
+
this.isDrawingPolygon = false;
|
| 1777 |
+
this.polygonPoints = [];
|
| 1778 |
+
this.currentPolygon = null;
|
| 1779 |
+
this.selectedBoxIndex = -1;
|
| 1780 |
+
this.updateSelectedBoxInfo();
|
| 1781 |
+
this.drawCanvas();
|
| 1782 |
+
this.showAlert('Polygon drawing canceled', 'info');
|
| 1783 |
+
}
|
| 1784 |
+
|
| 1785 |
+
cancelBboxDrawing() {
|
| 1786 |
+
this.isDrawing = false;
|
| 1787 |
+
this.currentBox = null;
|
| 1788 |
+
this.selectedBoxIndex = -1;
|
| 1789 |
+
this.updateSelectedBoxInfo();
|
| 1790 |
+
this.drawCanvas();
|
| 1791 |
+
this.showAlert('Bounding box drawing canceled', 'info');
|
| 1792 |
+
}
|
| 1793 |
+
|
| 1794 |
|
| 1795 |
// New method to resize selected box
|
| 1796 |
resizeSelectedBox(deltaWidth, deltaHeight) {
|
comic_panel_extractor/train.py
CHANGED
|
@@ -8,6 +8,71 @@ import os
|
|
| 8 |
from pathlib import Path
|
| 9 |
import shutil
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
def create_filtered_dataset(original_dataset_path, output_filtered_dataset_path):
|
| 12 |
"""
|
| 13 |
Create a filtered dataset with only images that have non-empty labels
|
|
@@ -56,6 +121,7 @@ def create_filtered_dataset(original_dataset_path, output_filtered_dataset_path)
|
|
| 56 |
shutil.copy2(img_file, output_images_dir / img_file.name)
|
| 57 |
# Copy label
|
| 58 |
shutil.copy2(label_file, output_labels_dir / label_file.name)
|
|
|
|
| 59 |
copied_count += 1
|
| 60 |
else:
|
| 61 |
print(f"Skipping {img_file.name} - empty label file")
|
|
|
|
| 8 |
from pathlib import Path
|
| 9 |
import shutil
|
| 10 |
|
| 11 |
+
def convert_box_to_polygon(label_file: Path):
|
| 12 |
+
"""
|
| 13 |
+
Converts YOLO box-format labels (class xc yc w h) to YOLO polygon-format labels
|
| 14 |
+
for segmentation. Creates a 4-point polygon representing the bounding box.
|
| 15 |
+
Overwrites the label file in place if conversion is needed.
|
| 16 |
+
"""
|
| 17 |
+
if not label_file.exists():
|
| 18 |
+
return
|
| 19 |
+
|
| 20 |
+
new_lines = []
|
| 21 |
+
changed = False
|
| 22 |
+
|
| 23 |
+
with open(label_file, "r") as f:
|
| 24 |
+
for line in f:
|
| 25 |
+
line = line.strip()
|
| 26 |
+
if not line: # Skip empty lines
|
| 27 |
+
continue
|
| 28 |
+
|
| 29 |
+
parts = line.split()
|
| 30 |
+
|
| 31 |
+
if len(parts) == 5:
|
| 32 |
+
# Box format β convert to polygon
|
| 33 |
+
try:
|
| 34 |
+
cls = int(float(parts[0])) # Class should be integer
|
| 35 |
+
xc, yc, bw, bh = map(float, parts[1:])
|
| 36 |
+
|
| 37 |
+
# Calculate corner points (clockwise from top-left)
|
| 38 |
+
x1 = max(0.0, min(1.0, xc - bw / 2)) # top-left x
|
| 39 |
+
y1 = max(0.0, min(1.0, yc - bh / 2)) # top-left y
|
| 40 |
+
x2 = max(0.0, min(1.0, xc + bw / 2)) # top-right x
|
| 41 |
+
y2 = max(0.0, min(1.0, yc - bh / 2)) # top-right y
|
| 42 |
+
x3 = max(0.0, min(1.0, xc + bw / 2)) # bottom-right x
|
| 43 |
+
y3 = max(0.0, min(1.0, yc + bh / 2)) # bottom-right y
|
| 44 |
+
x4 = max(0.0, min(1.0, xc - bw / 2)) # bottom-left x
|
| 45 |
+
y4 = max(0.0, min(1.0, yc + bh / 2)) # bottom-left y
|
| 46 |
+
|
| 47 |
+
# Format: class x1 y1 x2 y2 x3 y3 x4 y4
|
| 48 |
+
polygon_line = f"{cls} {x1:.6f} {y1:.6f} {x2:.6f} {y2:.6f} {x3:.6f} {y3:.6f} {x4:.6f} {y4:.6f}"
|
| 49 |
+
new_lines.append(polygon_line)
|
| 50 |
+
changed = True
|
| 51 |
+
|
| 52 |
+
except (ValueError, IndexError):
|
| 53 |
+
# If parsing fails, keep original line
|
| 54 |
+
new_lines.append(line)
|
| 55 |
+
|
| 56 |
+
elif len(parts) > 5 and len(parts) % 2 == 1:
|
| 57 |
+
# Already polygon format (odd number of parts: class + pairs of coordinates)
|
| 58 |
+
try:
|
| 59 |
+
cls = int(float(parts[0]))
|
| 60 |
+
coords = [float(x) for x in parts[1:]]
|
| 61 |
+
# Clamp coordinates to [0,1] range
|
| 62 |
+
coords = [max(0.0, min(1.0, coord)) for coord in coords]
|
| 63 |
+
coord_str = " ".join(f"{coord:.6f}" for coord in coords)
|
| 64 |
+
new_lines.append(f"{cls} {coord_str}")
|
| 65 |
+
except (ValueError, IndexError):
|
| 66 |
+
# If parsing fails, keep original line
|
| 67 |
+
new_lines.append(line)
|
| 68 |
+
else:
|
| 69 |
+
# Unknown format, keep as-is
|
| 70 |
+
new_lines.append(line)
|
| 71 |
+
|
| 72 |
+
if changed:
|
| 73 |
+
with open(label_file, "w") as f:
|
| 74 |
+
f.write("\n".join(new_lines) + "\n")
|
| 75 |
+
|
| 76 |
def create_filtered_dataset(original_dataset_path, output_filtered_dataset_path):
|
| 77 |
"""
|
| 78 |
Create a filtered dataset with only images that have non-empty labels
|
|
|
|
| 121 |
shutil.copy2(img_file, output_images_dir / img_file.name)
|
| 122 |
# Copy label
|
| 123 |
shutil.copy2(label_file, output_labels_dir / label_file.name)
|
| 124 |
+
convert_box_to_polygon(output_labels_dir / label_file.name)
|
| 125 |
copied_count += 1
|
| 126 |
else:
|
| 127 |
print(f"Skipping {img_file.name} - empty label file")
|
comic_panel_extractor/yolo_manager.py
CHANGED
|
@@ -68,8 +68,8 @@ class YOLOManager:
|
|
| 68 |
print(f"π¦ Loading model from: {weights_path}")
|
| 69 |
self.model = YOLO(weights_path)
|
| 70 |
else:
|
| 71 |
-
print("β¨ Loading pretrained model '
|
| 72 |
-
self.model = YOLO(f"{Config.current_path}/
|
| 73 |
return self.model
|
| 74 |
|
| 75 |
def train(self,
|
|
|
|
| 68 |
print(f"π¦ Loading model from: {weights_path}")
|
| 69 |
self.model = YOLO(weights_path)
|
| 70 |
else:
|
| 71 |
+
print("β¨ Loading pretrained model 'yolov12s-seg.pt'")
|
| 72 |
+
self.model = YOLO(f"{Config.current_path}/yolov12s-seg.pt")
|
| 73 |
return self.model
|
| 74 |
|
| 75 |
def train(self,
|