| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import AVFoundation |
| import UIKit |
| import os |
|
|
|
|
| public struct PixelData { |
| var a: UInt8 |
| var r: UInt8 |
| var g: UInt8 |
| var b: UInt8 |
| } |
|
|
| extension UIImage { |
| convenience init?(pixels: [PixelData], width: Int, height: Int) { |
| guard width > 0 && height > 0, pixels.count == width * height else { return nil } |
| var data = pixels |
| guard let providerRef = CGDataProvider(data: Data(bytes: &data, count: data.count * MemoryLayout<PixelData>.size) as CFData) |
| else { return nil } |
| guard let cgim = CGImage( |
| width: width, |
| height: height, |
| bitsPerComponent: 8, |
| bitsPerPixel: 32, |
| bytesPerRow: width * MemoryLayout<PixelData>.size, |
| space: CGColorSpaceCreateDeviceRGB(), |
| bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue), |
| provider: providerRef, |
| decode: nil, |
| shouldInterpolate: false, |
| intent: .defaultIntent) |
| else { return nil } |
| self.init(cgImage: cgim) |
| } |
| } |
|
|
|
|
| class ViewController: UIViewController { |
| |
| @IBOutlet weak var previewView: PreviewView! |
|
|
| |
| @IBOutlet weak var overlayView: UIImageView! |
| |
| private var imageView : UIImageView = UIImageView(frame:CGRect(x:0, y:0, width:400, height:400)) |
| |
| private var imageViewInitialized: Bool = false |
| |
| @IBOutlet weak var resumeButton: UIButton! |
| @IBOutlet weak var cameraUnavailableLabel: UILabel! |
|
|
| @IBOutlet weak var tableView: UITableView! |
|
|
| @IBOutlet weak var threadCountLabel: UILabel! |
| @IBOutlet weak var threadCountStepper: UIStepper! |
|
|
| @IBOutlet weak var delegatesControl: UISegmentedControl! |
|
|
| |
| var threadCount: Int = Constants.defaultThreadCount |
| var delegate: Delegates = Constants.defaultDelegate |
|
|
| |
| |
| private var inferencedData: InferencedData? |
|
|
| |
| private let minimumScore: Float = 0.5 |
| |
| private var avg_latency: Double = 0.0 |
|
|
| |
| private var overlayViewFrame: CGRect? |
|
|
| private var previewViewFrame: CGRect? |
|
|
| |
| |
| private lazy var cameraCapture = CameraFeedManager(previewView: previewView) |
|
|
| |
| private var modelDataHandler: ModelDataHandler? |
|
|
| |
| override func viewDidLoad() { |
| super.viewDidLoad() |
|
|
| do { |
| modelDataHandler = try ModelDataHandler() |
| } catch let error { |
| fatalError(error.localizedDescription) |
| } |
|
|
| cameraCapture.delegate = self |
| tableView.delegate = self |
| tableView.dataSource = self |
|
|
| |
| |
| |
| threadCountStepper.setDecrementImage( |
| threadCountStepper.decrementImage(for: .normal), for: .normal) |
| threadCountStepper.setIncrementImage( |
| threadCountStepper.incrementImage(for: .normal), for: .normal) |
| |
| threadCountStepper.value = Double(Constants.defaultThreadCount) |
| threadCountLabel.text = Constants.defaultThreadCount.description |
|
|
| |
| delegatesControl.setTitleTextAttributes( |
| [NSAttributedString.Key.foregroundColor: UIColor.lightGray], |
| for: .normal) |
| delegatesControl.setTitleTextAttributes( |
| [NSAttributedString.Key.foregroundColor: UIColor.black], |
| for: .selected) |
| |
| delegatesControl.removeAllSegments() |
| Delegates.allCases.forEach { delegate in |
| delegatesControl.insertSegment( |
| withTitle: delegate.description, |
| at: delegate.rawValue, |
| animated: false) |
| } |
| delegatesControl.selectedSegmentIndex = 0 |
| } |
|
|
| override func viewWillAppear(_ animated: Bool) { |
| super.viewWillAppear(animated) |
|
|
| cameraCapture.checkCameraConfigurationAndStartSession() |
| } |
|
|
| override func viewWillDisappear(_ animated: Bool) { |
| cameraCapture.stopSession() |
| } |
|
|
| override func viewDidLayoutSubviews() { |
| overlayViewFrame = overlayView.frame |
| previewViewFrame = previewView.frame |
| } |
|
|
| |
| @IBAction func didChangeThreadCount(_ sender: UIStepper) { |
| let changedCount = Int(sender.value) |
| if threadCountLabel.text == changedCount.description { |
| return |
| } |
|
|
| do { |
| modelDataHandler = try ModelDataHandler(threadCount: changedCount, delegate: delegate) |
| } catch let error { |
| fatalError(error.localizedDescription) |
| } |
| threadCount = changedCount |
| threadCountLabel.text = changedCount.description |
| os_log("Thread count is changed to: %d", threadCount) |
| } |
|
|
| @IBAction func didChangeDelegate(_ sender: UISegmentedControl) { |
| guard let changedDelegate = Delegates(rawValue: delegatesControl.selectedSegmentIndex) else { |
| fatalError("Unexpected value from delegates segemented controller.") |
| } |
| do { |
| modelDataHandler = try ModelDataHandler(threadCount: threadCount, delegate: changedDelegate) |
| } catch let error { |
| fatalError(error.localizedDescription) |
| } |
| delegate = changedDelegate |
| os_log("Delegate is changed to: %s", delegate.description) |
| } |
|
|
| @IBAction func didTapResumeButton(_ sender: Any) { |
| cameraCapture.resumeInterruptedSession { complete in |
|
|
| if complete { |
| self.resumeButton.isHidden = true |
| self.cameraUnavailableLabel.isHidden = true |
| } else { |
| self.presentUnableToResumeSessionAlert() |
| } |
| } |
| } |
|
|
| func presentUnableToResumeSessionAlert() { |
| let alert = UIAlertController( |
| title: "Unable to Resume Session", |
| message: "There was an error while attempting to resume session.", |
| preferredStyle: .alert |
| ) |
| alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) |
|
|
| self.present(alert, animated: true) |
| } |
| } |
|
|
| |
| extension ViewController: CameraFeedManagerDelegate { |
| func cameraFeedManager(_ manager: CameraFeedManager, didOutput pixelBuffer: CVPixelBuffer) { |
| runModel(on: pixelBuffer) |
| } |
|
|
| |
| func cameraFeedManagerDidEncounterSessionRunTimeError(_ manager: CameraFeedManager) { |
| |
| |
| self.resumeButton.isHidden = false |
| } |
|
|
| func cameraFeedManager( |
| _ manager: CameraFeedManager, sessionWasInterrupted canResumeManually: Bool |
| ) { |
| |
| if canResumeManually { |
| self.resumeButton.isHidden = false |
| } else { |
| self.cameraUnavailableLabel.isHidden = false |
| } |
| } |
|
|
| func cameraFeedManagerDidEndSessionInterruption(_ manager: CameraFeedManager) { |
| |
| self.cameraUnavailableLabel.isHidden = true |
| self.resumeButton.isHidden = true |
| } |
|
|
| func presentVideoConfigurationErrorAlert(_ manager: CameraFeedManager) { |
| let alertController = UIAlertController( |
| title: "Confirguration Failed", message: "Configuration of camera has failed.", |
| preferredStyle: .alert) |
| let okAction = UIAlertAction(title: "OK", style: .cancel, handler: nil) |
| alertController.addAction(okAction) |
|
|
| present(alertController, animated: true, completion: nil) |
| } |
|
|
| func presentCameraPermissionsDeniedAlert(_ manager: CameraFeedManager) { |
| let alertController = UIAlertController( |
| title: "Camera Permissions Denied", |
| message: |
| "Camera permissions have been denied for this app. You can change this by going to Settings", |
| preferredStyle: .alert) |
|
|
| let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) |
| let settingsAction = UIAlertAction(title: "Settings", style: .default) { action in |
| if let url = URL.init(string: UIApplication.openSettingsURLString) { |
| UIApplication.shared.open(url, options: [:], completionHandler: nil) |
| } |
| } |
|
|
| alertController.addAction(cancelAction) |
| alertController.addAction(settingsAction) |
|
|
| present(alertController, animated: true, completion: nil) |
| } |
|
|
| @objc func runModel(on pixelBuffer: CVPixelBuffer) { |
| guard let overlayViewFrame = overlayViewFrame, let previewViewFrame = previewViewFrame |
| else { |
| return |
| } |
| |
| |
| |
| |
| let modelInputRange = overlayViewFrame.applying( |
| previewViewFrame.size.transformKeepAspect(toFitIn: pixelBuffer.size)) |
|
|
| |
| guard |
| let (result, width, height, times) = self.modelDataHandler?.runMidas( |
| on: pixelBuffer, |
| from: modelInputRange, |
| to: overlayViewFrame.size) |
| else { |
| os_log("Cannot get inference result.", type: .error) |
| return |
| } |
|
|
| if avg_latency == 0 { |
| avg_latency = times.inference |
| } else { |
| avg_latency = times.inference*0.1 + avg_latency*0.9 |
| } |
| |
| |
| inferencedData = InferencedData(score: Float(avg_latency), times: times) |
| |
| |
| |
| |
| let outputs = result |
| let outputs_size = width * height; |
| |
| var multiplier : Float = 1.0; |
| |
| let max_val : Float = outputs.max() ?? 0 |
| let min_val : Float = outputs.min() ?? 0 |
| |
| if((max_val - min_val) > 0) { |
| multiplier = 255 / (max_val - min_val); |
| } |
| |
| |
| DispatchQueue.main.async { |
| self.tableView.reloadData() |
| |
| var pixels: [PixelData] = .init(repeating: .init(a: 255, r: 0, g: 0, b: 0), count: width * height) |
| |
| for i in pixels.indices { |
| |
| |
| let val = UInt8((outputs[i] - min_val) * multiplier) |
| |
| pixels[i].r = val |
| pixels[i].g = val |
| pixels[i].b = val |
| |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| DispatchQueue.main.async { |
| let image = UIImage(pixels: pixels, width: width, height: height) |
|
|
| self.imageView.image = image |
| |
| if (self.imageViewInitialized == false) { |
| self.imageViewInitialized = true |
| self.overlayView.addSubview(self.imageView) |
| self.overlayView.setNeedsDisplay() |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
|
|
|
|
| |
| extension ViewController: UITableViewDelegate, UITableViewDataSource { |
| func numberOfSections(in tableView: UITableView) -> Int { |
| return InferenceSections.allCases.count |
| } |
|
|
| func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { |
| guard let section = InferenceSections(rawValue: section) else { |
| return 0 |
| } |
|
|
| return section.subcaseCount |
| } |
|
|
| func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { |
| let cell = tableView.dequeueReusableCell(withIdentifier: "InfoCell") as! InfoCell |
| guard let section = InferenceSections(rawValue: indexPath.section) else { |
| return cell |
| } |
| guard let data = inferencedData else { return cell } |
|
|
| var fieldName: String |
| var info: String |
|
|
| switch section { |
| case .Score: |
| fieldName = section.description |
| info = String(format: "%.3f", data.score) |
| case .Time: |
| guard let row = ProcessingTimes(rawValue: indexPath.row) else { |
| return cell |
| } |
| var time: Double |
| switch row { |
| case .InferenceTime: |
| time = data.times.inference |
| } |
| fieldName = row.description |
| info = String(format: "%.2fms", time) |
| } |
|
|
| cell.fieldNameLabel.text = fieldName |
| cell.infoLabel.text = info |
|
|
| return cell |
| } |
|
|
| func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { |
| guard let section = InferenceSections(rawValue: indexPath.section) else { |
| return 0 |
| } |
|
|
| var height = Traits.normalCellHeight |
| if indexPath.row == section.subcaseCount - 1 { |
| height = Traits.separatorCellHeight + Traits.bottomSpacing |
| } |
| return height |
| } |
|
|
| } |
|
|
| |
| |
| fileprivate enum Traits { |
| static let normalCellHeight: CGFloat = 35.0 |
| static let separatorCellHeight: CGFloat = 25.0 |
| static let bottomSpacing: CGFloat = 30.0 |
| } |
|
|
| fileprivate struct InferencedData { |
| var score: Float |
| var times: Times |
| } |
|
|
| |
| fileprivate enum InferenceSections: Int, CaseIterable { |
| case Score |
| case Time |
|
|
| var description: String { |
| switch self { |
| case .Score: |
| return "Average" |
| case .Time: |
| return "Processing Time" |
| } |
| } |
|
|
| var subcaseCount: Int { |
| switch self { |
| case .Score: |
| return 1 |
| case .Time: |
| return ProcessingTimes.allCases.count |
| } |
| } |
| } |
|
|
| |
| fileprivate enum ProcessingTimes: Int, CaseIterable { |
| case InferenceTime |
|
|
| var description: String { |
| switch self { |
| case .InferenceTime: |
| return "Inference Time" |
| } |
| } |
| } |
|
|