From 949bc8270f459bf0b8e8e2e9f55e3727a9f023e4 Mon Sep 17 00:00:00 2001 From: john-rocky Date: Wed, 6 Aug 2025 18:39:01 +0900 Subject: [PATCH 01/10] Add threshold configuration methods to YOLO and YOLOView classes - Add setNumItemsThreshold, setConfidenceThreshold, setIouThreshold methods - Add getter methods for current threshold values - Add setThresholds method for batch configuration - Update README.md with threshold configuration documentation - Resolves issue #149: Enable programmatic threshold configuration --- Sources/YOLO/README.md | 57 +++++++++++++++++++++++++++- Sources/YOLO/YOLO.swift | 69 ++++++++++++++++++++++++++++++++++ Sources/YOLO/YOLOView.swift | 74 +++++++++++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 1 deletion(-) diff --git a/Sources/YOLO/README.md b/Sources/YOLO/README.md index 02d762c3..a3d65d05 100644 --- a/Sources/YOLO/README.md +++ b/Sources/YOLO/README.md @@ -231,7 +231,62 @@ class CameraViewController: UIViewController { } ``` -With just a few lines of code, you can integrate real-time, YOLO-based inference into your application’s camera feed. For more advanced use cases, explore the customization options available for these components. +With just a few lines of code, you can integrate real-time, YOLO-based inference into your application's camera feed. For more advanced use cases, explore the customization options available for these components. + +### Threshold Configuration + +Both `YOLO` and `YOLOView`/`YOLOCamera` classes provide methods to configure detection thresholds: + +#### YOLO Class Methods + +```swift +// Set individual thresholds +model.setNumItemsThreshold(100) // Maximum number of detections (default: 30) +model.setConfidenceThreshold(0.5) // Confidence threshold 0.0-1.0 (default: 0.25) +model.setIouThreshold(0.6) // IoU threshold for NMS 0.0-1.0 (default: 0.4) + +// Or set all thresholds at once +model.setThresholds(numItems: 100, confidence: 0.5, iou: 0.6) + +// Get current threshold values +let numItems = model.getNumItemsThreshold() // Returns Int? +let confidence = model.getConfidenceThreshold() // Returns Double? +let iou = model.getIouThreshold() // Returns Double? +``` + +#### YOLOView/YOLOCamera Methods + +```swift +// For UIKit (YOLOView) +yoloView.setNumItemsThreshold(50) +yoloView.setConfidenceThreshold(0.3) +yoloView.setIouThreshold(0.5) + +// Or set all at once +yoloView.setThresholds(numItems: 50, confidence: 0.3, iou: 0.5) + +// Get current values +let numItems = yoloView.getNumItemsThreshold() // Returns Int +let confidence = yoloView.getConfidenceThreshold() // Returns Double +let iou = yoloView.getIouThreshold() // Returns Double + +// For SwiftUI (YOLOCamera) - set during initialization +YOLOCamera( + modelPathOrName: "yolo11n", + task: .detect, + cameraPosition: .back +) +.onAppear { + // Access the underlying view if needed for threshold configuration + // Note: Direct threshold configuration for YOLOCamera may require + // additional implementation or accessing the underlying YOLOView +} +``` + +**Note:** +- `numItemsThreshold`: Controls the maximum number of detections returned +- `confidenceThreshold`: Filters out detections below this confidence level +- `iouThreshold`: Controls Non-Maximum Suppression overlap threshold ## ⚙️ How to Obtain YOLO Core ML Models diff --git a/Sources/YOLO/YOLO.swift b/Sources/YOLO/YOLO.swift index 89df3636..5479e373 100644 --- a/Sources/YOLO/YOLO.swift +++ b/Sources/YOLO/YOLO.swift @@ -112,6 +112,75 @@ public class YOLO { } } } + + // MARK: - Threshold Configuration Methods + + /// Sets the maximum number of detection items to include in results. + /// - Parameter numItems: The maximum number of items to include (default is 30). + public func setNumItemsThreshold(_ numItems: Int) { + if let basePredictor = predictor as? BasePredictor { + basePredictor.setNumItemsThreshold(numItems: numItems) + } + } + + /// Gets the current maximum number of detection items. + /// - Returns: The current threshold value, or nil if not applicable. + public func getNumItemsThreshold() -> Int? { + return (predictor as? BasePredictor)?.numItemsThreshold + } + + /// Sets the confidence threshold for filtering results. + /// - Parameter confidence: The confidence threshold value (0.0 to 1.0, default is 0.25). + public func setConfidenceThreshold(_ confidence: Double) { + guard confidence >= 0.0 && confidence <= 1.0 else { + print("Warning: Confidence threshold should be between 0.0 and 1.0") + return + } + if let basePredictor = predictor as? BasePredictor { + basePredictor.setConfidenceThreshold(confidence: confidence) + } + } + + /// Gets the current confidence threshold. + /// - Returns: The current confidence threshold value, or nil if not applicable. + public func getConfidenceThreshold() -> Double? { + return (predictor as? BasePredictor)?.confidenceThreshold + } + + /// Sets the IoU (Intersection over Union) threshold for non-maximum suppression. + /// - Parameter iou: The IoU threshold value (0.0 to 1.0, default is 0.4). + public func setIouThreshold(_ iou: Double) { + guard iou >= 0.0 && iou <= 1.0 else { + print("Warning: IoU threshold should be between 0.0 and 1.0") + return + } + if let basePredictor = predictor as? BasePredictor { + basePredictor.setIouThreshold(iou: iou) + } + } + + /// Gets the current IoU threshold. + /// - Returns: The current IoU threshold value, or nil if not applicable. + public func getIouThreshold() -> Double? { + return (predictor as? BasePredictor)?.iouThreshold + } + + /// Sets all thresholds at once. + /// - Parameters: + /// - numItems: The maximum number of items to include. + /// - confidence: The confidence threshold value (0.0 to 1.0). + /// - iou: The IoU threshold value (0.0 to 1.0). + public func setThresholds(numItems: Int? = nil, confidence: Double? = nil, iou: Double? = nil) { + if let numItems = numItems { + setNumItemsThreshold(numItems) + } + if let confidence = confidence { + setConfidenceThreshold(confidence) + } + if let iou = iou { + setIouThreshold(iou) + } + } public func callAsFunction(_ uiImage: UIImage, returnAnnotatedImage: Bool = true) -> YOLOResult { let ciImage = CIImage(image: uiImage)! diff --git a/Sources/YOLO/YOLOView.swift b/Sources/YOLO/YOLOView.swift index a60430ef..5a42c6c9 100644 --- a/Sources/YOLO/YOLOView.swift +++ b/Sources/YOLO/YOLOView.swift @@ -319,6 +319,80 @@ public class YOLOView: UIView, VideoCaptureDelegate { public func resume() { videoCapture.start() } + + // MARK: - Threshold Configuration Methods + + /// Sets the maximum number of detection items to include in results. + /// - Parameter numItems: The maximum number of items to include (default is 30). + public func setNumItemsThreshold(_ numItems: Int) { + sliderNumItems.value = Float(numItems) + if let predictor = videoCapture.predictor as? BasePredictor { + predictor.setNumItemsThreshold(numItems: numItems) + } + } + + /// Gets the current maximum number of detection items. + /// - Returns: The current threshold value. + public func getNumItemsThreshold() -> Int { + return Int(sliderNumItems.value) + } + + /// Sets the confidence threshold for filtering results. + /// - Parameter confidence: The confidence threshold value (0.0 to 1.0, default is 0.25). + public func setConfidenceThreshold(_ confidence: Double) { + guard confidence >= 0.0 && confidence <= 1.0 else { + print("Warning: Confidence threshold should be between 0.0 and 1.0") + return + } + sliderConf.value = Float(confidence) + labelSliderConf.text = String(format: "%.2f Confidence Threshold", confidence) + if let predictor = videoCapture.predictor as? BasePredictor { + predictor.setConfidenceThreshold(confidence: confidence) + } + } + + /// Gets the current confidence threshold. + /// - Returns: The current confidence threshold value. + public func getConfidenceThreshold() -> Double { + return Double(sliderConf.value) + } + + /// Sets the IoU (Intersection over Union) threshold for non-maximum suppression. + /// - Parameter iou: The IoU threshold value (0.0 to 1.0, default is 0.4). + public func setIouThreshold(_ iou: Double) { + guard iou >= 0.0 && iou <= 1.0 else { + print("Warning: IoU threshold should be between 0.0 and 1.0") + return + } + sliderIoU.value = Float(iou) + labelSliderIoU.text = String(format: "%.2f IoU Threshold", iou) + if let predictor = videoCapture.predictor as? BasePredictor { + predictor.setIouThreshold(iou: iou) + } + } + + /// Gets the current IoU threshold. + /// - Returns: The current IoU threshold value. + public func getIouThreshold() -> Double { + return Double(sliderIoU.value) + } + + /// Sets all thresholds at once. + /// - Parameters: + /// - numItems: The maximum number of items to include. + /// - confidence: The confidence threshold value (0.0 to 1.0). + /// - iou: The IoU threshold value (0.0 to 1.0). + public func setThresholds(numItems: Int? = nil, confidence: Double? = nil, iou: Double? = nil) { + if let numItems = numItems { + setNumItemsThreshold(numItems) + } + if let confidence = confidence { + setConfidenceThreshold(confidence) + } + if let iou = iou { + setIouThreshold(iou) + } + } func setUpBoundingBoxViews() { // Ensure all bounding box views are initialized up to the maximum allowed. From dcb07756d35a79bc8df5b54b82b92f31233480f7 Mon Sep 17 00:00:00 2001 From: UltralyticsAssistant Date: Wed, 6 Aug 2025 09:53:01 +0000 Subject: [PATCH 02/10] Auto-format by https://ultralytics.com/actions --- Sources/YOLO/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/YOLO/README.md b/Sources/YOLO/README.md index a3d65d05..c1d99ac7 100644 --- a/Sources/YOLO/README.md +++ b/Sources/YOLO/README.md @@ -283,7 +283,8 @@ YOLOCamera( } ``` -**Note:** +**Note:** + - `numItemsThreshold`: Controls the maximum number of detections returned - `confidenceThreshold`: Filters out detections below this confidence level - `iouThreshold`: Controls Non-Maximum Suppression overlap threshold From 0f9351f2973394c784f00492926ebc1b6643bbae Mon Sep 17 00:00:00 2001 From: UltralyticsAssistant Date: Thu, 7 Aug 2025 21:09:52 +0000 Subject: [PATCH 03/10] Auto-format by https://ultralytics.com/actions --- Sources/YOLO/YOLO.swift | 16 ++++++++-------- Sources/YOLO/YOLOView.swift | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Sources/YOLO/YOLO.swift b/Sources/YOLO/YOLO.swift index 5479e373..0784b54e 100644 --- a/Sources/YOLO/YOLO.swift +++ b/Sources/YOLO/YOLO.swift @@ -112,9 +112,9 @@ public class YOLO { } } } - + // MARK: - Threshold Configuration Methods - + /// Sets the maximum number of detection items to include in results. /// - Parameter numItems: The maximum number of items to include (default is 30). public func setNumItemsThreshold(_ numItems: Int) { @@ -122,13 +122,13 @@ public class YOLO { basePredictor.setNumItemsThreshold(numItems: numItems) } } - + /// Gets the current maximum number of detection items. /// - Returns: The current threshold value, or nil if not applicable. public func getNumItemsThreshold() -> Int? { return (predictor as? BasePredictor)?.numItemsThreshold } - + /// Sets the confidence threshold for filtering results. /// - Parameter confidence: The confidence threshold value (0.0 to 1.0, default is 0.25). public func setConfidenceThreshold(_ confidence: Double) { @@ -140,13 +140,13 @@ public class YOLO { basePredictor.setConfidenceThreshold(confidence: confidence) } } - + /// Gets the current confidence threshold. /// - Returns: The current confidence threshold value, or nil if not applicable. public func getConfidenceThreshold() -> Double? { return (predictor as? BasePredictor)?.confidenceThreshold } - + /// Sets the IoU (Intersection over Union) threshold for non-maximum suppression. /// - Parameter iou: The IoU threshold value (0.0 to 1.0, default is 0.4). public func setIouThreshold(_ iou: Double) { @@ -158,13 +158,13 @@ public class YOLO { basePredictor.setIouThreshold(iou: iou) } } - + /// Gets the current IoU threshold. /// - Returns: The current IoU threshold value, or nil if not applicable. public func getIouThreshold() -> Double? { return (predictor as? BasePredictor)?.iouThreshold } - + /// Sets all thresholds at once. /// - Parameters: /// - numItems: The maximum number of items to include. diff --git a/Sources/YOLO/YOLOView.swift b/Sources/YOLO/YOLOView.swift index 24173c09..a7535ebe 100644 --- a/Sources/YOLO/YOLOView.swift +++ b/Sources/YOLO/YOLOView.swift @@ -319,9 +319,9 @@ public class YOLOView: UIView, VideoCaptureDelegate { public func resume() { videoCapture.start() } - + // MARK: - Threshold Configuration Methods - + /// Sets the maximum number of detection items to include in results. /// - Parameter numItems: The maximum number of items to include (default is 30). public func setNumItemsThreshold(_ numItems: Int) { @@ -330,13 +330,13 @@ public class YOLOView: UIView, VideoCaptureDelegate { predictor.setNumItemsThreshold(numItems: numItems) } } - + /// Gets the current maximum number of detection items. /// - Returns: The current threshold value. public func getNumItemsThreshold() -> Int { return Int(sliderNumItems.value) } - + /// Sets the confidence threshold for filtering results. /// - Parameter confidence: The confidence threshold value (0.0 to 1.0, default is 0.25). public func setConfidenceThreshold(_ confidence: Double) { @@ -350,13 +350,13 @@ public class YOLOView: UIView, VideoCaptureDelegate { predictor.setConfidenceThreshold(confidence: confidence) } } - + /// Gets the current confidence threshold. /// - Returns: The current confidence threshold value. public func getConfidenceThreshold() -> Double { return Double(sliderConf.value) } - + /// Sets the IoU (Intersection over Union) threshold for non-maximum suppression. /// - Parameter iou: The IoU threshold value (0.0 to 1.0, default is 0.4). public func setIouThreshold(_ iou: Double) { @@ -370,13 +370,13 @@ public class YOLOView: UIView, VideoCaptureDelegate { predictor.setIouThreshold(iou: iou) } } - + /// Gets the current IoU threshold. /// - Returns: The current IoU threshold value. public func getIouThreshold() -> Double { return Double(sliderIoU.value) } - + /// Sets all thresholds at once. /// - Parameters: /// - numItems: The maximum number of items to include. From 31131a073addcf64f77556b1a80c5b3b015ee73e Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 7 Aug 2025 23:10:44 +0200 Subject: [PATCH 04/10] Update format.yml Signed-off-by: Glenn Jocher From a123d2e61e47b879fea4a7d3d214f273616a2364 Mon Sep 17 00:00:00 2001 From: john-rocky Date: Mon, 11 Aug 2025 15:14:41 +0900 Subject: [PATCH 05/10] Refactor YOLO.swift to simplify predictor creation code - Simplified loadModel method by using a single handleResult closure - Fixed type signature to use BasePredictor instead of Predictor protocol - Reduced code duplication in task-specific predictor creation - Maintained full functionality while improving code readability --- Sources/YOLO/YOLO.swift | 96 ++++++++++------------------------------- 1 file changed, 23 insertions(+), 73 deletions(-) diff --git a/Sources/YOLO/YOLO.swift b/Sources/YOLO/YOLO.swift index fe9e0897..8b4bbb51 100644 --- a/Sources/YOLO/YOLO.swift +++ b/Sources/YOLO/YOLO.swift @@ -69,66 +69,28 @@ public class YOLO { private func loadModel( from modelURL: URL, task: YOLOTask, completion: ((Result) -> Void)? ) { - func handleSuccess(predictor: Predictor) { - self.predictor = predictor - completion?(.success(self)) - } - - func handleFailure(_ error: Error) { - print("Failed to load model with error: \(error)") - completion?(.failure(error)) + let handleResult: (Result) -> Void = { result in + switch result { + case .success(let predictor): + self.predictor = predictor + completion?(.success(self)) + case .failure(let error): + print("Failed to load model with error: \(error)") + completion?(.failure(error)) + } } switch task { case .classify: - Classifier.create( - unwrappedModelURL: modelURL, - completion: { result in - switch result { - case .success(let predictor): handleSuccess(predictor: predictor) - case .failure(let error): handleFailure(error) - } - }) - + Classifier.create(unwrappedModelURL: modelURL, completion: handleResult) case .segment: - Segmenter.create( - unwrappedModelURL: modelURL, - completion: { result in - switch result { - case .success(let predictor): handleSuccess(predictor: predictor) - case .failure(let error): handleFailure(error) - } - }) - + Segmenter.create(unwrappedModelURL: modelURL, completion: handleResult) case .pose: - PoseEstimator.create( - unwrappedModelURL: modelURL, - completion: { result in - switch result { - case .success(let predictor): handleSuccess(predictor: predictor) - case .failure(let error): handleFailure(error) - } - }) - + PoseEstimator.create(unwrappedModelURL: modelURL, completion: handleResult) case .obb: - ObbDetector.create( - unwrappedModelURL: modelURL, - completion: { result in - switch result { - case .success(let predictor): handleSuccess(predictor: predictor) - case .failure(let error): handleFailure(error) - } - }) - + ObbDetector.create(unwrappedModelURL: modelURL, completion: handleResult) default: - ObjectDetector.create( - unwrappedModelURL: modelURL, - completion: { result in - switch result { - case .success(let predictor): handleSuccess(predictor: predictor) - case .failure(let error): handleFailure(error) - } - }) + ObjectDetector.create(unwrappedModelURL: modelURL, completion: handleResult) } } @@ -137,15 +99,13 @@ public class YOLO { /// Sets the maximum number of detection items to include in results. /// - Parameter numItems: The maximum number of items to include (default is 30). public func setNumItemsThreshold(_ numItems: Int) { - if let basePredictor = predictor as? BasePredictor { - basePredictor.setNumItemsThreshold(numItems: numItems) - } + (predictor as? BasePredictor)?.setNumItemsThreshold(numItems: numItems) } /// Gets the current maximum number of detection items. /// - Returns: The current threshold value, or nil if not applicable. public func getNumItemsThreshold() -> Int? { - return (predictor as? BasePredictor)?.numItemsThreshold + (predictor as? BasePredictor)?.numItemsThreshold } /// Sets the confidence threshold for filtering results. @@ -155,15 +115,13 @@ public class YOLO { print("Warning: Confidence threshold should be between 0.0 and 1.0") return } - if let basePredictor = predictor as? BasePredictor { - basePredictor.setConfidenceThreshold(confidence: confidence) - } + (predictor as? BasePredictor)?.setConfidenceThreshold(confidence: confidence) } /// Gets the current confidence threshold. /// - Returns: The current confidence threshold value, or nil if not applicable. public func getConfidenceThreshold() -> Double? { - return (predictor as? BasePredictor)?.confidenceThreshold + (predictor as? BasePredictor)?.confidenceThreshold } /// Sets the IoU (Intersection over Union) threshold for non-maximum suppression. @@ -173,15 +131,13 @@ public class YOLO { print("Warning: IoU threshold should be between 0.0 and 1.0") return } - if let basePredictor = predictor as? BasePredictor { - basePredictor.setIouThreshold(iou: iou) - } + (predictor as? BasePredictor)?.setIouThreshold(iou: iou) } /// Gets the current IoU threshold. /// - Returns: The current IoU threshold value, or nil if not applicable. public func getIouThreshold() -> Double? { - return (predictor as? BasePredictor)?.iouThreshold + (predictor as? BasePredictor)?.iouThreshold } /// Sets all thresholds at once. @@ -190,15 +146,9 @@ public class YOLO { /// - confidence: The confidence threshold value (0.0 to 1.0). /// - iou: The IoU threshold value (0.0 to 1.0). public func setThresholds(numItems: Int? = nil, confidence: Double? = nil, iou: Double? = nil) { - if let numItems = numItems { - setNumItemsThreshold(numItems) - } - if let confidence = confidence { - setConfidenceThreshold(confidence) - } - if let iou = iou { - setIouThreshold(iou) - } + numItems.map { setNumItemsThreshold($0) } + confidence.map { setConfidenceThreshold($0) } + iou.map { setIouThreshold($0) } } public func callAsFunction(_ uiImage: UIImage, returnAnnotatedImage: Bool = true) -> YOLOResult { From 048d3fe54d396941a0e40bc375416c58a48c5878 Mon Sep 17 00:00:00 2001 From: john-rocky Date: Mon, 11 Aug 2025 15:24:16 +0900 Subject: [PATCH 06/10] Simplify threshold configuration code - Simplified getter methods to single-line returns in YOLOView - Used range containment check (0.0...1.0).contains() for validation - Maintained all functionality while reducing code verbosity --- Sources/YOLO/YOLO.swift | 4 +- Sources/YOLO/YOLOCamera.swift | 14 +--- Sources/YOLO/YOLOView.swift | 132 +++++++++++----------------------- 3 files changed, 48 insertions(+), 102 deletions(-) diff --git a/Sources/YOLO/YOLO.swift b/Sources/YOLO/YOLO.swift index 8b4bbb51..cef2accb 100644 --- a/Sources/YOLO/YOLO.swift +++ b/Sources/YOLO/YOLO.swift @@ -111,7 +111,7 @@ public class YOLO { /// Sets the confidence threshold for filtering results. /// - Parameter confidence: The confidence threshold value (0.0 to 1.0, default is 0.25). public func setConfidenceThreshold(_ confidence: Double) { - guard confidence >= 0.0 && confidence <= 1.0 else { + guard (0.0...1.0).contains(confidence) else { print("Warning: Confidence threshold should be between 0.0 and 1.0") return } @@ -127,7 +127,7 @@ public class YOLO { /// Sets the IoU (Intersection over Union) threshold for non-maximum suppression. /// - Parameter iou: The IoU threshold value (0.0 to 1.0, default is 0.4). public func setIouThreshold(_ iou: Double) { - guard iou >= 0.0 && iou <= 1.0 else { + guard (0.0...1.0).contains(iou) else { print("Warning: IoU threshold should be between 0.0 and 1.0") return } diff --git a/Sources/YOLO/YOLOCamera.swift b/Sources/YOLO/YOLOCamera.swift index e989ae85..41526386 100644 --- a/Sources/YOLO/YOLOCamera.swift +++ b/Sources/YOLO/YOLOCamera.swift @@ -65,17 +65,9 @@ struct YOLOViewRepresentable: UIViewRepresentable { let onDetection: ((YOLOResult) -> Void)? func makeUIView(context: Context) -> YOLOView { - let finalModelPathOrName: String - - if let modelURL = modelURL { - finalModelPathOrName = modelURL.path - } else if let modelPathOrName = modelPathOrName { - finalModelPathOrName = modelPathOrName - } else { - fatalError("Either modelPathOrName or modelURL must be provided") - } - - return YOLOView(frame: .zero, modelPathOrName: finalModelPathOrName, task: task) + let modelPath = modelURL?.path ?? modelPathOrName ?? "" + assert(!modelPath.isEmpty, "Either modelPathOrName or modelURL must be provided") + return YOLOView(frame: .zero, modelPathOrName: modelPath, task: task) } func updateUIView(_ uiView: YOLOView, context: Context) { diff --git a/Sources/YOLO/YOLOView.swift b/Sources/YOLO/YOLOView.swift index a7535ebe..16e41d85 100644 --- a/Sources/YOLO/YOLOView.swift +++ b/Sources/YOLO/YOLOView.swift @@ -326,56 +326,44 @@ public class YOLOView: UIView, VideoCaptureDelegate { /// - Parameter numItems: The maximum number of items to include (default is 30). public func setNumItemsThreshold(_ numItems: Int) { sliderNumItems.value = Float(numItems) - if let predictor = videoCapture.predictor as? BasePredictor { - predictor.setNumItemsThreshold(numItems: numItems) - } + (videoCapture.predictor as? BasePredictor)?.setNumItemsThreshold(numItems: numItems) } /// Gets the current maximum number of detection items. /// - Returns: The current threshold value. - public func getNumItemsThreshold() -> Int { - return Int(sliderNumItems.value) - } + public func getNumItemsThreshold() -> Int { Int(sliderNumItems.value) } /// Sets the confidence threshold for filtering results. /// - Parameter confidence: The confidence threshold value (0.0 to 1.0, default is 0.25). public func setConfidenceThreshold(_ confidence: Double) { - guard confidence >= 0.0 && confidence <= 1.0 else { + guard (0.0...1.0).contains(confidence) else { print("Warning: Confidence threshold should be between 0.0 and 1.0") return } sliderConf.value = Float(confidence) labelSliderConf.text = String(format: "%.2f Confidence Threshold", confidence) - if let predictor = videoCapture.predictor as? BasePredictor { - predictor.setConfidenceThreshold(confidence: confidence) - } + (videoCapture.predictor as? BasePredictor)?.setConfidenceThreshold(confidence: confidence) } /// Gets the current confidence threshold. /// - Returns: The current confidence threshold value. - public func getConfidenceThreshold() -> Double { - return Double(sliderConf.value) - } + public func getConfidenceThreshold() -> Double { Double(sliderConf.value) } /// Sets the IoU (Intersection over Union) threshold for non-maximum suppression. /// - Parameter iou: The IoU threshold value (0.0 to 1.0, default is 0.4). public func setIouThreshold(_ iou: Double) { - guard iou >= 0.0 && iou <= 1.0 else { + guard (0.0...1.0).contains(iou) else { print("Warning: IoU threshold should be between 0.0 and 1.0") return } sliderIoU.value = Float(iou) labelSliderIoU.text = String(format: "%.2f IoU Threshold", iou) - if let predictor = videoCapture.predictor as? BasePredictor { - predictor.setIouThreshold(iou: iou) - } + (videoCapture.predictor as? BasePredictor)?.setIouThreshold(iou: iou) } /// Gets the current IoU threshold. /// - Returns: The current IoU threshold value. - public func getIouThreshold() -> Double { - return Double(sliderIoU.value) - } + public func getIouThreshold() -> Double { Double(sliderIoU.value) } /// Sets all thresholds at once. /// - Parameters: @@ -383,15 +371,9 @@ public class YOLOView: UIView, VideoCaptureDelegate { /// - confidence: The confidence threshold value (0.0 to 1.0). /// - iou: The IoU threshold value (0.0 to 1.0). public func setThresholds(numItems: Int? = nil, confidence: Double? = nil, iou: Double? = nil) { - if let numItems = numItems { - setNumItemsThreshold(numItems) - } - if let confidence = confidence { - setConfidenceThreshold(confidence) - } - if let iou = iou { - setIouThreshold(iou) - } + numItems.map { setNumItemsThreshold($0) } + confidence.map { setConfidenceThreshold($0) } + iou.map { setIouThreshold($0) } } func setUpBoundingBoxViews() { @@ -1148,42 +1130,40 @@ extension YOLOView { return copy } - /// Creates a copy of the mask layer for capture - private func copyMaskLayer(_ maskLayer: CALayer) -> CALayer? { + /// Creates a copy of a visualization layer for capture + private func copyVisualizationLayer(_ layer: CALayer, isFullFrame: Bool = false) -> CALayer? { let tempLayer = CALayer() let overlayFrame = self.overlayLayer.frame - let maskFrame = maskLayer.frame - - // Adjust mask frame to be relative to the main view, not overlayLayer - tempLayer.frame = CGRect( - x: overlayFrame.origin.x + maskFrame.origin.x, - y: overlayFrame.origin.y + maskFrame.origin.y, - width: maskFrame.width, - height: maskFrame.height - ) - tempLayer.contents = maskLayer.contents - copyLayerProperties(from: maskLayer, to: tempLayer) - return tempLayer - } - - /// Creates a copy of the pose layer including all sublayers - private func copyPoseLayer(_ poseLayer: CALayer) -> CALayer? { - let tempLayer = CALayer() - let overlayFrame = self.overlayLayer.frame - - tempLayer.frame = CGRect( - x: overlayFrame.origin.x, - y: overlayFrame.origin.y, - width: overlayFrame.width, - height: overlayFrame.height - ) - tempLayer.opacity = poseLayer.opacity - - // Copy all sublayers (keypoints and skeleton lines) - if let sublayers = poseLayer.sublayers { + + if isFullFrame { + tempLayer.frame = CGRect( + x: overlayFrame.origin.x, + y: overlayFrame.origin.y, + width: overlayFrame.width, + height: overlayFrame.height + ) + } else { + // For mask layer - adjust frame to be relative to main view + let layerFrame = layer.frame + tempLayer.frame = CGRect( + x: overlayFrame.origin.x + layerFrame.origin.x, + y: overlayFrame.origin.y + layerFrame.origin.y, + width: layerFrame.width, + height: layerFrame.height + ) + tempLayer.contents = layer.contents + } + + tempLayer.opacity = layer.opacity + copyLayerProperties(from: layer, to: tempLayer) + + // Copy sublayers if present + if let sublayers = layer.sublayers { for sublayer in sublayers { if let shapeLayer = sublayer as? CAShapeLayer { tempLayer.addSublayer(copyShapeLayer(shapeLayer)) + } else if let textLayer = sublayer as? CATextLayer { + tempLayer.addSublayer(copyTextLayer(textLayer)) } else { let copyLayer = CALayer() copyLayer.frame = sublayer.frame @@ -1194,32 +1174,6 @@ extension YOLOView { } return tempLayer } - - /// Creates a copy of the OBB layer including all sublayers - private func copyOBBLayer(_ obbLayer: CALayer) -> CALayer? { - let tempLayer = CALayer() - let overlayFrame = self.overlayLayer.frame - - tempLayer.frame = CGRect( - x: overlayFrame.origin.x, - y: overlayFrame.origin.y, - width: overlayFrame.width, - height: overlayFrame.height - ) - tempLayer.opacity = obbLayer.opacity - - // Copy all sublayers - if let sublayers = obbLayer.sublayers { - for sublayer in sublayers { - if let shapeLayer = sublayer as? CAShapeLayer { - tempLayer.addSublayer(copyShapeLayer(shapeLayer)) - } else if let textLayer = sublayer as? CATextLayer { - tempLayer.addSublayer(copyTextLayer(textLayer)) - } - } - } - return tempLayer - } } extension YOLOView: AVCapturePhotoCaptureDelegate { @@ -1269,7 +1223,7 @@ extension YOLOView: AVCapturePhotoCaptureDelegate { // Add mask layer if present (for segmentation task) if let maskLayer = self.maskLayer, !maskLayer.isHidden { - if let tempLayer = copyMaskLayer(maskLayer) { + if let tempLayer = copyVisualizationLayer(maskLayer, isFullFrame: false) { self.layer.addSublayer(tempLayer) tempLayers.append(tempLayer) } @@ -1277,7 +1231,7 @@ extension YOLOView: AVCapturePhotoCaptureDelegate { // Add pose layer if present (for pose task) if let poseLayer = self.poseLayer { - if let tempLayer = copyPoseLayer(poseLayer) { + if let tempLayer = copyVisualizationLayer(poseLayer, isFullFrame: true) { self.layer.addSublayer(tempLayer) tempLayers.append(tempLayer) } @@ -1285,7 +1239,7 @@ extension YOLOView: AVCapturePhotoCaptureDelegate { // Add OBB layer if present (for OBB task) if let obbLayer = self.obbLayer, !obbLayer.isHidden { - if let tempLayer = copyOBBLayer(obbLayer) { + if let tempLayer = copyVisualizationLayer(obbLayer, isFullFrame: true) { self.layer.addSublayer(tempLayer) tempLayers.append(tempLayer) } From 230fc90fd4b8225517ca5c143e775494af3fee97 Mon Sep 17 00:00:00 2001 From: UltralyticsAssistant Date: Mon, 11 Aug 2025 06:30:25 +0000 Subject: [PATCH 07/10] Auto-format by https://ultralytics.com/actions --- Sources/YOLO/YOLOView.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/YOLO/YOLOView.swift b/Sources/YOLO/YOLOView.swift index 16e41d85..cbcc4644 100644 --- a/Sources/YOLO/YOLOView.swift +++ b/Sources/YOLO/YOLOView.swift @@ -1134,7 +1134,7 @@ extension YOLOView { private func copyVisualizationLayer(_ layer: CALayer, isFullFrame: Bool = false) -> CALayer? { let tempLayer = CALayer() let overlayFrame = self.overlayLayer.frame - + if isFullFrame { tempLayer.frame = CGRect( x: overlayFrame.origin.x, @@ -1153,10 +1153,10 @@ extension YOLOView { ) tempLayer.contents = layer.contents } - + tempLayer.opacity = layer.opacity copyLayerProperties(from: layer, to: tempLayer) - + // Copy sublayers if present if let sublayers = layer.sublayers { for sublayer in sublayers { From 33e6b535f6f69f1841d2d78c96cf69018b42385d Mon Sep 17 00:00:00 2001 From: john-rocky Date: Mon, 11 Aug 2025 15:36:50 +0900 Subject: [PATCH 08/10] Fix ObjectDetector for loops to use results.count instead of hardcoded limit - Changed 'for i in 0..<100' to 'for i in 0.. Date: Mon, 11 Aug 2025 06:37:58 +0000 Subject: [PATCH 09/10] Auto-format by https://ultralytics.com/actions --- Sources/YOLO/ObjectDetector.swift | 60 +++++++++++++++---------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Sources/YOLO/ObjectDetector.swift b/Sources/YOLO/ObjectDetector.swift index f1828ca7..b57e7e18 100644 --- a/Sources/YOLO/ObjectDetector.swift +++ b/Sources/YOLO/ObjectDetector.swift @@ -65,21 +65,21 @@ class ObjectDetector: BasePredictor, @unchecked Sendable { var boxes = [Box]() for i in 0.. Date: Tue, 26 Aug 2025 23:23:37 +0900 Subject: [PATCH 10/10] Remove unnecessary comments in YOLOCamera example Address review feedback by removing placeholder comments in onAppear block --- Sources/YOLO/README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/Sources/YOLO/README.md b/Sources/YOLO/README.md index ee5047c4..654c1638 100644 --- a/Sources/YOLO/README.md +++ b/Sources/YOLO/README.md @@ -296,9 +296,6 @@ YOLOCamera( cameraPosition: .back ) .onAppear { - // Access the underlying view if needed for threshold configuration - // Note: Direct threshold configuration for YOLOCamera may require - // additional implementation or accessing the underlying YOLOView } ```