Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -176,30 +176,63 @@ class PolygonAugmentation:
|
|
| 176 |
for poly, label in zip(aug_polygons, aug_labels):
|
| 177 |
if not poly or len(poly) < 3:
|
| 178 |
continue
|
| 179 |
-
|
|
|
|
|
|
|
| 180 |
"label": label,
|
| 181 |
"points": poly,
|
| 182 |
"group_id": None,
|
| 183 |
"shape_type": "polygon",
|
| 184 |
"flags": {},
|
| 185 |
-
"
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
|
| 188 |
# Get actual dimensions from augmented image
|
| 189 |
aug_height, aug_width = aug_image.shape[:2]
|
| 190 |
|
|
|
|
| 191 |
aug_data = {
|
| 192 |
"version": original_data.get("version", "5.0.1"),
|
| 193 |
"flags": original_data.get("flags", {}),
|
| 194 |
"shapes": new_shapes,
|
| 195 |
"imagePath": aug_img_name,
|
| 196 |
-
"imageData": None,
|
| 197 |
-
"imageHeight": aug_height,
|
| 198 |
-
"imageWidth": aug_width
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
}
|
| 200 |
|
| 201 |
if self.debug:
|
| 202 |
-
logger.info(f"[DEBUG] Created
|
|
|
|
| 203 |
|
| 204 |
return aug_data
|
| 205 |
|
|
@@ -574,26 +607,90 @@ class PolygonAugmentation:
|
|
| 574 |
return results
|
| 575 |
|
| 576 |
def create_visualization(self, aug_image, aug_data):
|
| 577 |
-
"""Create visualization with colored
|
| 578 |
-
# Create a dynamic color map for unique labels
|
| 579 |
unique_labels = list(set(shape['label'] for shape in aug_data['shapes']))
|
| 580 |
if not unique_labels:
|
| 581 |
label_color_map = {"unknown": (0, 255, 0)}
|
| 582 |
else:
|
| 583 |
num_labels = len(unique_labels)
|
| 584 |
-
|
| 585 |
label_color_map = {}
|
| 586 |
-
for
|
| 587 |
-
|
| 588 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 589 |
label_color_map[label] = rgb
|
| 590 |
|
| 591 |
# Convert augmented image to RGB for visualization
|
| 592 |
aug_image_rgb = cv2.cvtColor(aug_image, cv2.COLOR_BGR2RGB)
|
| 593 |
overlay = aug_image_rgb.copy()
|
| 594 |
-
|
| 595 |
-
# Create masks and outlines for visualization
|
| 596 |
height, width = aug_image.shape[:2]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 597 |
for shape in aug_data['shapes']:
|
| 598 |
label = shape['label']
|
| 599 |
color = label_color_map.get(label, (0, 255, 0))
|
|
@@ -601,49 +698,161 @@ class PolygonAugmentation:
|
|
| 601 |
if len(points) < 3:
|
| 602 |
continue
|
| 603 |
|
| 604 |
-
#
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
|
| 612 |
# Draw polygon outline
|
| 613 |
-
cv2.polylines(overlay, [points], isClosed=True, color=color, thickness=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
|
| 615 |
return Image.fromarray(overlay)
|
| 616 |
|
| 617 |
def create_download_package(self):
|
| 618 |
-
"""Create a zip file with all augmented images and JSON files"""
|
| 619 |
if not self.augmented_results:
|
| 620 |
return None
|
| 621 |
|
| 622 |
zip_buffer = io.BytesIO()
|
| 623 |
|
| 624 |
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
| 625 |
-
# Add all augmented images and their JSON files
|
| 626 |
for idx, result in enumerate(self.augmented_results):
|
| 627 |
-
# Save image
|
| 628 |
img_buffer = io.BytesIO()
|
| 629 |
-
result['image'].save(img_buffer, format='PNG')
|
| 630 |
-
img_filename = result['metadata']['filename']
|
| 631 |
-
zip_file.writestr(img_filename, img_buffer.getvalue())
|
| 632 |
|
| 633 |
-
#
|
| 634 |
-
|
| 635 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 636 |
zip_file.writestr(json_filename, json_str)
|
| 637 |
|
| 638 |
-
# Add summary metadata
|
| 639 |
summary = {
|
| 640 |
-
'
|
| 641 |
-
|
| 642 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 643 |
}
|
| 644 |
-
zip_file.writestr('augmentation_summary.json', json.dumps(summary, indent=2))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 645 |
|
| 646 |
zip_buffer.seek(0)
|
|
|
|
| 647 |
return zip_buffer.getvalue()
|
| 648 |
|
| 649 |
def create_interface():
|
|
|
|
| 176 |
for poly, label in zip(aug_polygons, aug_labels):
|
| 177 |
if not poly or len(poly) < 3:
|
| 178 |
continue
|
| 179 |
+
|
| 180 |
+
# Create LabelMe format shape
|
| 181 |
+
shape_data = {
|
| 182 |
"label": label,
|
| 183 |
"points": poly,
|
| 184 |
"group_id": None,
|
| 185 |
"shape_type": "polygon",
|
| 186 |
"flags": {},
|
| 187 |
+
"description": "",
|
| 188 |
+
"attributes": {},
|
| 189 |
+
"iscrowd": 0,
|
| 190 |
+
"difficult": 0
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
# Add additional metadata for special polygon types
|
| 194 |
+
if label.lower() in ['ring', 'donut', 'annulus', 'circle', 'round']:
|
| 195 |
+
shape_data["attributes"]["polygon_type"] = "ring"
|
| 196 |
+
elif label.lower() in ['background', 'bg', 'back']:
|
| 197 |
+
shape_data["attributes"]["polygon_type"] = "background"
|
| 198 |
+
else:
|
| 199 |
+
shape_data["attributes"]["polygon_type"] = "object"
|
| 200 |
+
|
| 201 |
+
new_shapes.append(shape_data)
|
| 202 |
|
| 203 |
# Get actual dimensions from augmented image
|
| 204 |
aug_height, aug_width = aug_image.shape[:2]
|
| 205 |
|
| 206 |
+
# Create LabelMe compatible JSON structure
|
| 207 |
aug_data = {
|
| 208 |
"version": original_data.get("version", "5.0.1"),
|
| 209 |
"flags": original_data.get("flags", {}),
|
| 210 |
"shapes": new_shapes,
|
| 211 |
"imagePath": aug_img_name,
|
| 212 |
+
"imageData": None, # Explicitly set to None as requested
|
| 213 |
+
"imageHeight": aug_height,
|
| 214 |
+
"imageWidth": aug_width,
|
| 215 |
+
"imageDepth": 3 if len(aug_image.shape) == 3 else 1,
|
| 216 |
+
|
| 217 |
+
# Additional LabelMe metadata
|
| 218 |
+
"lineColor": [0, 255, 0, 128],
|
| 219 |
+
"fillColor": [255, 0, 0, 128],
|
| 220 |
+
"textSize": 10,
|
| 221 |
+
"textColor": [0, 0, 0, 255],
|
| 222 |
+
|
| 223 |
+
# Augmentation metadata
|
| 224 |
+
"augmentation": {
|
| 225 |
+
"augmented": True,
|
| 226 |
+
"augmentation_id": aug_id,
|
| 227 |
+
"original_file": original_data.get("imagePath", "unknown"),
|
| 228 |
+
"augmentation_timestamp": datetime.now().isoformat(),
|
| 229 |
+
"augmentation_tool": "PolygonAugmentation v1.0"
|
| 230 |
+
}
|
| 231 |
}
|
| 232 |
|
| 233 |
if self.debug:
|
| 234 |
+
logger.info(f"[DEBUG] Created LabelMe JSON: {len(new_shapes)} shapes, size: {aug_width}x{aug_height}")
|
| 235 |
+
logger.info(f"[DEBUG] Shape types: {[s['attributes'].get('polygon_type', 'unknown') for s in new_shapes]}")
|
| 236 |
|
| 237 |
return aug_data
|
| 238 |
|
|
|
|
| 607 |
return results
|
| 608 |
|
| 609 |
def create_visualization(self, aug_image, aug_data):
|
| 610 |
+
"""Create visualization with colored polygon masks and outlines for each class"""
|
| 611 |
+
# Create a dynamic color map for unique labels with better color distribution
|
| 612 |
unique_labels = list(set(shape['label'] for shape in aug_data['shapes']))
|
| 613 |
if not unique_labels:
|
| 614 |
label_color_map = {"unknown": (0, 255, 0)}
|
| 615 |
else:
|
| 616 |
num_labels = len(unique_labels)
|
| 617 |
+
# Create more distinct colors using different hue ranges
|
| 618 |
label_color_map = {}
|
| 619 |
+
for i, label in enumerate(unique_labels):
|
| 620 |
+
if label.lower() in ['background', 'bg', 'back']:
|
| 621 |
+
# Background gets a neutral gray-blue color
|
| 622 |
+
rgb = (100, 149, 237) # Cornflower blue with low opacity
|
| 623 |
+
elif 'ring' in label.lower() or 'donut' in label.lower():
|
| 624 |
+
# Ring/donut shapes get purple-pink colors
|
| 625 |
+
hue = 0.8 + (i * 0.1) % 0.2 # Purple range
|
| 626 |
+
rgb = colorsys.hsv_to_rgb(hue, 0.8, 0.9)
|
| 627 |
+
rgb = tuple(int(c * 255) for c in rgb)
|
| 628 |
+
else:
|
| 629 |
+
# Regular objects get distributed colors across the spectrum
|
| 630 |
+
hue = (i * 0.618033988749895) % 1.0 # Golden ratio for better distribution
|
| 631 |
+
saturation = 0.7 + (i % 3) * 0.1 # Vary saturation
|
| 632 |
+
value = 0.8 + (i % 2) * 0.15 # Vary brightness
|
| 633 |
+
rgb = colorsys.hsv_to_rgb(hue, saturation, value)
|
| 634 |
+
rgb = tuple(int(c * 255) for c in rgb)
|
| 635 |
+
|
| 636 |
label_color_map[label] = rgb
|
| 637 |
|
| 638 |
# Convert augmented image to RGB for visualization
|
| 639 |
aug_image_rgb = cv2.cvtColor(aug_image, cv2.COLOR_BGR2RGB)
|
| 640 |
overlay = aug_image_rgb.copy()
|
|
|
|
|
|
|
| 641 |
height, width = aug_image.shape[:2]
|
| 642 |
+
|
| 643 |
+
# Create a composite mask to handle overlapping polygons
|
| 644 |
+
composite_mask = np.zeros((height, width, 3), dtype=np.uint8)
|
| 645 |
+
|
| 646 |
+
# Group shapes by label for better visualization
|
| 647 |
+
shapes_by_label = {}
|
| 648 |
+
for shape in aug_data['shapes']:
|
| 649 |
+
label = shape['label']
|
| 650 |
+
if label not in shapes_by_label:
|
| 651 |
+
shapes_by_label[label] = []
|
| 652 |
+
shapes_by_label[label].append(shape)
|
| 653 |
+
|
| 654 |
+
# Process each label group
|
| 655 |
+
for label, shapes in shapes_by_label.items():
|
| 656 |
+
color = label_color_map.get(label, (0, 255, 0))
|
| 657 |
+
|
| 658 |
+
# Create mask for all polygons of this label
|
| 659 |
+
label_mask = np.zeros((height, width), dtype=np.uint8)
|
| 660 |
+
|
| 661 |
+
for shape in shapes:
|
| 662 |
+
points = np.array(shape['points'], dtype=np.int32)
|
| 663 |
+
if len(points) < 3:
|
| 664 |
+
continue
|
| 665 |
+
|
| 666 |
+
# Fill the polygon area
|
| 667 |
+
cv2.fillPoly(label_mask, [points], 255)
|
| 668 |
+
|
| 669 |
+
# Apply color to the mask areas
|
| 670 |
+
if label_mask.sum() > 0: # Only if mask has content
|
| 671 |
+
# Determine alpha based on label type
|
| 672 |
+
if label.lower() in ['background', 'bg', 'back']:
|
| 673 |
+
alpha = 0.15 # Lower opacity for background
|
| 674 |
+
elif 'ring' in label.lower() or 'donut' in label.lower():
|
| 675 |
+
alpha = 0.4 # Medium opacity for rings
|
| 676 |
+
else:
|
| 677 |
+
alpha = 0.35 # Standard opacity for objects
|
| 678 |
+
|
| 679 |
+
# Create colored mask
|
| 680 |
+
colored_mask = np.zeros_like(aug_image_rgb)
|
| 681 |
+
colored_mask[label_mask == 255] = color
|
| 682 |
+
|
| 683 |
+
# Blend with overlay
|
| 684 |
+
mask_area = label_mask == 255
|
| 685 |
+
overlay[mask_area] = cv2.addWeighted(
|
| 686 |
+
overlay[mask_area],
|
| 687 |
+
1.0 - alpha,
|
| 688 |
+
colored_mask[mask_area],
|
| 689 |
+
alpha,
|
| 690 |
+
0
|
| 691 |
+
)
|
| 692 |
+
|
| 693 |
+
# Draw polygon outlines with thicker lines for better visibility
|
| 694 |
for shape in aug_data['shapes']:
|
| 695 |
label = shape['label']
|
| 696 |
color = label_color_map.get(label, (0, 255, 0))
|
|
|
|
| 698 |
if len(points) < 3:
|
| 699 |
continue
|
| 700 |
|
| 701 |
+
# Determine line thickness based on polygon type
|
| 702 |
+
if label.lower() in ['background', 'bg', 'back']:
|
| 703 |
+
thickness = 1 # Thinner lines for background
|
| 704 |
+
elif 'ring' in label.lower() or 'donut' in label.lower():
|
| 705 |
+
thickness = 3 # Thicker lines for rings to show structure
|
| 706 |
+
else:
|
| 707 |
+
thickness = 2 # Standard thickness
|
| 708 |
|
| 709 |
# Draw polygon outline
|
| 710 |
+
cv2.polylines(overlay, [points], isClosed=True, color=color, thickness=thickness)
|
| 711 |
+
|
| 712 |
+
# Add label text near the polygon
|
| 713 |
+
if len(points) > 0:
|
| 714 |
+
# Find a good position for the label
|
| 715 |
+
moments = cv2.moments(points)
|
| 716 |
+
if moments['m00'] != 0:
|
| 717 |
+
cx = int(moments['m10'] / moments['m00'])
|
| 718 |
+
cy = int(moments['m01'] / moments['m00'])
|
| 719 |
+
else:
|
| 720 |
+
cx, cy = points[0][0], points[0][1]
|
| 721 |
+
|
| 722 |
+
# Ensure text position is within image bounds
|
| 723 |
+
cx = max(10, min(cx, width - 50))
|
| 724 |
+
cy = max(20, min(cy, height - 10))
|
| 725 |
+
|
| 726 |
+
# Add text background for better readability
|
| 727 |
+
font = cv2.FONT_HERSHEY_SIMPLEX
|
| 728 |
+
font_scale = 0.4
|
| 729 |
+
text_thickness = 1
|
| 730 |
+
text_size = cv2.getTextSize(label, font, font_scale, text_thickness)[0]
|
| 731 |
+
|
| 732 |
+
# Draw background rectangle
|
| 733 |
+
cv2.rectangle(overlay,
|
| 734 |
+
(cx - 2, cy - text_size[1] - 4),
|
| 735 |
+
(cx + text_size[0] + 2, cy + 2),
|
| 736 |
+
(0, 0, 0), -1)
|
| 737 |
+
|
| 738 |
+
# Draw text
|
| 739 |
+
cv2.putText(overlay, label, (cx, cy - 2), font, font_scale, color, text_thickness)
|
| 740 |
+
|
| 741 |
+
if self.debug:
|
| 742 |
+
logger.info(f"[DEBUG] Created visualization with {len(unique_labels)} unique labels: {list(unique_labels)}")
|
| 743 |
|
| 744 |
return Image.fromarray(overlay)
|
| 745 |
|
| 746 |
def create_download_package(self):
|
| 747 |
+
"""Create a zip file with all augmented images and proper LabelMe JSON files"""
|
| 748 |
if not self.augmented_results:
|
| 749 |
return None
|
| 750 |
|
| 751 |
zip_buffer = io.BytesIO()
|
| 752 |
|
| 753 |
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
| 754 |
+
# Add all augmented images and their corresponding LabelMe JSON files
|
| 755 |
for idx, result in enumerate(self.augmented_results):
|
| 756 |
+
# Save augmented image
|
| 757 |
img_buffer = io.BytesIO()
|
|
|
|
|
|
|
|
|
|
| 758 |
|
| 759 |
+
# Convert PIL image back to OpenCV format for saving
|
| 760 |
+
img_cv = np.array(result['image'])
|
| 761 |
+
img_bgr = cv2.cvtColor(img_cv, cv2.COLOR_RGB2BGR)
|
| 762 |
+
|
| 763 |
+
# Encode image as PNG
|
| 764 |
+
success, encoded_img = cv2.imencode('.png', img_bgr)
|
| 765 |
+
if success:
|
| 766 |
+
zip_file.writestr(result['metadata']['filename'], encoded_img.tobytes())
|
| 767 |
+
else:
|
| 768 |
+
# Fallback to PIL saving
|
| 769 |
+
result['image'].save(img_buffer, format='PNG')
|
| 770 |
+
zip_file.writestr(result['metadata']['filename'], img_buffer.getvalue())
|
| 771 |
+
|
| 772 |
+
# Save corresponding LabelMe JSON file
|
| 773 |
+
json_filename = result['metadata']['filename'].replace('.png', '.json')
|
| 774 |
+
|
| 775 |
+
# Create a clean LabelMe JSON without internal metadata
|
| 776 |
+
clean_json_data = {
|
| 777 |
+
"version": result['json_data'].get("version", "5.0.1"),
|
| 778 |
+
"flags": result['json_data'].get("flags", {}),
|
| 779 |
+
"shapes": result['json_data']['shapes'],
|
| 780 |
+
"imagePath": result['metadata']['filename'],
|
| 781 |
+
"imageData": None,
|
| 782 |
+
"imageHeight": result['json_data']['imageHeight'],
|
| 783 |
+
"imageWidth": result['json_data']['imageWidth']
|
| 784 |
+
}
|
| 785 |
+
|
| 786 |
+
json_str = json.dumps(clean_json_data, indent=2, ensure_ascii=False)
|
| 787 |
zip_file.writestr(json_filename, json_str)
|
| 788 |
|
| 789 |
+
# Add comprehensive summary metadata
|
| 790 |
summary = {
|
| 791 |
+
'package_info': {
|
| 792 |
+
'total_augmentations': len(self.augmented_results),
|
| 793 |
+
'generation_timestamp': datetime.now().isoformat(),
|
| 794 |
+
'generator': 'PolygonAugmentation v1.0',
|
| 795 |
+
'format': 'LabelMe JSON + PNG images'
|
| 796 |
+
},
|
| 797 |
+
'augmentation_summary': [
|
| 798 |
+
{
|
| 799 |
+
'filename': result['metadata']['filename'],
|
| 800 |
+
'json_file': result['metadata']['filename'].replace('.png', '.json'),
|
| 801 |
+
'augmentation_type': result['metadata']['augmentation_type'],
|
| 802 |
+
'parameter_value': result['metadata']['parameter_value'],
|
| 803 |
+
'polygon_count': len(result['json_data']['shapes']),
|
| 804 |
+
'image_size': f"{result['json_data']['imageWidth']}x{result['json_data']['imageHeight']}",
|
| 805 |
+
'timestamp': result['metadata']['timestamp'],
|
| 806 |
+
'labels': list(set([shape['label'] for shape in result['json_data']['shapes']]))
|
| 807 |
+
}
|
| 808 |
+
for result in self.augmented_results
|
| 809 |
+
],
|
| 810 |
+
'statistics': {
|
| 811 |
+
'unique_augmentation_types': list(set([r['metadata']['augmentation_type'] for r in self.augmented_results])),
|
| 812 |
+
'total_polygons': sum([len(r['json_data']['shapes']) for r in self.augmented_results]),
|
| 813 |
+
'unique_labels': list(set([
|
| 814 |
+
shape['label']
|
| 815 |
+
for result in self.augmented_results
|
| 816 |
+
for shape in result['json_data']['shapes']
|
| 817 |
+
])),
|
| 818 |
+
'average_polygons_per_image': sum([len(r['json_data']['shapes']) for r in self.augmented_results]) / len(self.augmented_results)
|
| 819 |
+
}
|
| 820 |
}
|
| 821 |
+
zip_file.writestr('augmentation_summary.json', json.dumps(summary, indent=2, ensure_ascii=False))
|
| 822 |
+
|
| 823 |
+
# Add README for the package
|
| 824 |
+
readme_content = f"""# Augmented Dataset Package
|
| 825 |
+
|
| 826 |
+
## Overview
|
| 827 |
+
This package contains {len(self.augmented_results)} augmented images with their corresponding LabelMe annotation files.
|
| 828 |
+
|
| 829 |
+
## Contents
|
| 830 |
+
- **Images**: PNG format augmented images
|
| 831 |
+
- **Annotations**: LabelMe JSON format annotation files
|
| 832 |
+
- **Summary**: augmentation_summary.json with detailed metadata
|
| 833 |
+
|
| 834 |
+
## File Structure
|
| 835 |
+
- Each image file (*.png) has a corresponding annotation file (*.json) with the same base name
|
| 836 |
+
- All annotations are in standard LabelMe format without embedded image data
|
| 837 |
+
|
| 838 |
+
## Statistics
|
| 839 |
+
- Total augmented images: {len(self.augmented_results)}
|
| 840 |
+
- Total polygons: {sum([len(r['json_data']['shapes']) for r in self.augmented_results])}
|
| 841 |
+
- Unique labels: {list(set([shape['label'] for result in self.augmented_results for shape in result['json_data']['shapes']]))}
|
| 842 |
+
- Augmentation types used: {list(set([r['metadata']['augmentation_type'] for r in self.augmented_results]))}
|
| 843 |
+
|
| 844 |
+
## Usage
|
| 845 |
+
1. Extract the ZIP file
|
| 846 |
+
2. Load images and annotations using any tool that supports LabelMe format
|
| 847 |
+
3. Use the augmentation_summary.json for batch processing or analysis
|
| 848 |
+
|
| 849 |
+
Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
| 850 |
+
Tool: PolygonAugmentation v1.0
|
| 851 |
+
"""
|
| 852 |
+
zip_file.writestr('README.md', readme_content)
|
| 853 |
|
| 854 |
zip_buffer.seek(0)
|
| 855 |
+
logger.info(f"Created download package with {len(self.augmented_results)} image-annotation pairs")
|
| 856 |
return zip_buffer.getvalue()
|
| 857 |
|
| 858 |
def create_interface():
|