Repository navigation
Expand file tree
/
Copy pathLabeledBox.swift
More file actions
126 lines (109 loc) · 4.05 KB
/
Copy pathLabeledBox.swift
File metadata and controls
126 lines (109 loc) · 4.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import SwiftUI
struct LabeledBox: Identifiable, Codable {
let id: UUID
var label: String
var rect: CGRect // Keep for backward compatibility
var isSaved: Bool
var detectionMethod: String?
// NEW: Precise segmentation data
var contourPoints: [CGPoint]? // Polygon outline points
var maskData: Data? // Raw mask bitmap data
var maskSize: CGSize? // Original mask dimensions
var hasPreciseSegmentation: Bool {
return contourPoints != nil || maskData != nil
}
// Convenience initializers
init(id: UUID = UUID(), label: String, rect: CGRect, isSaved: Bool, detectionMethod: String? = nil) {
self.id = id
self.label = label
self.rect = rect
self.isSaved = isSaved
self.detectionMethod = detectionMethod
contourPoints = nil
maskData = nil
maskSize = nil
}
// New initializer with precise segmentation
init(id: UUID = UUID(), label: String, rect: CGRect, isSaved: Bool, detectionMethod: String? = nil, contourPoints: [CGPoint]? = nil, maskData: Data? = nil, maskSize: CGSize? = nil) {
self.id = id
self.label = label
self.rect = rect
self.isSaved = isSaved
self.detectionMethod = detectionMethod
self.contourPoints = contourPoints
self.maskData = maskData
self.maskSize = maskSize
}
// Get display shape - use contour if available, otherwise rect
var displayPath: Path {
if let points = contourPoints, !points.isEmpty {
// Create path from contour points
var path = Path()
guard let firstPoint = points.first else { return rectanglePath }
path.move(to: firstPoint)
for point in points.dropFirst() {
path.addLine(to: point)
}
path.closeSubpath()
return path
} else {
// Fallback to rectangle
return rectanglePath
}
}
private var rectanglePath: Path {
Path(CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: rect.height))
}
// Convert normalized contour points to screen coordinates (SMART COORDINATE DETECTION)
func screenContourPoints(imageSize: CGSize, canvasSize: CGSize) -> [CGPoint] {
guard let points = contourPoints else { return [] }
let scaleX = canvasSize.width / imageSize.width
let scaleY = canvasSize.height / imageSize.height
let scale = min(scaleX, scaleY)
let scaledImageSize = CGSize(
width: imageSize.width * scale,
height: imageSize.height * scale
)
let offsetX = (canvasSize.width - scaledImageSize.width) / 2
let offsetY = (canvasSize.height - scaledImageSize.height) / 2
return points.map { point in
// FIXED: Direct coordinate mapping
CGPoint(
x: point.x * scaledImageSize.width + offsetX,
y: point.y * scaledImageSize.height + offsetY
)
}
}
// Get tight bounding rect from contour points
var tightBoundingRect: CGRect {
guard let points = contourPoints, !points.isEmpty else {
return rect
}
let minX = points.map { $0.x }.min() ?? rect.minX
let maxX = points.map { $0.x }.max() ?? rect.maxX
let minY = points.map { $0.y }.min() ?? rect.minY
let maxY = points.map { $0.y }.max() ?? rect.maxY
return CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY)
}
}
// MARK: - Extensions
// Helper for creating SAM2 boxes with precise segmentation
extension LabeledBox {
static func sam2Box(
id: UUID = UUID(),
label: String,
boundingRect: CGRect,
contourPoints: [CGPoint],
detectionMethod: String = "SAM2 CoreML",
isSaved: Bool = false
) -> LabeledBox {
return LabeledBox(
id: id,
label: label,
rect: boundingRect,
isSaved: isSaved,
detectionMethod: detectionMethod,
contourPoints: contourPoints
)
}
}