laudari commited on
Commit
5468c68
·
verified ·
1 Parent(s): aefb53c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +246 -37
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
- new_shapes.append({
 
 
180
  "label": label,
181
  "points": poly,
182
  "group_id": None,
183
  "shape_type": "polygon",
184
  "flags": {},
185
- "confidence": 1.0
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, # Use actual augmented image height
198
- "imageWidth": aug_width # Use actual augmented image width
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  }
200
 
201
  if self.debug:
202
- logger.info(f"[DEBUG] Created augmented data: {len(new_shapes)} shapes, size: {aug_width}x{aug_height}")
 
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 polygons and masks"""
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
- hues = [i / num_labels for i in range(num_labels)]
585
  label_color_map = {}
586
- for label, hue in zip(unique_labels, hues):
587
- rgb = colorsys.hsv_to_rgb(hue, 1.0, 1.0)
588
- rgb = tuple(int(c * 255) for c in rgb)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- # Draw semi-transparent mask
605
- mask = np.zeros((height, width), dtype=np.uint8)
606
- cv2.fillPoly(mask, [points], 1)
607
- colored_mask = np.zeros_like(aug_image_rgb)
608
- colored_mask[mask == 1] = color
609
- alpha = 0.3
610
- overlay = cv2.addWeighted(overlay, 1.0, colored_mask, alpha, 0.0)
611
 
612
  # Draw polygon outline
613
- cv2.polylines(overlay, [points], isClosed=True, color=color, thickness=2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- # Save JSON data
634
- json_filename = f"metadata_{idx}.json"
635
- json_str = json.dumps(result['json_data'], indent=2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
636
  zip_file.writestr(json_filename, json_str)
637
 
638
- # Add summary metadata
639
  summary = {
640
- 'total_augmentations': len(self.augmented_results),
641
- 'generation_timestamp': datetime.now().isoformat(),
642
- 'augmentation_summary': [result['metadata'] for result in self.augmented_results]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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():