From 02385472b62a1eaef07abb60c777d8050d4c47f2 Mon Sep 17 00:00:00 2001 From: Giles Payne Date: Tue, 9 Jun 2020 03:32:53 +0900 Subject: [PATCH] Merge pull request #17165 from komakai:objc-binding Objc binding * Initial work on Objective-C wrapper * Objective-C generator script; update manually generated wrappers * Add Mat tests * Core Tests * Imgproc wrapper generation and tests * Fixes for Imgcodecs wrapper * Miscellaneous fixes. Swift build support * Objective-C wrapper build/install * Add Swift wrappers for videoio/objdetect/feature2d * Framework build;iOS support * Fix toArray functions;Use enum types whenever possible * Use enum types where possible;prepare test build * Update test * Add test runner scripts for iOS and macOS * Add test scripts and samples * Build fixes * Fix build (cmake 3.17.x compatibility) * Fix warnings * Fix enum name conflicting handling * Add support for document generation with Jazzy * Swift/Native fast accessor functions * Add Objective-C wrapper for calib3d, dnn, ml, photo and video modules * Remove IntOut/FloatOut/DoubleOut classes * Fix iOS default test platform value * Fix samples * Revert default framework name to opencv2 * Add converter util functions * Fix failing test * Fix whitespace * Add handling for deprecated methods;fix warnings;define __OPENCV_BUILD * Suppress cmake warnings * Reduce severity of "jazzy not found" log message * Fix incorrect #include of compatibility header in ios.h * Use explicit returns in subscript/get implementation * Reduce minimum required cmake version to 3.15 for Objective-C/Swift binding --- .gitignore | 1 + CMakeLists.txt | 7 + cmake/OpenCVGenConfig.cmake | 5 + cmake/OpenCVModule.cmake | 1 + modules/calib3d/CMakeLists.txt | 2 +- modules/calib3d/misc/objc/gen_dict.json | 5 + .../calib3d/misc/objc/test/Calib3dTest.swift | 465 +++++ modules/core/CMakeLists.txt | 2 +- modules/core/misc/objc/common/ArrayUtil.h | 16 + modules/core/misc/objc/common/ArrayUtil.mm | 15 + modules/core/misc/objc/common/ByteVector.h | 88 + modules/core/misc/objc/common/ByteVector.mm | 76 + .../core/misc/objc/common/ByteVectorExt.swift | 53 + modules/core/misc/objc/common/CVObjcUtil.h | 85 + modules/core/misc/objc/common/Converters.h | 98 + modules/core/misc/objc/common/Converters.mm | 205 ++ modules/core/misc/objc/common/CvType.h | 67 + modules/core/misc/objc/common/CvType.mm | 105 + modules/core/misc/objc/common/CvTypeExt.swift | 90 + modules/core/misc/objc/common/DMatch.h | 82 + modules/core/misc/objc/common/DMatch.mm | 107 + modules/core/misc/objc/common/Double2.h | 93 + modules/core/misc/objc/common/Double2.mm | 75 + modules/core/misc/objc/common/Double3.h | 94 + modules/core/misc/objc/common/Double3.mm | 85 + modules/core/misc/objc/common/DoubleVector.h | 88 + modules/core/misc/objc/common/DoubleVector.mm | 76 + .../misc/objc/common/DoubleVectorExt.swift | 53 + modules/core/misc/objc/common/Float4.h | 99 + modules/core/misc/objc/common/Float4.mm | 95 + modules/core/misc/objc/common/Float6.h | 112 ++ modules/core/misc/objc/common/Float6.mm | 115 ++ modules/core/misc/objc/common/FloatVector.h | 88 + modules/core/misc/objc/common/FloatVector.mm | 76 + .../misc/objc/common/FloatVectorExt.swift | 53 + modules/core/misc/objc/common/Int4.h | 99 + modules/core/misc/objc/common/Int4.mm | 95 + modules/core/misc/objc/common/IntVector.h | 88 + modules/core/misc/objc/common/IntVector.mm | 76 + .../core/misc/objc/common/IntVectorExt.swift | 53 + modules/core/misc/objc/common/KeyPoint.h | 98 + modules/core/misc/objc/common/KeyPoint.mm | 95 + modules/core/misc/objc/common/Mat.h | 157 ++ modules/core/misc/objc/common/Mat.mm | 913 +++++++++ modules/core/misc/objc/common/MatExt.swift | 244 +++ modules/core/misc/objc/common/MatOfByte.h | 62 + modules/core/misc/objc/common/MatOfByte.mm | 69 + modules/core/misc/objc/common/MatOfDMatch.h | 64 + modules/core/misc/objc/common/MatOfDMatch.mm | 83 + modules/core/misc/objc/common/MatOfDouble.h | 63 + modules/core/misc/objc/common/MatOfDouble.mm | 69 + modules/core/misc/objc/common/MatOfFloat.h | 60 + modules/core/misc/objc/common/MatOfFloat.mm | 69 + modules/core/misc/objc/common/MatOfFloat4.h | 62 + modules/core/misc/objc/common/MatOfFloat4.mm | 69 + modules/core/misc/objc/common/MatOfFloat6.h | 62 + modules/core/misc/objc/common/MatOfFloat6.mm | 69 + modules/core/misc/objc/common/MatOfInt.h | 62 + modules/core/misc/objc/common/MatOfInt.mm | 69 + modules/core/misc/objc/common/MatOfInt4.h | 62 + modules/core/misc/objc/common/MatOfInt4.mm | 69 + modules/core/misc/objc/common/MatOfKeyPoint.h | 64 + .../core/misc/objc/common/MatOfKeyPoint.mm | 87 + modules/core/misc/objc/common/MatOfPoint2f.h | 64 + modules/core/misc/objc/common/MatOfPoint2f.mm | 81 + modules/core/misc/objc/common/MatOfPoint2i.h | 65 + modules/core/misc/objc/common/MatOfPoint2i.mm | 81 + modules/core/misc/objc/common/MatOfPoint3.h | 64 + modules/core/misc/objc/common/MatOfPoint3.mm | 82 + modules/core/misc/objc/common/MatOfPoint3f.h | 64 + modules/core/misc/objc/common/MatOfPoint3f.mm | 82 + modules/core/misc/objc/common/MatOfRect2d.h | 65 + modules/core/misc/objc/common/MatOfRect2d.mm | 88 + modules/core/misc/objc/common/MatOfRect2i.h | 65 + modules/core/misc/objc/common/MatOfRect2i.mm | 83 + .../core/misc/objc/common/MatOfRotatedRect.h | 64 + .../core/misc/objc/common/MatOfRotatedRect.mm | 87 + .../core/misc/objc/common/MinMaxLocResult.h | 38 + .../core/misc/objc/common/MinMaxLocResult.mm | 27 + modules/core/misc/objc/common/Point2d.h | 87 + modules/core/misc/objc/common/Point2d.mm | 107 + modules/core/misc/objc/common/Point2f.h | 87 + modules/core/misc/objc/common/Point2f.mm | 105 + modules/core/misc/objc/common/Point2i.h | 88 + modules/core/misc/objc/common/Point2i.mm | 105 + modules/core/misc/objc/common/Point3d.h | 84 + modules/core/misc/objc/common/Point3d.mm | 114 ++ modules/core/misc/objc/common/Point3f.h | 85 + modules/core/misc/objc/common/Point3f.mm | 111 ++ modules/core/misc/objc/common/Point3i.h | 84 + modules/core/misc/objc/common/Point3i.mm | 111 ++ modules/core/misc/objc/common/Range.h | 93 + modules/core/misc/objc/common/Range.m | 86 + modules/core/misc/objc/common/Rect2d.h | 111 ++ modules/core/misc/objc/common/Rect2d.mm | 155 ++ modules/core/misc/objc/common/Rect2f.h | 111 ++ modules/core/misc/objc/common/Rect2f.mm | 151 ++ modules/core/misc/objc/common/Rect2i.h | 112 ++ modules/core/misc/objc/common/Rect2i.mm | 150 ++ modules/core/misc/objc/common/RotatedRect.h | 86 + modules/core/misc/objc/common/RotatedRect.mm | 113 ++ modules/core/misc/objc/common/Scalar.h | 96 + modules/core/misc/objc/common/Scalar.mm | 117 ++ modules/core/misc/objc/common/Size2d.h | 87 + modules/core/misc/objc/common/Size2d.mm | 110 ++ modules/core/misc/objc/common/Size2f.h | 87 + modules/core/misc/objc/common/Size2f.mm | 108 + modules/core/misc/objc/common/Size2i.h | 88 + modules/core/misc/objc/common/Size2i.mm | 108 + modules/core/misc/objc/common/TermCriteria.h | 77 + modules/core/misc/objc/common/TermCriteria.mm | 119 ++ modules/core/misc/objc/filelist | 4 + modules/core/misc/objc/gen_dict.json | 375 ++++ modules/core/misc/objc/manual/core_manual.hpp | 26 + .../core/misc/objc/test/ConvertersTest.swift | 109 ++ modules/core/misc/objc/test/CoreTest.swift | 1710 ++++++++++++++++ modules/core/misc/objc/test/CvTypeTest.swift | 75 + modules/core/misc/objc/test/DMatchTest.swift | 44 + .../core/misc/objc/test/KeyPointTest.swift | 59 + modules/core/misc/objc/test/MatTest.swift | 1146 +++++++++++ modules/core/misc/objc/test/MatTestObjc.m | 58 + modules/core/misc/objc/test/Point3Test.swift | 101 + modules/core/misc/objc/test/PointTest.swift | 88 + modules/core/misc/objc/test/RangeTest.swift | 104 + modules/core/misc/objc/test/RectTest.swift | 153 ++ .../core/misc/objc/test/RotatedRectTest.swift | 174 ++ modules/core/misc/objc/test/ScalarTest.swift | 98 + modules/core/misc/objc/test/SizeTest.swift | 86 + .../misc/objc/test/TermCriteriaTest.swift | 82 + .../misc/objc/test/resources/chessboard.jpg | Bin 0 -> 27908 bytes .../core/misc/objc/test/resources/lena.png | Bin 0 -> 494405 bytes modules/dnn/CMakeLists.txt | 2 +- modules/dnn/misc/objc/gen_dict.json | 41 + modules/features2d/CMakeLists.txt | 2 +- modules/features2d/misc/objc/gen_dict.json | 16 + modules/imgcodecs/CMakeLists.txt | 6 +- .../imgcodecs/include/opencv2/imgcodecs/ios.h | 2 +- .../imgcodecs/misc/objc/ios/Mat+Converters.h | 27 + .../imgcodecs/misc/objc/ios/Mat+Converters.mm | 28 + .../misc/objc/test/ImgcodecsTest.swift | 50 + modules/imgproc/CMakeLists.txt | 2 +- modules/imgproc/misc/objc/common/Moments.h | 66 + modules/imgproc/misc/objc/common/Moments.mm | 304 +++ modules/imgproc/misc/objc/gen_dict.json | 121 ++ .../imgproc/misc/objc/test/ImgprocTest.swift | 1743 +++++++++++++++++ .../imgproc/misc/objc/test/MomentsTest.swift | 42 + .../imgproc/misc/objc/test/Subdiv2DTest.swift | 23 + modules/ml/CMakeLists.txt | 2 +- modules/ml/misc/objc/gen_dict.json | 9 + modules/objc/CMakeLists.txt | 6 + modules/objc/common.cmake | 11 + modules/objc/doc/README.md | 15 + modules/objc/generator/CMakeLists.txt | 86 + modules/objc/generator/gen_objc.py | 1384 +++++++++++++ .../generator/templates/cmakelists.template | 59 + .../templates/objc_class_body.template | 16 + .../templates/objc_class_header.template | 29 + .../templates/objc_module_body.template | 14 + .../templates/objc_module_header.template | 26 + modules/objc/test/cmakelists.template | 45 + modules/objc/test/dummy/dummy.mm | 8 + modules/objc/test/test/OpenCVTestCase.swift | 226 +++ modules/objdetect/CMakeLists.txt | 2 +- modules/photo/CMakeLists.txt | 2 +- modules/photo/misc/objc/gen_dict.json | 6 + modules/video/CMakeLists.txt | 2 +- modules/videoio/CMakeLists.txt | 2 +- modules/videoio/misc/objc/gen_dict.json | 17 + .../misc/objc/ios/CvAbstractCamera2.mm | 445 +++++ modules/videoio/misc/objc/ios/CvCamera2.h | 84 + .../videoio/misc/objc/ios/CvPhotoCamera2.m | 138 ++ .../videoio/misc/objc/ios/CvVideoCamera2.mm | 575 ++++++ platforms/ios/build_framework.py | 117 +- platforms/ios/run_tests.py | 114 ++ platforms/osx/build_framework.py | 8 +- platforms/osx/run_tests.py | 44 + .../project.pbxproj | 397 ++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../ColorBlobDetection/AppDelegate.swift | 19 + .../AppIcon.appiconset/100.png | Bin 0 -> 3758 bytes .../AppIcon.appiconset/114.png | Bin 0 -> 4188 bytes .../AppIcon.appiconset/120.png | Bin 0 -> 4484 bytes .../AppIcon.appiconset/144.png | Bin 0 -> 5533 bytes .../AppIcon.appiconset/152.png | Bin 0 -> 5685 bytes .../AppIcon.appiconset/167.png | Bin 0 -> 6547 bytes .../AppIcon.appiconset/180.png | Bin 0 -> 7138 bytes .../Assets.xcassets/AppIcon.appiconset/20.png | Bin 0 -> 638 bytes .../Assets.xcassets/AppIcon.appiconset/29.png | Bin 0 -> 946 bytes .../Assets.xcassets/AppIcon.appiconset/40.png | Bin 0 -> 1266 bytes .../Assets.xcassets/AppIcon.appiconset/50.png | Bin 0 -> 1667 bytes .../Assets.xcassets/AppIcon.appiconset/57.png | Bin 0 -> 2034 bytes .../Assets.xcassets/AppIcon.appiconset/58.png | Bin 0 -> 2073 bytes .../Assets.xcassets/AppIcon.appiconset/60.png | Bin 0 -> 2125 bytes .../Assets.xcassets/AppIcon.appiconset/72.png | Bin 0 -> 2676 bytes .../Assets.xcassets/AppIcon.appiconset/76.png | Bin 0 -> 2778 bytes .../Assets.xcassets/AppIcon.appiconset/80.png | Bin 0 -> 2940 bytes .../Assets.xcassets/AppIcon.appiconset/87.png | Bin 0 -> 3155 bytes .../AppIcon.appiconset/Contents.json | 158 ++ .../AppIcon.appiconset/appstore.png | Bin 0 -> 62918 bytes .../Assets.xcassets/Contents.json | 6 + .../Base.lproj/LaunchScreen.storyboard | 25 + .../Base.lproj/Main.storyboard | 42 + .../ColorBlobDetector.swift | 75 + .../ColorBlobDetection/Info.plist | 49 + .../ColorBlobDetection/ViewController.swift | 124 ++ .../FaceDetection.xcodeproj/project.pbxproj | 407 ++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../FaceDetection/AppDelegate.swift | 19 + .../AppIcon.appiconset/100.png | Bin 0 -> 3758 bytes .../AppIcon.appiconset/114.png | Bin 0 -> 4188 bytes .../AppIcon.appiconset/120.png | Bin 0 -> 4484 bytes .../AppIcon.appiconset/144.png | Bin 0 -> 5533 bytes .../AppIcon.appiconset/152.png | Bin 0 -> 5685 bytes .../AppIcon.appiconset/167.png | Bin 0 -> 6547 bytes .../AppIcon.appiconset/180.png | Bin 0 -> 7138 bytes .../Assets.xcassets/AppIcon.appiconset/20.png | Bin 0 -> 638 bytes .../Assets.xcassets/AppIcon.appiconset/29.png | Bin 0 -> 946 bytes .../Assets.xcassets/AppIcon.appiconset/40.png | Bin 0 -> 1266 bytes .../Assets.xcassets/AppIcon.appiconset/50.png | Bin 0 -> 1667 bytes .../Assets.xcassets/AppIcon.appiconset/57.png | Bin 0 -> 2034 bytes .../Assets.xcassets/AppIcon.appiconset/58.png | Bin 0 -> 2073 bytes .../Assets.xcassets/AppIcon.appiconset/60.png | Bin 0 -> 2125 bytes .../Assets.xcassets/AppIcon.appiconset/72.png | Bin 0 -> 2676 bytes .../Assets.xcassets/AppIcon.appiconset/76.png | Bin 0 -> 2778 bytes .../Assets.xcassets/AppIcon.appiconset/80.png | Bin 0 -> 2940 bytes .../Assets.xcassets/AppIcon.appiconset/87.png | Bin 0 -> 3155 bytes .../AppIcon.appiconset/Contents.json | 158 ++ .../AppIcon.appiconset/appstore.png | Bin 0 -> 62918 bytes .../Assets.xcassets/Contents.json | 6 + .../Base.lproj/LaunchScreen.storyboard | 25 + .../FaceDetection/Base.lproj/Main.storyboard | 42 + .../FaceDetection/DetectionBasedTracker.h | 32 + .../FaceDetection/DetectionBasedTracker.mm | 85 + .../FaceDetection-Bridging-Header.h | 5 + .../FaceDetection/FaceDetection/Info.plist | 49 + .../FaceDetection/ViewController.swift | 90 + .../FaceDetection/lbpcascade_frontalface.xml | 1505 ++++++++++++++ 237 files changed, 24107 insertions(+), 28 deletions(-) create mode 100644 modules/calib3d/misc/objc/gen_dict.json create mode 100644 modules/calib3d/misc/objc/test/Calib3dTest.swift create mode 100644 modules/core/misc/objc/common/ArrayUtil.h create mode 100644 modules/core/misc/objc/common/ArrayUtil.mm create mode 100644 modules/core/misc/objc/common/ByteVector.h create mode 100644 modules/core/misc/objc/common/ByteVector.mm create mode 100644 modules/core/misc/objc/common/ByteVectorExt.swift create mode 100644 modules/core/misc/objc/common/CVObjcUtil.h create mode 100755 modules/core/misc/objc/common/Converters.h create mode 100644 modules/core/misc/objc/common/Converters.mm create mode 100644 modules/core/misc/objc/common/CvType.h create mode 100644 modules/core/misc/objc/common/CvType.mm create mode 100644 modules/core/misc/objc/common/CvTypeExt.swift create mode 100644 modules/core/misc/objc/common/DMatch.h create mode 100644 modules/core/misc/objc/common/DMatch.mm create mode 100644 modules/core/misc/objc/common/Double2.h create mode 100644 modules/core/misc/objc/common/Double2.mm create mode 100644 modules/core/misc/objc/common/Double3.h create mode 100644 modules/core/misc/objc/common/Double3.mm create mode 100644 modules/core/misc/objc/common/DoubleVector.h create mode 100644 modules/core/misc/objc/common/DoubleVector.mm create mode 100644 modules/core/misc/objc/common/DoubleVectorExt.swift create mode 100644 modules/core/misc/objc/common/Float4.h create mode 100644 modules/core/misc/objc/common/Float4.mm create mode 100644 modules/core/misc/objc/common/Float6.h create mode 100644 modules/core/misc/objc/common/Float6.mm create mode 100644 modules/core/misc/objc/common/FloatVector.h create mode 100644 modules/core/misc/objc/common/FloatVector.mm create mode 100644 modules/core/misc/objc/common/FloatVectorExt.swift create mode 100644 modules/core/misc/objc/common/Int4.h create mode 100644 modules/core/misc/objc/common/Int4.mm create mode 100644 modules/core/misc/objc/common/IntVector.h create mode 100644 modules/core/misc/objc/common/IntVector.mm create mode 100644 modules/core/misc/objc/common/IntVectorExt.swift create mode 100644 modules/core/misc/objc/common/KeyPoint.h create mode 100644 modules/core/misc/objc/common/KeyPoint.mm create mode 100644 modules/core/misc/objc/common/Mat.h create mode 100644 modules/core/misc/objc/common/Mat.mm create mode 100644 modules/core/misc/objc/common/MatExt.swift create mode 100644 modules/core/misc/objc/common/MatOfByte.h create mode 100644 modules/core/misc/objc/common/MatOfByte.mm create mode 100644 modules/core/misc/objc/common/MatOfDMatch.h create mode 100644 modules/core/misc/objc/common/MatOfDMatch.mm create mode 100644 modules/core/misc/objc/common/MatOfDouble.h create mode 100644 modules/core/misc/objc/common/MatOfDouble.mm create mode 100644 modules/core/misc/objc/common/MatOfFloat.h create mode 100644 modules/core/misc/objc/common/MatOfFloat.mm create mode 100644 modules/core/misc/objc/common/MatOfFloat4.h create mode 100644 modules/core/misc/objc/common/MatOfFloat4.mm create mode 100644 modules/core/misc/objc/common/MatOfFloat6.h create mode 100644 modules/core/misc/objc/common/MatOfFloat6.mm create mode 100644 modules/core/misc/objc/common/MatOfInt.h create mode 100644 modules/core/misc/objc/common/MatOfInt.mm create mode 100644 modules/core/misc/objc/common/MatOfInt4.h create mode 100644 modules/core/misc/objc/common/MatOfInt4.mm create mode 100644 modules/core/misc/objc/common/MatOfKeyPoint.h create mode 100644 modules/core/misc/objc/common/MatOfKeyPoint.mm create mode 100644 modules/core/misc/objc/common/MatOfPoint2f.h create mode 100644 modules/core/misc/objc/common/MatOfPoint2f.mm create mode 100644 modules/core/misc/objc/common/MatOfPoint2i.h create mode 100644 modules/core/misc/objc/common/MatOfPoint2i.mm create mode 100644 modules/core/misc/objc/common/MatOfPoint3.h create mode 100644 modules/core/misc/objc/common/MatOfPoint3.mm create mode 100644 modules/core/misc/objc/common/MatOfPoint3f.h create mode 100644 modules/core/misc/objc/common/MatOfPoint3f.mm create mode 100644 modules/core/misc/objc/common/MatOfRect2d.h create mode 100644 modules/core/misc/objc/common/MatOfRect2d.mm create mode 100644 modules/core/misc/objc/common/MatOfRect2i.h create mode 100644 modules/core/misc/objc/common/MatOfRect2i.mm create mode 100644 modules/core/misc/objc/common/MatOfRotatedRect.h create mode 100644 modules/core/misc/objc/common/MatOfRotatedRect.mm create mode 100644 modules/core/misc/objc/common/MinMaxLocResult.h create mode 100644 modules/core/misc/objc/common/MinMaxLocResult.mm create mode 100644 modules/core/misc/objc/common/Point2d.h create mode 100644 modules/core/misc/objc/common/Point2d.mm create mode 100644 modules/core/misc/objc/common/Point2f.h create mode 100644 modules/core/misc/objc/common/Point2f.mm create mode 100644 modules/core/misc/objc/common/Point2i.h create mode 100644 modules/core/misc/objc/common/Point2i.mm create mode 100644 modules/core/misc/objc/common/Point3d.h create mode 100644 modules/core/misc/objc/common/Point3d.mm create mode 100644 modules/core/misc/objc/common/Point3f.h create mode 100644 modules/core/misc/objc/common/Point3f.mm create mode 100644 modules/core/misc/objc/common/Point3i.h create mode 100644 modules/core/misc/objc/common/Point3i.mm create mode 100644 modules/core/misc/objc/common/Range.h create mode 100644 modules/core/misc/objc/common/Range.m create mode 100644 modules/core/misc/objc/common/Rect2d.h create mode 100644 modules/core/misc/objc/common/Rect2d.mm create mode 100644 modules/core/misc/objc/common/Rect2f.h create mode 100644 modules/core/misc/objc/common/Rect2f.mm create mode 100644 modules/core/misc/objc/common/Rect2i.h create mode 100644 modules/core/misc/objc/common/Rect2i.mm create mode 100644 modules/core/misc/objc/common/RotatedRect.h create mode 100644 modules/core/misc/objc/common/RotatedRect.mm create mode 100644 modules/core/misc/objc/common/Scalar.h create mode 100644 modules/core/misc/objc/common/Scalar.mm create mode 100644 modules/core/misc/objc/common/Size2d.h create mode 100644 modules/core/misc/objc/common/Size2d.mm create mode 100644 modules/core/misc/objc/common/Size2f.h create mode 100644 modules/core/misc/objc/common/Size2f.mm create mode 100644 modules/core/misc/objc/common/Size2i.h create mode 100644 modules/core/misc/objc/common/Size2i.mm create mode 100644 modules/core/misc/objc/common/TermCriteria.h create mode 100644 modules/core/misc/objc/common/TermCriteria.mm create mode 100644 modules/core/misc/objc/filelist create mode 100644 modules/core/misc/objc/gen_dict.json create mode 100644 modules/core/misc/objc/manual/core_manual.hpp create mode 100755 modules/core/misc/objc/test/ConvertersTest.swift create mode 100644 modules/core/misc/objc/test/CoreTest.swift create mode 100644 modules/core/misc/objc/test/CvTypeTest.swift create mode 100644 modules/core/misc/objc/test/DMatchTest.swift create mode 100644 modules/core/misc/objc/test/KeyPointTest.swift create mode 100644 modules/core/misc/objc/test/MatTest.swift create mode 100644 modules/core/misc/objc/test/MatTestObjc.m create mode 100644 modules/core/misc/objc/test/Point3Test.swift create mode 100644 modules/core/misc/objc/test/PointTest.swift create mode 100644 modules/core/misc/objc/test/RangeTest.swift create mode 100644 modules/core/misc/objc/test/RectTest.swift create mode 100644 modules/core/misc/objc/test/RotatedRectTest.swift create mode 100644 modules/core/misc/objc/test/ScalarTest.swift create mode 100644 modules/core/misc/objc/test/SizeTest.swift create mode 100644 modules/core/misc/objc/test/TermCriteriaTest.swift create mode 100644 modules/core/misc/objc/test/resources/chessboard.jpg create mode 100644 modules/core/misc/objc/test/resources/lena.png create mode 100644 modules/dnn/misc/objc/gen_dict.json create mode 100644 modules/features2d/misc/objc/gen_dict.json create mode 100644 modules/imgcodecs/misc/objc/ios/Mat+Converters.h create mode 100644 modules/imgcodecs/misc/objc/ios/Mat+Converters.mm create mode 100644 modules/imgcodecs/misc/objc/test/ImgcodecsTest.swift create mode 100644 modules/imgproc/misc/objc/common/Moments.h create mode 100644 modules/imgproc/misc/objc/common/Moments.mm create mode 100644 modules/imgproc/misc/objc/gen_dict.json create mode 100644 modules/imgproc/misc/objc/test/ImgprocTest.swift create mode 100644 modules/imgproc/misc/objc/test/MomentsTest.swift create mode 100644 modules/imgproc/misc/objc/test/Subdiv2DTest.swift create mode 100644 modules/ml/misc/objc/gen_dict.json create mode 100644 modules/objc/CMakeLists.txt create mode 100644 modules/objc/common.cmake create mode 100644 modules/objc/doc/README.md create mode 100644 modules/objc/generator/CMakeLists.txt create mode 100755 modules/objc/generator/gen_objc.py create mode 100644 modules/objc/generator/templates/cmakelists.template create mode 100644 modules/objc/generator/templates/objc_class_body.template create mode 100644 modules/objc/generator/templates/objc_class_header.template create mode 100644 modules/objc/generator/templates/objc_module_body.template create mode 100644 modules/objc/generator/templates/objc_module_header.template create mode 100644 modules/objc/test/cmakelists.template create mode 100644 modules/objc/test/dummy/dummy.mm create mode 100644 modules/objc/test/test/OpenCVTestCase.swift create mode 100644 modules/photo/misc/objc/gen_dict.json create mode 100644 modules/videoio/misc/objc/gen_dict.json create mode 100644 modules/videoio/misc/objc/ios/CvAbstractCamera2.mm create mode 100644 modules/videoio/misc/objc/ios/CvCamera2.h create mode 100644 modules/videoio/misc/objc/ios/CvPhotoCamera2.m create mode 100644 modules/videoio/misc/objc/ios/CvVideoCamera2.mm create mode 100755 platforms/ios/run_tests.py create mode 100755 platforms/osx/run_tests.py create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection.xcodeproj/project.pbxproj create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/AppDelegate.swift create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/100.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/114.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/120.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/144.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/152.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/167.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/180.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/20.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/29.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/40.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/50.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/57.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/58.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/60.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/72.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/76.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/80.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/87.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/appstore.png create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/Contents.json create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Base.lproj/LaunchScreen.storyboard create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Base.lproj/Main.storyboard create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/ColorBlobDetector.swift create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Info.plist create mode 100644 samples/swift/ios/ColorBlobDetection/ColorBlobDetection/ViewController.swift create mode 100644 samples/swift/ios/FaceDetection/FaceDetection.xcodeproj/project.pbxproj create mode 100644 samples/swift/ios/FaceDetection/FaceDetection.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/AppDelegate.swift create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/100.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/114.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/120.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/144.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/152.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/167.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/180.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/20.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/29.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/40.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/50.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/57.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/58.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/60.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/72.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/76.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/80.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/87.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/appstore.png create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/Contents.json create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Base.lproj/LaunchScreen.storyboard create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Base.lproj/Main.storyboard create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/DetectionBasedTracker.h create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/DetectionBasedTracker.mm create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/FaceDetection-Bridging-Header.h create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/Info.plist create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/ViewController.swift create mode 100644 samples/swift/ios/FaceDetection/FaceDetection/lbpcascade_frontalface.xml diff --git a/.gitignore b/.gitignore index 8f9ef226cc..f34c2100f3 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ bin/ build node_modules CMakeSettings.json +xcuserdata/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c0b3880fc..166257eca5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -452,6 +452,7 @@ OCV_OPTION(BUILD_FAT_JAVA_LIB "Create Java wrapper exporting all functions OCV_OPTION(BUILD_ANDROID_SERVICE "Build OpenCV Manager for Google Play" OFF IF ANDROID ) OCV_OPTION(BUILD_CUDA_STUBS "Build CUDA modules stubs when no CUDA SDK" OFF IF (NOT APPLE_FRAMEWORK) ) OCV_OPTION(BUILD_JAVA "Enable Java support" (ANDROID OR NOT CMAKE_CROSSCOMPILING) IF (ANDROID OR (NOT APPLE_FRAMEWORK AND NOT WINRT)) ) +OCV_OPTION(BUILD_OBJC "Enable Objective-C support" ON IF APPLE_FRAMEWORK ) # OpenCV installation options # =================================================== @@ -1595,6 +1596,12 @@ if(BUILD_JAVA) status(" Java tests:" BUILD_TESTS AND opencv_test_java_BINARY_DIR THEN YES ELSE NO) endif() +# ========================== Objective-C ======================= +if(BUILD_OBJC) + status("") + status(" Objective-C wrappers:" HAVE_opencv_objc THEN YES ELSE NO) +endif() + ocv_cmake_hook(STATUS_DUMP_EXTRA) # ========================== auxiliary ========================== diff --git a/cmake/OpenCVGenConfig.cmake b/cmake/OpenCVGenConfig.cmake index 699128990f..838852c4e7 100644 --- a/cmake/OpenCVGenConfig.cmake +++ b/cmake/OpenCVGenConfig.cmake @@ -30,6 +30,11 @@ if(BUILD_FAT_JAVA_LIB AND HAVE_opencv_java) list(APPEND OPENCV_MODULES_CONFIGCMAKE opencv_java) endif() +if(BUILD_OBJC AND HAVE_opencv_objc) + list(APPEND OPENCV_MODULES_CONFIGCMAKE opencv_objc) +endif() + + # ------------------------------------------------------------------------------------------- # Part 1/3: ${BIN_DIR}/OpenCVConfig.cmake -> For use *without* "make install" # ------------------------------------------------------------------------------------------- diff --git a/cmake/OpenCVModule.cmake b/cmake/OpenCVModule.cmake index 90539573bf..c66855a239 100644 --- a/cmake/OpenCVModule.cmake +++ b/cmake/OpenCVModule.cmake @@ -788,6 +788,7 @@ macro(ocv_glob_module_sources) if (APPLE) file(GLOB_RECURSE lib_srcs_apple "${CMAKE_CURRENT_LIST_DIR}/src/*.mm" + "${CMAKE_CURRENT_LIST_DIR}/src/*.swift" ) list(APPEND lib_srcs ${lib_srcs_apple}) endif() diff --git a/modules/calib3d/CMakeLists.txt b/modules/calib3d/CMakeLists.txt index 9af6570b09..0953e3cd7e 100644 --- a/modules/calib3d/CMakeLists.txt +++ b/modules/calib3d/CMakeLists.txt @@ -7,5 +7,5 @@ if(DEBUG_opencv_calib3d) list(APPEND debug_modules opencv_highgui) endif() ocv_define_module(calib3d opencv_imgproc opencv_features2d opencv_flann ${debug_modules} - WRAP java python js + WRAP java objc python js ) diff --git a/modules/calib3d/misc/objc/gen_dict.json b/modules/calib3d/misc/objc/gen_dict.json new file mode 100644 index 0000000000..aeed9f5b44 --- /dev/null +++ b/modules/calib3d/misc/objc/gen_dict.json @@ -0,0 +1,5 @@ +{ + "func_arg_fix" : { + "findCirclesGrid" : { "blobDetector" : {"defval" : "cv::SimpleBlobDetector::create()"} } + } +} diff --git a/modules/calib3d/misc/objc/test/Calib3dTest.swift b/modules/calib3d/misc/objc/test/Calib3dTest.swift new file mode 100644 index 0000000000..85786e2fde --- /dev/null +++ b/modules/calib3d/misc/objc/test/Calib3dTest.swift @@ -0,0 +1,465 @@ +// +// Calib3dTest.swift +// +// Created by Giles Payne on 2020/05/26. +// + +import XCTest +import OpenCV + +class Calib3dTest: OpenCVTestCase { + + var size = Size() + + override func setUp() { + super.setUp() + size = Size(width: 3, height: 3) + } + + override func tearDown() { + super.tearDown() + } + + func testComposeRTMatMatMatMatMatMat() throws { + let rvec1 = Mat(rows: 3, cols: 1, type: CvType.CV_32F) + try rvec1.put(row: 0, col: 0, data: [0.5302828, 0.19925919, 0.40105945] as [Float]) + let tvec1 = Mat(rows: 3, cols: 1, type: CvType.CV_32F) + try tvec1.put(row: 0, col: 0, data: [0.81438506, 0.43713298, 0.2487897] as [Float]) + let rvec2 = Mat(rows: 3, cols: 1, type: CvType.CV_32F) + try rvec2.put(row: 0, col: 0, data: [0.77310503, 0.76209372, 0.30779448] as [Float]) + let tvec2 = Mat(rows: 3, cols: 1, type: CvType.CV_32F) + try tvec2.put(row: 0, col: 0, data: [0.70243168, 0.4784472, 0.79219002] as [Float]) + + let rvec3 = Mat() + let tvec3 = Mat() + + let outRvec = Mat(rows: 3, cols: 1, type: CvType.CV_32F) + try outRvec.put(row: 0, col: 0, data: [1.418641, 0.88665926, 0.56020796]) + let outTvec = Mat(rows: 3, cols: 1, type: CvType.CV_32F) + try outTvec.put(row: 0, col: 0, data: [1.4560841, 1.0680628, 0.81598103]) + + Calib3d.composeRT(rvec1: rvec1, tvec1: tvec1, rvec2: rvec2, tvec2: tvec2, rvec3: rvec3, tvec3: tvec3) + + try assertMatEqual(outRvec, rvec3, OpenCVTestCase.EPS) + try assertMatEqual(outTvec, tvec3, OpenCVTestCase.EPS) + } + + func testFilterSpecklesMatDoubleIntDouble() throws { + gray_16s_1024.copy(to: dst) + let center = Point(x: gray_16s_1024.rows() / 2, y: gray_16s_1024.cols() / 2) + Imgproc.circle(img: dst, center: center, radius: 1, color: Scalar.all(4096)) + + try assertMatNotEqual(gray_16s_1024, dst) + Calib3d.filterSpeckles(img: dst, newVal: 1024.0, maxSpeckleSize: 100, maxDiff: 0.0) + try assertMatEqual(gray_16s_1024, dst) + } + + func testFindChessboardCornersMatSizeMat() { + let patternSize = Size(width: 9, height: 6) + let corners = MatOfPoint2f() + Calib3d.findChessboardCorners(image: grayChess, patternSize: patternSize, corners: corners) + XCTAssertFalse(corners.empty()) + } + + func testFindChessboardCornersMatSizeMatInt() { + let patternSize = Size(width: 9, height: 6) + let corners = MatOfPoint2f() + Calib3d.findChessboardCorners(image: grayChess, patternSize: patternSize, corners: corners, flags: Calib3d.CALIB_CB_ADAPTIVE_THRESH + Calib3d.CALIB_CB_NORMALIZE_IMAGE + Calib3d.CALIB_CB_FAST_CHECK) + XCTAssertFalse(corners.empty()) + } + + func testFind4QuadCornerSubpix() { + let patternSize = Size(width: 9, height: 6) + let corners = MatOfPoint2f() + let region_size = Size(width: 5, height: 5) + Calib3d.findChessboardCorners(image: grayChess, patternSize: patternSize, corners: corners) + Calib3d.find4QuadCornerSubpix(img: grayChess, corners: corners, region_size: region_size) + XCTAssertFalse(corners.empty()) + } + + func testFindCirclesGridMatSizeMat() { + let size = 300 + let img = Mat(rows:Int32(size), cols:Int32(size), type:CvType.CV_8U) + img.setTo(scalar: Scalar(255)) + let centers = Mat() + + XCTAssertFalse(Calib3d.findCirclesGrid(image: img, patternSize: Size(width: 5, height: 5), centers: centers)) + + for i in 0..<5 { + for j in 0..<5 { + let x = Int32(size * (2 * i + 1) / 10) + let y = Int32(size * (2 * j + 1) / 10) + let pt = Point(x: x, y: y) + Imgproc.circle(img: img, center: pt, radius: 10, color: Scalar(0), thickness: -1) + } + } + + XCTAssert(Calib3d.findCirclesGrid(image: img, patternSize:Size(width:5, height:5), centers:centers)) + + XCTAssertEqual(25, centers.rows()) + XCTAssertEqual(1, centers.cols()) + XCTAssertEqual(CvType.CV_32FC2, centers.type()) + } + + func testFindCirclesGridMatSizeMatInt() { + let size:Int32 = 300 + let img = Mat(rows:size, cols: size, type: CvType.CV_8U) + img.setTo(scalar: Scalar(255)) + let centers = Mat() + + XCTAssertFalse(Calib3d.findCirclesGrid(image: img, patternSize: Size(width: 3, height: 5), centers: centers, flags: Calib3d.CALIB_CB_CLUSTERING | Calib3d.CALIB_CB_ASYMMETRIC_GRID)) + + let step = size * 2 / 15 + let offsetx = size / 6 + let offsety = (size - 4 * step) / 2 + for i:Int32 in 0...2 { + for j:Int32 in 0...4 { + let pt = Point(x: offsetx + (2 * i + j % 2) * step, y: offsety + step * j) + Imgproc.circle(img: img, center: pt, radius: 10, color: Scalar(0), thickness: -1) + } + } + + XCTAssert(Calib3d.findCirclesGrid(image: img, patternSize: Size(width: 3, height: 5), centers: centers, flags: Calib3d.CALIB_CB_CLUSTERING | Calib3d.CALIB_CB_ASYMMETRIC_GRID)) + + XCTAssertEqual(15, centers.rows()) + XCTAssertEqual(1, centers.cols()) + XCTAssertEqual(CvType.CV_32FC2, centers.type()) + } + + func testFindHomographyListOfPointListOfPoint() throws { + let NUM:Int32 = 20 + + let originalPoints = MatOfPoint2f() + originalPoints.alloc(NUM) + let transformedPoints = MatOfPoint2f() + transformedPoints.alloc(NUM) + + for i:Int32 in 0.. src, List dst, Mat cameraMatrix, Mat distCoeffs) + func testUndistortPointsListOfPointListOfPointMatMat() { + let src = MatOfPoint2f(array: [Point2f(x: 1, y: 2), Point2f(x: 3, y: 4), Point2f(x: -1, y: -1)]) + let dst = MatOfPoint2f() + let cameraMatrix = Mat.eye(rows: 3, cols: 3, type: CvType.CV_64FC1) + let distCoeffs = Mat(rows: 8, cols: 1, type: CvType.CV_64FC1, scalar: Scalar(0)) + + Calib3d.undistortPoints(src: src, dst: dst, cameraMatrix: cameraMatrix, distCoeffs: distCoeffs) + + XCTAssertEqual(src.toArray(), dst.toArray()) + } +} diff --git a/modules/core/CMakeLists.txt b/modules/core/CMakeLists.txt index ee2c4b4b43..59273a3a6b 100644 --- a/modules/core/CMakeLists.txt +++ b/modules/core/CMakeLists.txt @@ -19,7 +19,7 @@ ocv_add_dispatched_file_force_all(test_intrin512 TEST AVX512_SKX) ocv_add_module(core OPTIONAL opencv_cudev - WRAP java python js) + WRAP java objc python js) set(extra_libs "") diff --git a/modules/core/misc/objc/common/ArrayUtil.h b/modules/core/misc/objc/common/ArrayUtil.h new file mode 100644 index 0000000000..ceb0c997ee --- /dev/null +++ b/modules/core/misc/objc/common/ArrayUtil.h @@ -0,0 +1,16 @@ +// +// ArrayUtil.h +// +// Created by Giles Payne on 2020/02/09. +// + +#pragma once + +#import + +/** +* Utility function to create and populate an NSMutableArray with a specific size +* @param size Size of array to create +* @param val Value with which to initialize array elements +*/ +NSMutableArray* createArrayWithSize(int size, NSObject* val); diff --git a/modules/core/misc/objc/common/ArrayUtil.mm b/modules/core/misc/objc/common/ArrayUtil.mm new file mode 100644 index 0000000000..a18a8892fe --- /dev/null +++ b/modules/core/misc/objc/common/ArrayUtil.mm @@ -0,0 +1,15 @@ +// +// ArrayUtil.mm +// +// Created by Giles Payne on 2020/02/09. +// + +#import "ArrayUtil.h" + +NSMutableArray* createArrayWithSize(int size, NSObject* val) { + NSMutableArray *array = [NSMutableArray arrayWithCapacity:size]; + for (int i = 0; i < size; i++){ + [array addObject:val]; + } + return array; +} diff --git a/modules/core/misc/objc/common/ByteVector.h b/modules/core/misc/objc/common/ByteVector.h new file mode 100644 index 0000000000..e0cce73877 --- /dev/null +++ b/modules/core/misc/objc/common/ByteVector.h @@ -0,0 +1,88 @@ +// +// ByteVector.h +// +// Created by Giles Payne on 2020/01/04. +// + +#pragma once + +#import +#ifdef __cplusplus +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** +* Utility class to wrap a `std::vector` +*/ +@interface ByteVector : NSObject + +#pragma mark - Constructors + +/** +* Create ByteVector and initialize with the contents of an NSData object +* @param data NSData containing raw byte array +*/ +-(instancetype)initWithData:(NSData*)data; + +/** +* Create ByteVector and initialize with the contents of another ByteVector object +* @param src ByteVector containing data to copy +*/ +-(instancetype)initWithVector:(ByteVector*)src; + +#ifdef __OBJC__ +/** +* Create ByteVector from raw C array +* @param array The raw C array +* @elements elements The number of elements in the array +*/ +-(instancetype)initWithNativeArray:(char*)array elements:(NSInteger)elements; +#endif + +#ifdef __cplusplus +/** +* Create ByteVector from std::vector +* @param src The std::vector object to wrap +*/ +-(instancetype)initWithStdVector:(std::vector&)src; ++(instancetype)fromNative:(std::vector&)src; +#endif + +#pragma mark - Properties + +/** +* Length of the vector +*/ +@property(readonly) NSInteger length; + +#ifdef __OBJC__ +/** +* Raw C array +*/ +@property(readonly) char* nativeArray; +#endif + +#ifdef __cplusplus +/** +* The wrapped std::vector object +*/ +@property(readonly) std::vector& nativeRef; +#endif + +/** +* NSData object containing the raw byte data +*/ +@property(readonly) NSData* data; + +#pragma mark - Accessor method + +/** +* Return array element +* @param index Index of the array element to return +*/ +-(char)get:(NSInteger)index; + +@end +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/ByteVector.mm b/modules/core/misc/objc/common/ByteVector.mm new file mode 100644 index 0000000000..e1f5ae3dba --- /dev/null +++ b/modules/core/misc/objc/common/ByteVector.mm @@ -0,0 +1,76 @@ +// +// ByteVector.m +// +// Created by Giles Payne on 2020/01/04. +// + +#import "ByteVector.h" +#import + +@implementation ByteVector { + std::vector v; +} + +-(instancetype)initWithData:(NSData*)data { + self = [super init]; + if (self) { + if (data.length % sizeof(char) != 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Invalid data length" userInfo:nil]; + } + v.insert(v.begin(), (char*)data.bytes, (char*)data.bytes + data.length/sizeof(char)); + } + return self; +} + +-(instancetype)initWithVector:(ByteVector*)src { + self = [super init]; + if (self) { + v.insert(v.begin(), src.nativeRef.begin(), src.nativeRef.end()); + } + return self; +} + +-(NSInteger)length { + return v.size(); +} + +-(char*)nativeArray { + return (char*)v.data(); +} + +-(instancetype)initWithNativeArray:(char*)array elements:(NSInteger)elements { + self = [super init]; + if (self) { + v.insert(v.begin(), array, array + elements); + } + return self; +} + +- (std::vector&)nativeRef { + return v; +} + +-(instancetype)initWithStdVector:(std::vector&)src { + self = [super init]; + if (self) { + v.insert(v.begin(), src.begin(), src.end()); + } + return self; +} + ++(instancetype)fromNative:(std::vector&)src { + return [[ByteVector alloc] initWithStdVector:src]; +} + +-(char)get:(NSInteger)index { + if (index < 0 || index >= (long)v.size()) { + @throw [NSException exceptionWithName:NSRangeException reason:@"Invalid data length" userInfo:nil]; + } + return v[index]; +} + +-(NSData*)data { + return [NSData dataWithBytesNoCopy:v.data() length:(v.size() * sizeof(char)) freeWhenDone:NO]; +} + +@end diff --git a/modules/core/misc/objc/common/ByteVectorExt.swift b/modules/core/misc/objc/common/ByteVectorExt.swift new file mode 100644 index 0000000000..e553de0aa4 --- /dev/null +++ b/modules/core/misc/objc/common/ByteVectorExt.swift @@ -0,0 +1,53 @@ +// +// ByteVectorExt.swift +// +// Created by Giles Payne on 2020/01/04. +// + +import Foundation + +public extension ByteVector { + convenience init(_ array:[Int8]) { + let data = array.withUnsafeBufferPointer { Data(buffer: $0) } + self.init(data:data); + } + + subscript(index: Int) -> Int8 { + get { + return self.get(index) + } + } + + var array: [Int8] { + get { + var ret = Array(repeating: 0, count: data.count/MemoryLayout.stride) + _ = ret.withUnsafeMutableBytes { data.copyBytes(to: $0) } + return ret + } + } +} + +extension ByteVector : Sequence { + public typealias Iterator = ByteVectorIterator + public func makeIterator() -> ByteVectorIterator { + return ByteVectorIterator(self) + } +} + +public struct ByteVectorIterator: IteratorProtocol { + public typealias Element = Int8 + let byteVector: ByteVector + var pos = 0 + + init(_ byteVector: ByteVector) { + self.byteVector = byteVector + } + + mutating public func next() -> Int8? { + guard pos >= 0 && pos < byteVector.length + else { return nil } + + pos += 1 + return byteVector.get(pos - 1) + } +} diff --git a/modules/core/misc/objc/common/CVObjcUtil.h b/modules/core/misc/objc/common/CVObjcUtil.h new file mode 100644 index 0000000000..be28f75af7 --- /dev/null +++ b/modules/core/misc/objc/common/CVObjcUtil.h @@ -0,0 +1,85 @@ +// +// CVObjcUtil.h +// +// Created by Giles Payne on 2020/01/02. +// + +#pragma once + +typedef union { double d; int64_t l; } V64; +typedef union { float f; int32_t i; } V32; + +#define DOUBLE_TO_BITS(x) ((V64){ .d = x }).l +#define FLOAT_TO_BITS(x) ((V32){ .f = x }).i + +#ifdef __cplusplus +#import + +#define MAKE_PTR(t) (*((cv::Ptr*)self.nativePtr)) + +template std::vector objc2cv(NSArray* _Nonnull array, CV& (* _Nonnull converter)(OBJC* _Nonnull)) { + std::vector ret; + for (OBJC* obj in array) { + ret.push_back(converter(obj)); + } + return ret; +} + +#define OBJC2CV(CV_CLASS, OBJC_CLASS, v, a) \ + std::vector v = objc2cv(a, [](OBJC_CLASS* objc) -> CV_CLASS& { return objc.nativeRef; }) + +#define OBJC2CV_CUSTOM(CV_CLASS, OBJC_CLASS, v, a, CONV) \ + std::vector v; \ + for (OBJC_CLASS* obj in a) { \ + CV_CLASS tmp = CONV(obj); \ + v.push_back(tmp); \ + } + +template void cv2objc(std::vector& vector, NSMutableArray* _Nonnull array, OBJC* _Nonnull (* _Nonnull converter)(CV&)) { + [array removeAllObjects]; + for (size_t index = 0; index < vector.size(); index++) { + [array addObject:converter(vector[index])]; + } +} + +#define CV2OBJC(CV_CLASS, OBJC_CLASS, v, a) \ + cv2objc(v, a, [](CV_CLASS& cv) -> OBJC_CLASS* { return [OBJC_CLASS fromNative:cv]; }) + +#define CV2OBJC_CUSTOM(CV_CLASS, OBJC_CLASS, v, a, UNCONV) \ + [a removeAllObjects]; \ + for (size_t index = 0; index < v.size(); index++) { \ + OBJC_CLASS *tmp = UNCONV(v[index]); \ + [a addObject:tmp]; \ + } + +template std::vector> objc2cv2(NSArray*>* _Nonnull array, CV& (* _Nonnull converter)(OBJC* _Nonnull)) { + std::vector> ret; + for (NSArray* innerArray in array) { + std::vector innerVector; + for (OBJC* obj in innerArray) { + innerVector.push_back(converter(obj)); + } + ret.push_back(innerVector); + } + return ret; +} + +#define OBJC2CV2(CV_CLASS, OBJC_CLASS, v, a) \ + std::vector> v = objc2cv2(a, [](OBJC_CLASS* objc) -> CV_CLASS& { return objc.nativeRef; }) + +template void cv2objc2(std::vector>& vector, NSMutableArray*>* _Nonnull array, OBJC* _Nonnull (* _Nonnull converter)(CV&)) { + [array removeAllObjects]; + for (size_t index = 0; index < vector.size(); index++) { + std::vector& innerVector = vector[index]; + NSMutableArray* innerArray = [NSMutableArray arrayWithCapacity:innerVector.size()]; + for (size_t index2 = 0; index2 < innerVector.size(); index2++) { + [innerArray addObject:converter(innerVector[index2])]; + } + [array addObject:innerArray]; + } +} + +#define CV2OBJC2(CV_CLASS, OBJC_CLASS, v, a) \ + cv2objc2(v, a, [](CV_CLASS& cv) -> OBJC_CLASS* { return [OBJC_CLASS fromNative:cv]; }) + +#endif diff --git a/modules/core/misc/objc/common/Converters.h b/modules/core/misc/objc/common/Converters.h new file mode 100755 index 0000000000..c73c3fac76 --- /dev/null +++ b/modules/core/misc/objc/common/Converters.h @@ -0,0 +1,98 @@ +// +// Converters.h +// +// Created by Giles Payne on 2020/03/03. +// + +#pragma once + +#ifdef __cplusplus +#import +#endif + +#import +#import "Mat.h" +#import "CvType.h" +#import "Point2i.h" +#import "Point2f.h" +#import "Point2d.h" +#import "Point3i.h" +#import "Point3f.h" +#import "Point3d.h" +#import "Rect2i.h" +#import "Rect2d.h" +#import "KeyPoint.h" +#import "DMatch.h" +#import "RotatedRect.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface Converters : NSObject + ++ (Mat*)vector_Point_to_Mat:(NSArray*)pts NS_SWIFT_NAME(vector_Point_to_Mat(_:)); + ++ (NSArray*)Mat_to_vector_Point:(Mat*)mat NS_SWIFT_NAME(Mat_to_vector_Point(_:)); + ++ (Mat*)vector_Point2f_to_Mat:(NSArray*)pts NS_SWIFT_NAME(vector_Point2f_to_Mat(_:)); + ++ (NSArray*)Mat_to_vector_Point2f:(Mat*)mat NS_SWIFT_NAME(Mat_to_vector_Point2f(_:)); + ++ (Mat*)vector_Point2d_to_Mat:(NSArray*)pts NS_SWIFT_NAME(vector_Point2d_to_Mat(_:)); + ++ (NSArray*)Mat_to_vector_Point2d:(Mat*)mat NS_SWIFT_NAME(Mat_to_vector_Point2d(_:)); + ++ (Mat*)vector_Point3i_to_Mat:(NSArray*)pts NS_SWIFT_NAME(vector_Point3i_to_Mat(_:)); + ++ (NSArray*)Mat_to_vector_Point3i:(Mat*)mat NS_SWIFT_NAME(Mat_to_vector_Point3i(_:)); + ++ (Mat*)vector_Point3f_to_Mat:(NSArray*)pts NS_SWIFT_NAME(vector_Point3f_to_Mat(_:)); + ++ (NSArray*)Mat_to_vector_Point3f:(Mat*)mat NS_SWIFT_NAME(Mat_to_vector_Point3f(_:)); + ++ (Mat*)vector_Point3d_to_Mat:(NSArray*)pts NS_SWIFT_NAME(vector_Point3d_to_Mat(_:)); + ++ (NSArray*)Mat_to_vector_Point3d:(Mat*)mat NS_SWIFT_NAME(Mat_to_vector_Point3d(_:)); + ++ (Mat*)vector_float_to_Mat:(NSArray*)fs NS_SWIFT_NAME(vector_float_to_Mat(_:)); + ++ (NSArray*)Mat_to_vector_float:(Mat*)mat NS_SWIFT_NAME(Mat_to_vector_float(_:)); + ++ (Mat*)vector_uchar_to_Mat:(NSArray*)us NS_SWIFT_NAME(vector_uchar_to_Mat(_:)); + ++ (NSArray*)Mat_to_vector_uchar:(Mat*)mat NS_SWIFT_NAME(Mat_to_vector_uchar(_:)); + ++ (Mat*)vector_char_to_Mat:(NSArray*)cs NS_SWIFT_NAME(vector_char_to_Mat(_:)); + ++ (NSArray*)Mat_to_vector_char:(Mat*)mat NS_SWIFT_NAME(Mat_to_vector_char(_:)); + ++ (Mat*)vector_int_to_Mat:(NSArray*)is NS_SWIFT_NAME(vector_int_to_Mat(_:)); + ++ (NSArray*)Mat_to_vector_int:(Mat*)mat NS_SWIFT_NAME(Mat_to_vector_int(_:)); + ++ (Mat*)vector_Rect_to_Mat:(NSArray*)rs NS_SWIFT_NAME(vector_Rect_to_Mat(_:)); + ++ (NSArray*)Mat_to_vector_Rect:(Mat*)mat NS_SWIFT_NAME(Mat_to_vector_Rect(_:)); + ++ (Mat*)vector_Rect2d_to_Mat:(NSArray*)rs NS_SWIFT_NAME(vector_Rect2d_to_Mat(_:)); + ++ (NSArray*)Mat_to_vector_Rect2d:(Mat*)mat NS_SWIFT_NAME(Mat_to_vector_Rect2d(_:)); + ++ (Mat*)vector_KeyPoint_to_Mat:(NSArray*)kps NS_SWIFT_NAME(vector_KeyPoint_to_Mat(_:)); + ++ (NSArray*)Mat_to_vector_KeyPoint:(Mat*)mat NS_SWIFT_NAME(Mat_to_vector_KeyPoint(_:)); + ++ (Mat*)vector_double_to_Mat:(NSArray*)ds NS_SWIFT_NAME(vector_double_to_Mat(_:)); + ++ (NSArray*)Mat_to_vector_double:(Mat*)mat NS_SWIFT_NAME(Mat_to_vector_double(_:)); + ++ (Mat*)vector_DMatch_to_Mat:(NSArray*)matches NS_SWIFT_NAME(vector_DMatch_to_Mat(_:)); + ++ (NSArray*)Mat_to_vector_DMatch:(Mat*)mat NS_SWIFT_NAME(Mat_to_vector_DMatch(_:)); + ++ (Mat*)vector_RotatedRect_to_Mat:(NSArray*)rs NS_SWIFT_NAME(vector_RotatedRect_to_Mat(_:)); + ++ (NSArray*)Mat_to_vector_RotatedRect:(Mat*)mat NS_SWIFT_NAME(Mat_to_vector_RotatedRect(_:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Converters.mm b/modules/core/misc/objc/common/Converters.mm new file mode 100644 index 0000000000..1260cda7e6 --- /dev/null +++ b/modules/core/misc/objc/common/Converters.mm @@ -0,0 +1,205 @@ +// +// Converters.mm +// +// Created by Giles Payne on 31/05/2020. +// + +#import "Converters.h" +#import "ArrayUtil.h" +#import "MatOfPoint2i.h" +#import "MatOfPoint2f.h" +#import "MatOfPoint3.h" +#import "MatOfPoint3f.h" +#import "MatOfFloat.h" +#import "MatOfByte.h" +#import "MatOfInt.h" +#import "MatOfDouble.h" +#import "MatOfRect2i.h" +#import "MatOfRect2d.h" +#import "MatOfKeyPoint.h" +#import "MatOfDMatch.h" +#import "MatOfRotatedRect.h" + +@implementation Converters + ++ (Mat*)vector_Point_to_Mat:(NSArray*)pts { + return [[MatOfPoint2i alloc] initWithArray:pts]; +} + ++ (NSArray*)Mat_to_vector_Point:(Mat*)mat { + return [[[MatOfPoint2i alloc] initWithMat:mat] toArray]; +} + ++ (Mat*)vector_Point2f_to_Mat:(NSArray*)pts { + return [[MatOfPoint2f alloc] initWithArray:pts]; +} + ++ (NSArray*)Mat_to_vector_Point2f:(Mat*)mat { + return [[[MatOfPoint2f alloc] initWithMat:mat] toArray]; +} + ++ (Mat*)vector_Point2d_to_Mat:(NSArray*)pts { + Mat* res = [[Mat alloc] initWithRows:(int)pts.count cols:1 type:CV_64FC2]; + NSMutableArray* buff = [NSMutableArray arrayWithCapacity:pts.count*2]; + for (Point2d* pt in pts) { + [buff addObject:[NSNumber numberWithDouble:pt.x]]; + [buff addObject:[NSNumber numberWithDouble:pt.y]]; + } + [res put:0 col:0 data:buff]; + return res; +} + ++ (NSArray*)Mat_to_vector_Point2d:(Mat*)mat { + if (mat.cols != 1 || mat.type != CV_64FC2) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Invalid Mat. Mat must be of type CV_64FC2 and have 1 column."] + userInfo:nil]; + @throw exception; + } + NSMutableArray* ret = [NSMutableArray new]; + NSMutableArray* buff = createArrayWithSize(mat.rows*2, [NSNumber numberWithInt:0]); + [mat get:0 col:0 data:buff]; + for (int i = 0; i < mat.rows; i++) { + [ret addObject:[[Point2d alloc] initWithX:buff[i * 2].doubleValue y:buff[i * 2 + 1].doubleValue]]; + } + return ret; +} + ++ (Mat*)vector_Point3i_to_Mat:(NSArray*)pts { + return [[MatOfPoint3 alloc] initWithArray:pts]; +} + ++ (NSArray*)Mat_to_vector_Point3i:(Mat*)mat { + return [[[MatOfPoint3 alloc] initWithMat:mat] toArray]; +} + ++ (Mat*)vector_Point3f_to_Mat:(NSArray*)pts { + return [[MatOfPoint3f alloc] initWithArray:pts]; +} + ++ (NSArray*)Mat_to_vector_Point3f:(Mat*)mat { + return [[[MatOfPoint3f alloc] initWithMat:mat] toArray]; +} + ++ (Mat*)vector_Point3d_to_Mat:(NSArray*)pts { + Mat* res = [[Mat alloc] initWithRows:(int)pts.count cols:1 type:CV_64FC3]; + NSMutableArray* buff = [NSMutableArray arrayWithCapacity:pts.count*3]; + for (Point3d* pt in pts) { + [buff addObject:[NSNumber numberWithDouble:pt.x]]; + [buff addObject:[NSNumber numberWithDouble:pt.y]]; + [buff addObject:[NSNumber numberWithDouble:pt.z]]; + } + [res put:0 col:0 data:buff]; + return res; +} + ++ (NSArray*)Mat_to_vector_Point3d:(Mat*)mat { + if (mat.cols != 1 || mat.type != CV_64FC3) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Invalid Mat. Mat must be of type CV_64FC3 and have 1 column."] + userInfo:nil]; + @throw exception; + } + NSMutableArray* ret = [NSMutableArray new]; + NSMutableArray* buff = createArrayWithSize(mat.rows*3, [NSNumber numberWithInt:0]); + [mat get:0 col:0 data:buff]; + for (int i = 0; i < mat.rows; i++) { + [ret addObject:[[Point3d alloc] initWithX:buff[i * 3].doubleValue y:buff[i * 3 + 1].doubleValue z:buff[i * 3 + 2].doubleValue]]; + } + return ret; +} + ++ (Mat*)vector_float_to_Mat:(NSArray*)fs { + return [[MatOfFloat alloc] initWithArray:fs]; +} + ++ (NSArray*)Mat_to_vector_float:(Mat*)mat { + return [[[MatOfFloat alloc] initWithMat:mat] toArray]; +} + ++ (Mat*)vector_uchar_to_Mat:(NSArray*)us { + return [[MatOfByte alloc] initWithArray:us]; +} + ++ (NSArray*)Mat_to_vector_uchar:(Mat*)mat { + return [[[MatOfByte alloc] initWithMat:mat] toArray]; +} + ++ (Mat*)vector_char_to_Mat:(NSArray*)cs { + Mat* res = [[Mat alloc] initWithRows:(int)cs.count cols:1 type:CV_8S]; + [res put:0 col:0 data:cs]; + return res; +} + ++ (NSArray*)Mat_to_vector_char:(Mat*)mat { + if (mat.cols != 1 || mat.type != CV_8S) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Invalid Mat. Mat must be of type CV_8S and have 1 column."] + userInfo:nil]; + @throw exception; + } + NSMutableArray* ret = createArrayWithSize(mat.rows, @0); + [mat get:0 col:0 data:ret]; + return ret; +} + ++ (Mat*)vector_int_to_Mat:(NSArray*)is { + return [[MatOfInt alloc] initWithArray:is]; +} + ++ (NSArray*)Mat_to_vector_int:(Mat*)mat { + return [[[MatOfInt alloc] initWithMat:mat] toArray]; +} + ++ (Mat*)vector_Rect_to_Mat:(NSArray*)rs { + return [[MatOfRect2i alloc] initWithArray:rs]; +} + ++ (NSArray*)Mat_to_vector_Rect:(Mat*)mat { + return [[[MatOfRect2i alloc] initWithMat:mat] toArray]; +} + ++ (Mat*)vector_Rect2d_to_Mat:(NSArray*)rs { + return [[MatOfRect2d alloc] initWithArray:rs]; +} + ++ (NSArray*)Mat_to_vector_Rect2d:(Mat*)mat { + return [[[MatOfRect2d alloc] initWithMat:mat] toArray]; +} + ++ (Mat*)vector_KeyPoint_to_Mat:(NSArray*)kps { + return [[MatOfKeyPoint alloc] initWithArray:kps]; +} + ++ (NSArray*)Mat_to_vector_KeyPoint:(Mat*)mat { + return [[[MatOfKeyPoint alloc] initWithMat:mat] toArray]; +} + ++ (Mat*)vector_double_to_Mat:(NSArray*)ds { + return [[MatOfDouble alloc] initWithArray:ds]; +} + ++ (NSArray*)Mat_to_vector_double:(Mat*)mat { + return [[[MatOfDouble alloc] initWithMat:mat] toArray]; +} + ++ (Mat*)vector_DMatch_to_Mat:(NSArray*)matches { + return [[MatOfDMatch alloc] initWithArray:matches]; +} + ++ (NSArray*)Mat_to_vector_DMatch:(Mat*)mat { + return [[[MatOfDMatch alloc] initWithMat:mat] toArray]; +} + ++ (Mat*)vector_RotatedRect_to_Mat:(NSArray*)rs { + return [[MatOfRotatedRect alloc] initWithArray:rs]; +} + ++ (NSArray*)Mat_to_vector_RotatedRect:(Mat*)mat { + return [[[MatOfRotatedRect alloc] initWithMat:mat] toArray]; +} + +@end diff --git a/modules/core/misc/objc/common/CvType.h b/modules/core/misc/objc/common/CvType.h new file mode 100644 index 0000000000..15d1598da0 --- /dev/null +++ b/modules/core/misc/objc/common/CvType.h @@ -0,0 +1,67 @@ +// +// CvType.h +// +// Created by Giles Payne on 2019/10/13. +// + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** +* Utility functions for handling CvType values +*/ +@interface CvType : NSObject + +#pragma mark - Type Utility functions + +/** +* Create CvType value from depth and channel values +* @param depth Depth value. One of CV_8U, CV_8S, CV_16U, CV_16S, CV_32S, CV_32F or CV_64F +* @param channels Number of channels (from 1 to (CV_CN_MAX - 1)) +*/ ++ (int)makeType:(int)depth channels:(int)channels; + +/** +* Get number of channels for type +* @param type Type value +*/ ++ (int)channels:(int)type; + +/** +* Get depth for type +* @param type Type value +*/ ++ (int)depth:(int)type; + +/** +* Get raw type size in bytes for type +* @param type Type value +*/ ++ (int)rawTypeSize:(int)type; + +/** +* Returns true if the raw type is an integer type (if depth is CV_8U, CV_8S, CV_16U, CV_16S or CV_32S) +* @param type Type value +*/ ++ (BOOL)isInteger:(int)type; + +/** +* Get element size in bytes for type +* @param type Type value +*/ ++ (int)ELEM_SIZE:(int)type NS_SWIFT_NAME(elemSize(_:)); + +/** +* Get the string name for type +* @param type Type value +*/ ++ (NSString*)typeToString:(int)type; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/CvType.mm b/modules/core/misc/objc/common/CvType.mm new file mode 100644 index 0000000000..0b8ee34540 --- /dev/null +++ b/modules/core/misc/objc/common/CvType.mm @@ -0,0 +1,105 @@ +// +// CvType.m +// +// Created by Giles Payne on 2019/10/13. +// + +#import "CvType.h" + +@implementation CvType + ++ (int)makeType:(int)depth channels:(int)channels { + if (channels <= 0 || channels >= CV_CN_MAX) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Channels count should be 1..%d", CV_CN_MAX - 1] + userInfo:nil]; + @throw exception; + } + if (depth < 0 || depth >= CV_DEPTH_MAX) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Data type depth should be 0..%d", CV_DEPTH_MAX - 1] + userInfo:nil]; + @throw exception; + } + return (depth & (CV_DEPTH_MAX - 1)) + ((channels - 1) << CV_CN_SHIFT); +} + ++ (int)channels:(int)type { + return (type >> CV_CN_SHIFT) + 1; +} + ++ (int)depth:(int)type { + return type & (CV_DEPTH_MAX - 1); +} + ++ (BOOL)isInteger:(int)type { + return [CvType depth:type] < CV_32F; +} + ++ (int)typeSizeBits:(int)type { + int depth = [CvType depth:type]; + switch (depth) { + case CV_8U: + case CV_8S: + return 8; + case CV_16U: + case CV_16S: + case CV_16F: + return 16; + case CV_32S: + case CV_32F: + return 32; + case CV_64F: + return 64; + default: + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Unsupported CvType value: %d", type] + userInfo:nil]; + @throw exception; + } +} + ++ (int)rawTypeSize:(int)type { + return [CvType typeSizeBits:type] >> 3; +} + ++ (char)typeMnenomic:(int)type { + int depth = [CvType depth:type]; + switch (depth) { + case CV_8U: + case CV_16U: + return 'U'; + case CV_8S: + case CV_16S: + case CV_32S: + return 'S'; + case CV_16F: + case CV_32F: + case CV_64F: + return 'F'; + default: + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Unsupported CvType value: %d", type] + userInfo:nil]; + @throw exception; + } +} + ++ (int)ELEM_SIZE:(int)type { + int typeSizeBytes = [CvType rawTypeSize:type]; + return typeSizeBytes * [CvType channels:type]; +} + ++ (NSString*)typeToString:(int)type { + int typeSizeBits = [CvType typeSizeBits:type]; + char typeMnenomic = [CvType typeMnenomic:type]; + int channels = [CvType channels:type]; + NSString* channelsSuffix = [NSString stringWithFormat:(channels <= 4)?@"%d":@"(%d)", channels]; + return [NSString stringWithFormat:@"CV_%d%cC%@", typeSizeBits, typeMnenomic, channelsSuffix]; +} + +@end diff --git a/modules/core/misc/objc/common/CvTypeExt.swift b/modules/core/misc/objc/common/CvTypeExt.swift new file mode 100644 index 0000000000..de065ea2de --- /dev/null +++ b/modules/core/misc/objc/common/CvTypeExt.swift @@ -0,0 +1,90 @@ +// +// CvTypeExt.swift +// +// Created by Giles Payne on 2020/01/19. +// + +import Foundation + +public extension CvType { + static let CV_8U: Int32 = 0 + static let CV_8S: Int32 = 1 + static let CV_16U: Int32 = 2 + static let CV_16S: Int32 = 3 + static let CV_32S: Int32 = 4 + static let CV_32F: Int32 = 5 + static let CV_64F: Int32 = 6 + static let CV_16F: Int32 = 7 + + static let CV_8UC1: Int32 = CV_8UC(1) + static let CV_8UC2: Int32 = CV_8UC(2) + static let CV_8UC3: Int32 = CV_8UC(3) + static let CV_8UC4: Int32 = CV_8UC(4) + static let CV_8SC1: Int32 = CV_8SC(1) + static let CV_8SC2: Int32 = CV_8SC(2) + static let CV_8SC3: Int32 = CV_8SC(3) + static let CV_8SC4: Int32 = CV_8SC(4) + + static let CV_16UC1: Int32 = CV_16UC(1) + static let CV_16UC2: Int32 = CV_16UC(2) + static let CV_16UC3: Int32 = CV_16UC(3) + static let CV_16UC4: Int32 = CV_16UC(4) + static let CV_16SC1: Int32 = CV_16SC(1) + static let CV_16SC2: Int32 = CV_16SC(2) + static let CV_16SC3: Int32 = CV_16SC(3) + static let CV_16SC4: Int32 = CV_16SC(4) + + static let CV_32SC1: Int32 = CV_32SC(1) + static let CV_32SC2: Int32 = CV_32SC(2) + static let CV_32SC3: Int32 = CV_32SC(3) + static let CV_32SC4: Int32 = CV_32SC(4) + static let CV_32FC1: Int32 = CV_32FC(1) + static let CV_32FC2: Int32 = CV_32FC(2) + static let CV_32FC3: Int32 = CV_32FC(3) + static let CV_32FC4: Int32 = CV_32FC(4) + + static let CV_64FC1: Int32 = CV_64FC(1) + static let CV_64FC2: Int32 = CV_64FC(2) + static let CV_64FC3: Int32 = CV_64FC(3) + static let CV_64FC4: Int32 = CV_64FC(4) + static let CV_16FC1: Int32 = CV_16FC(1) + static let CV_16FC2: Int32 = CV_16FC(2) + static let CV_16FC3: Int32 = CV_16FC(3) + static let CV_16FC4: Int32 = CV_16FC(4) + + static let CV_CN_MAX = 512 + static let CV_CN_SHIFT = 3 + static let CV_DEPTH_MAX = 1 << CV_CN_SHIFT + + static func CV_8UC(_ channels:Int32) -> Int32 { + return make(CV_8U, channels: channels) + } + + static func CV_8SC(_ channels:Int32) -> Int32 { + return make(CV_8S, channels: channels) + } + + static func CV_16UC(_ channels:Int32) -> Int32 { + return make(CV_16U, channels: channels) + } + + static func CV_16SC(_ channels:Int32) -> Int32 { + return make(CV_16S, channels: channels) + } + + static func CV_32SC(_ channels:Int32) -> Int32 { + return make(CV_32S, channels: channels) + } + + static func CV_32FC(_ channels:Int32) -> Int32 { + return make(CV_32F, channels: channels) + } + + static func CV_64FC(_ channels:Int32) -> Int32 { + return make(CV_64F, channels: channels) + } + + static func CV_16FC(_ channels:Int32) -> Int32 { + return make(CV_16F, channels: channels) + } +} diff --git a/modules/core/misc/objc/common/DMatch.h b/modules/core/misc/objc/common/DMatch.h new file mode 100644 index 0000000000..79d9f557d1 --- /dev/null +++ b/modules/core/misc/objc/common/DMatch.h @@ -0,0 +1,82 @@ +// +// DMatch.h +// +// Created by Giles Payne on 2019/12/25. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** +* Structure for matching: query descriptor index, train descriptor index, train +* image index and distance between descriptors. +*/ +@interface DMatch : NSObject + +/** + * Query descriptor index. + */ +@property int queryIdx; + +/** +* Train descriptor index. +*/ +@property int trainIdx; + +/** +* Train image index. +*/ +@property int imgIdx; + +/** +* Distance +*/ +@property float distance; +#ifdef __cplusplus +@property(readonly) cv::DMatch& nativeRef; +#endif + +- (instancetype)init; +- (instancetype)initWithQueryIdx:(int)queryIdx trainIdx:(int)trainIdx distance:(float)distance; +- (instancetype)initWithQueryIdx:(int)queryIdx trainIdx:(int)trainIdx imgIdx:(int)imgIdx distance:(float)distance; +#ifdef __cplusplus ++ (instancetype)fromNative:(cv::DMatch&)dMatch; +#endif + +/** +* Distance comparison +* @param it DMatch object to compare +*/ +- (BOOL)lessThan:(DMatch*)it; + +/** +* Clone object +*/ +- (DMatch*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)other; + +/** +* Calculate hash for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString*)description; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/DMatch.mm b/modules/core/misc/objc/common/DMatch.mm new file mode 100644 index 0000000000..7d74f254ad --- /dev/null +++ b/modules/core/misc/objc/common/DMatch.mm @@ -0,0 +1,107 @@ +// +// DMatch.m +// +// Created by Giles Payne on 2019/12/25. +// + +#import "DMatch.h" +#import "CVObjcUtil.h" + +@implementation DMatch { + cv::DMatch native; +} + +- (int)queryIdx { + return native.queryIdx; +} + +- (void)setQueryIdx:(int)queryIdx { + native.queryIdx = queryIdx; +} + +- (int)trainIdx { + return native.trainIdx; +} + +- (void)setTrainIdx:(int)trainIdx { + native.trainIdx = trainIdx; +} + +- (int)imgIdx { + return native.imgIdx; +} + +- (void)setImgIdx:(int)imgIdx { + native.imgIdx = imgIdx; +} + +- (float)distance { + return native.distance; +} + +- (void)setDistance:(float)distance { + native.distance = distance; +} + +- (cv::DMatch&)nativeRef { + return native; +} + +- (instancetype)init { + return [self initWithQueryIdx:-1 trainIdx:-1 distance:FLT_MAX]; +} + +- (instancetype)initWithQueryIdx:(int)queryIdx trainIdx:(int)trainIdx distance:(float)distance { + return [self initWithQueryIdx:queryIdx trainIdx:trainIdx imgIdx:-1 distance:distance]; +} + +- (instancetype)initWithQueryIdx:(int)queryIdx trainIdx:(int)trainIdx imgIdx:(int)imgIdx distance:(float)distance { + self = [super init]; + if (self != nil) { + self.queryIdx = queryIdx; + self.trainIdx = trainIdx; + self.imgIdx = imgIdx; + self.distance = distance; + } + return self; +} + ++ (instancetype)fromNative:(cv::DMatch&)dMatch { + return [[DMatch alloc] initWithQueryIdx:dMatch.queryIdx trainIdx:dMatch.trainIdx imgIdx:dMatch.imgIdx distance:dMatch.distance]; +} + +- (BOOL)lessThan:(DMatch*)it { + return self.distance < it.distance; +} + + +- (DMatch*)clone { + return [[DMatch alloc] initWithQueryIdx:self.queryIdx trainIdx:self.trainIdx imgIdx:self.imgIdx distance:self.distance]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[DMatch class]]) { + return NO; + } else { + DMatch* dMatch = (DMatch*)other; + return self.queryIdx == dMatch.queryIdx && self.trainIdx == dMatch.trainIdx && self.imgIdx == dMatch.imgIdx && self.distance == dMatch.distance; + } +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + result = prime * result + self.queryIdx; + result = prime * result + self.trainIdx; + result = prime * result + self.imgIdx; + result = prime * result + FLOAT_TO_BITS(self.distance); + return result; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"DMatch { queryIdx: %d, trainIdx: %d, imgIdx: %d, distance: %f}", self.queryIdx, self.trainIdx, self.imgIdx, self.distance]; +} + +@end diff --git a/modules/core/misc/objc/common/Double2.h b/modules/core/misc/objc/common/Double2.h new file mode 100644 index 0000000000..7512e37ee2 --- /dev/null +++ b/modules/core/misc/objc/common/Double2.h @@ -0,0 +1,93 @@ +// +// Double2.h +// +// Created by Giles Payne on 2020/05/22. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +@class Mat; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Simple wrapper for a vector of two `double` +*/ +@interface Double2 : NSObject + +#pragma mark - Properties + +/** +* First vector element +*/ +@property double v0; + +/** +* Second vector element +*/ +@property double v1; + +/** +* Third vector element +*/ +@property double v2; + + +#ifdef __cplusplus +/** +* The wrapped vector +*/ +@property(readonly) cv::Vec2d& nativeRef; +#endif + +#pragma mark - Constructors + +/** +* Create zero-initialize vecior +*/ +-(instancetype)init; + +/** +* Create vector with specified element values +* @param v0 First element +* @param v1 Second element +*/ +-(instancetype)initWithV0:(double)v0 v1:(double)v1; + +/** +* Create vector with specified element values +* @param vals array of element values +*/ +-(instancetype)initWithVals:(NSArray*)vals; +#ifdef __cplusplus ++(instancetype)fromNative:(cv::Vec2d&)vec2d; +#endif + +/** +* Update vector with specified element values +* @param vals array of element values +*/ +-(void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +/** +* Get vector as an array +*/ +-(NSArray*)get; + +#pragma mark - Common Methods + +/** +* Compare for equality +* @param other Object to compare +*/ +-(BOOL)isEqual:(nullable id)other; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Double2.mm b/modules/core/misc/objc/common/Double2.mm new file mode 100644 index 0000000000..92ea02187f --- /dev/null +++ b/modules/core/misc/objc/common/Double2.mm @@ -0,0 +1,75 @@ +// +// Double2.mm +// +// Created by Giles Payne on 2020/05/22. +// + +#import "Double2.h" +#import "Mat.h" + +@implementation Double2 { + cv::Vec2d native; +} + +-(double)v0 { + return native[0]; +} + +-(void)setV0:(double)v { + native[0] = v; +} + +-(double)v1 { + return native[1]; +} + +-(void)setV1:(double)v { + native[1] = v; +} + +-(instancetype)init { + return [self initWithV0:0 v1:0]; +} + +-(instancetype)initWithV0:(double)v0 v1:(double)v1 { + self = [super init]; + if (self) { + self.v0 = v0; + self.v1 = v1; + } + return self; +} + +-(instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + ++(instancetype)fromNative:(cv::Vec2d&)vec2d { + return [[Double2 alloc] initWithV0:vec2d[0] v1:vec2d[1]]; +} + +-(void)set:(NSArray*)vals { + self.v0 = (vals != nil && vals.count > 0) ? vals[0].doubleValue : 0; + self.v1 = (vals != nil && vals.count > 1) ? vals[1].doubleValue : 0; +} + +-(NSArray*)get { + return @[[NSNumber numberWithFloat:native[0]], [NSNumber numberWithFloat:native[1]]]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Double2 class]]) { + return NO; + } else { + Double2* d2 = (Double2*)other; + return self.v0 == d2.v0 && self.v1 == d2.v1; + } +} + +@end diff --git a/modules/core/misc/objc/common/Double3.h b/modules/core/misc/objc/common/Double3.h new file mode 100644 index 0000000000..dc361bbc25 --- /dev/null +++ b/modules/core/misc/objc/common/Double3.h @@ -0,0 +1,94 @@ +// +// Double3.h +// +// Created by Giles Payne on 2020/05/22. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +@class Mat; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Simple wrapper for a vector of three `double` +*/ +@interface Double3 : NSObject + +#pragma mark - Properties + +/** +* First vector element +*/ +@property double v0; + +/** +* Second vector element +*/ +@property double v1; + +/** +* Third vector element +*/ +@property double v2; + + +#ifdef __cplusplus +/** +* The wrapped vector +*/ +@property(readonly) cv::Vec3d& nativeRef; +#endif + +#pragma mark - Constructors + +/** +* Create zero-initialize vecior +*/ +-(instancetype)init; + +/** +* Create vector with specified element values +* @param v0 First element +* @param v1 Second element +* @param v2 Third element +*/ +-(instancetype)initWithV0:(double)v0 v1:(double)v1 v2:(double)v2; + +/** +* Create vector with specified element values +* @param vals array of element values +*/ +-(instancetype)initWithVals:(NSArray*)vals; +#ifdef __cplusplus ++(instancetype)fromNative:(cv::Vec3d&)vec3d; +#endif + +/** +* Update vector with specified element values +* @param vals array of element values +*/ +-(void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +/** +* Get vector as an array +*/ +-(NSArray*)get; + +#pragma mark - Common Methods + +/** +* Compare for equality +* @param other Object to compare +*/ +-(BOOL)isEqual:(nullable id)other; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Double3.mm b/modules/core/misc/objc/common/Double3.mm new file mode 100644 index 0000000000..b4c3cf0a20 --- /dev/null +++ b/modules/core/misc/objc/common/Double3.mm @@ -0,0 +1,85 @@ +// +// Double3.mm +// +// Created by Giles Payne on 2020/05/22. +// + +#import "Double3.h" +#import "Mat.h" + +@implementation Double3 { + cv::Vec3d native; +} + +-(double)v0 { + return native[0]; +} + +-(void)setV0:(double)v { + native[0] = v; +} + +-(double)v1 { + return native[1]; +} + +-(void)setV1:(double)v { + native[1] = v; +} + +-(double)v2 { + return native[2]; +} + +-(void)setV2:(double)v { + native[2] = v; +} + +-(instancetype)init { + return [self initWithV0:0 v1:0 v2:0]; +} + +-(instancetype)initWithV0:(double)v0 v1:(double)v1 v2:(double)v2 { + self = [super init]; + if (self) { + self.v0 = v0; + self.v1 = v1; + self.v2 = v2; + } + return self; +} + +-(instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + ++(instancetype)fromNative:(cv::Vec3d&)vec3d { + return [[Double3 alloc] initWithV0:vec3d[0] v1:vec3d[1] v2:vec3d[2]]; +} + +-(void)set:(NSArray*)vals { + self.v0 = (vals != nil && vals.count > 0) ? vals[0].doubleValue : 0; + self.v1 = (vals != nil && vals.count > 1) ? vals[1].doubleValue : 0; + self.v2 = (vals != nil && vals.count > 2) ? vals[2].doubleValue : 0; +} + +-(NSArray*)get { + return @[[NSNumber numberWithFloat:native[0]], [NSNumber numberWithFloat:native[1]], [NSNumber numberWithFloat:native[2]]]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Double3 class]]) { + return NO; + } else { + Double3* d3 = (Double3*)other; + return self.v0 == d3.v0 && self.v1 == d3.v1 && self.v2 == d3.v2; + } +} + +@end diff --git a/modules/core/misc/objc/common/DoubleVector.h b/modules/core/misc/objc/common/DoubleVector.h new file mode 100644 index 0000000000..6f338199c9 --- /dev/null +++ b/modules/core/misc/objc/common/DoubleVector.h @@ -0,0 +1,88 @@ +// +// DoubleVector.h +// +// Created by Giles Payne on 2020/01/04. +// + +#pragma once + +#import +#ifdef __cplusplus +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** +* Utility class to wrap a `std::vector` +*/ +@interface DoubleVector : NSObject + +#pragma mark - Constructors + +/** +* Create DoubleVector and initialize with the contents of an NSData object +* @param data NSData containing raw double array +*/ +-(instancetype)initWithData:(NSData*)data; + +/** +* Create DoubleVector and initialize with the contents of another DoubleVector object +* @param src DoubleVector containing data to copy +*/ +-(instancetype)initWithVector:(DoubleVector*)src; + +#ifdef __OBJC__ +/** +* Create DoubleVector from raw C array +* @param array The raw C array +* @elements elements The number of elements in the array +*/ +-(instancetype)initWithNativeArray:(double*)array elements:(int)elements; +#endif + +#ifdef __cplusplus +/** +* Create DoubleVector from std::vector +* @param src The std::vector object to wrap +*/ +-(instancetype)initWithStdVector:(std::vector&)src; ++(instancetype)fromNative:(std::vector&)src; +#endif + +#pragma mark - Properties + +/** +* Length of the vector +*/ +@property(readonly) size_t length; + +#ifdef __OBJC__ +/** +* Raw C array +*/ +@property(readonly) double* nativeArray; +#endif + +#ifdef __cplusplus +/** +* The wrapped std::vector object +*/ +@property(readonly) std::vector& nativeRef; +#endif + +/** +* NSData object containing the raw double data +*/ +@property(readonly) NSData* data; + +#pragma mark - Accessor method + +/** +* Return array element +* @param index Index of the array element to return +*/ +-(double)get:(NSInteger)index; + +@end +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/DoubleVector.mm b/modules/core/misc/objc/common/DoubleVector.mm new file mode 100644 index 0000000000..861e08d449 --- /dev/null +++ b/modules/core/misc/objc/common/DoubleVector.mm @@ -0,0 +1,76 @@ +// +// DoubleVector.m +// +// Created by Giles Payne on 2020/01/04. +// + +#import "DoubleVector.h" +#import + +@implementation DoubleVector { + std::vector v; +} + +-(instancetype)initWithData:(NSData*)data { + self = [super init]; + if (self) { + if (data.length % sizeof(double) != 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Invalid data length" userInfo:nil]; + } + v.insert(v.begin(), (double*)data.bytes, (double*)data.bytes + data.length/sizeof(double)); + } + return self; +} + +-(instancetype)initWithVector:(DoubleVector*)src { + self = [super init]; + if (self) { + v.insert(v.begin(), src.nativeRef.begin(), src.nativeRef.end()); + } + return self; +} + +-(size_t)length { + return v.size(); +} + +-(double*)nativeArray { + return (double*)v.data(); +} + +-(instancetype)initWithNativeArray:(double*)array elements:(int)elements { + self = [super init]; + if (self) { + v.insert(v.begin(), array, array + elements); + } + return self; +} + +- (std::vector&)nativeRef { + return v; +} + +-(instancetype)initWithStdVector:(std::vector&)src { + self = [super init]; + if (self) { + v.insert(v.begin(), src.begin(), src.end()); + } + return self; +} + ++(instancetype)fromNative:(std::vector&)src { + return [[DoubleVector alloc] initWithStdVector:src]; +} + +-(double)get:(NSInteger)index { + if (index < 0 || index >= (long)v.size()) { + @throw [NSException exceptionWithName:NSRangeException reason:@"Invalid data length" userInfo:nil]; + } + return v[index]; +} + +-(NSData*)data { + return [NSData dataWithBytesNoCopy:v.data() length:(v.size() * sizeof(double)) freeWhenDone:NO]; +} + +@end diff --git a/modules/core/misc/objc/common/DoubleVectorExt.swift b/modules/core/misc/objc/common/DoubleVectorExt.swift new file mode 100644 index 0000000000..61fad46a29 --- /dev/null +++ b/modules/core/misc/objc/common/DoubleVectorExt.swift @@ -0,0 +1,53 @@ +// +// DoubleVectorExt.swift +// +// Created by Giles Payne on 2020/01/04. +// + +import Foundation + +public extension DoubleVector { + convenience init(_ array:[Double]) { + let data = array.withUnsafeBufferPointer { Data(buffer: $0) } + self.init(data:data); + } + + subscript(index: Int) -> Double { + get { + return self.get(index) + } + } + + var array: [Double] { + get { + var ret = Array(repeating: 0, count: data.count/MemoryLayout.stride) + _ = ret.withUnsafeMutableBytes { data.copyBytes(to: $0) } + return ret + } + } +} + +extension DoubleVector : Sequence { + public typealias Iterator = DoubleVectorIterator + public func makeIterator() -> DoubleVectorIterator { + return DoubleVectorIterator(self) + } +} + +public struct DoubleVectorIterator: IteratorProtocol { + public typealias Element = Double + let doubleVector: DoubleVector + var pos = 0 + + init(_ doubleVector: DoubleVector) { + self.doubleVector = doubleVector + } + + mutating public func next() -> Double? { + guard pos >= 0 && pos < doubleVector.length + else { return nil } + + pos += 1 + return doubleVector.get(pos - 1) + } +} diff --git a/modules/core/misc/objc/common/Float4.h b/modules/core/misc/objc/common/Float4.h new file mode 100644 index 0000000000..d0c52dccfe --- /dev/null +++ b/modules/core/misc/objc/common/Float4.h @@ -0,0 +1,99 @@ +// +// Float4.h +// +// Created by Giles Payne on 2020/02/05. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class Mat; + +/** +* Simple wrapper for a vector of four `float` +*/ +@interface Float4 : NSObject + +#pragma mark - Properties + +/** +* First vector element +*/ +@property float v0; + +/** +* Second vector element +*/ +@property float v1; + +/** +* Third vector element +*/ +@property float v2; + +/** +* Fourth vector element +*/ +@property float v3; + +#ifdef __cplusplus +/** +* The wrapped vector +*/ +@property(readonly) cv::Vec4f& nativeRef; +#endif + +#pragma mark - Constructors + +/** +* Create zero-initialize vecior +*/ +-(instancetype)init; + +/** +* Create vector with specified element values +* @param v0 First element +* @param v1 Second element +* @param v2 Third element +* @param v3 Fourth element +*/ +-(instancetype)initWithV0:(float)v0 v1:(float)v1 v2:(float)v2 v3:(float)v3; + +/** +* Create vector with specified element values +* @param vals array of element values +*/ +-(instancetype)initWithVals:(NSArray*)vals; +#ifdef __cplusplus ++(instancetype)fromNative:(cv::Vec4f&)vec4f; +#endif + +/** +* Update vector with specified element values +* @param vals array of element values +*/ +-(void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +/** +* Get vector as an array +*/ +-(NSArray*)get; + +#pragma mark - Common Methods + +/** +* Compare for equality +* @param other Object to compare +*/ +-(BOOL)isEqual:(nullable id)other; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Float4.mm b/modules/core/misc/objc/common/Float4.mm new file mode 100644 index 0000000000..9b3f1c4a86 --- /dev/null +++ b/modules/core/misc/objc/common/Float4.mm @@ -0,0 +1,95 @@ +// +// Float4.mm +// +// Created by Giles Payne on 2020/02/05. +// + +#import "Float4.h" +#import "Mat.h" + +@implementation Float4 { + cv::Vec4f native; +} + +-(float)v0 { + return native[0]; +} + +-(void)setV0:(float)v { + native[0] = v; +} + +-(float)v1 { + return native[1]; +} + +-(void)setV1:(float)v { + native[1] = v; +} + +-(float)v2 { + return native[2]; +} + +-(void)setV2:(float)v { + native[2] = v; +} + +-(float)v3 { + return native[3]; +} + +-(void)setV3:(float)v { + native[3] = v; +} + +-(instancetype)init { + return [self initWithV0:0.0 v1:0.0 v2:0.0 v3:0.0]; +} + +-(instancetype)initWithV0:(float)v0 v1:(float)v1 v2:(float)v2 v3:(float)v3 { + self = [super init]; + if (self) { + self.v0 = v0; + self.v1 = v1; + self.v2 = v2; + self.v3 = v3; + } + return self; +} + +-(instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + ++(instancetype)fromNative:(cv::Vec4f&)vec4f { + return [[Float4 alloc] initWithV0:vec4f[0] v1:vec4f[1] v2:vec4f[2] v3:vec4f[3]]; +} + +-(void)set:(NSArray*)vals { + self.v0 = (vals != nil && vals.count > 0) ? vals[0].floatValue : 0; + self.v1 = (vals != nil && vals.count > 1) ? vals[1].floatValue : 0; + self.v2 = (vals != nil && vals.count > 2) ? vals[2].floatValue : 0; + self.v3 = (vals != nil && vals.count > 3) ? vals[3].floatValue : 0; +} + +-(NSArray*)get { + return @[[NSNumber numberWithFloat:native[0]], [NSNumber numberWithFloat:native[1]], [NSNumber numberWithFloat:native[2]], [NSNumber numberWithFloat:native[3]]]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Float4 class]]) { + return NO; + } else { + Float4* point = (Float4*)other; + return self.v0 == point.v0 && self.v1 == point.v1 && self.v2 == point.v2 && self.v3 == point.v3; + } +} + +@end diff --git a/modules/core/misc/objc/common/Float6.h b/modules/core/misc/objc/common/Float6.h new file mode 100644 index 0000000000..c1c0ddd510 --- /dev/null +++ b/modules/core/misc/objc/common/Float6.h @@ -0,0 +1,112 @@ +// +// Float6.h +// +// Created by Giles Payne on 2020/02/05. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +@class Mat; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Simple wrapper for a vector of six `float` +*/ +@interface Float6 : NSObject + +#pragma mark - Properties + +/** +* First vector element +*/ +@property float v0; + +/** +* Second vector element +*/ +@property float v1; + +/** +* Third vector element +*/ +@property float v2; + +/** +* Fourth vector element +*/ +@property float v3; + +/** +* Fifth vector element +*/ +@property float v4; + +/** +* Sixth vector element +*/ +@property float v5; + +#ifdef __cplusplus +/** +* The wrapped vector +*/ +@property(readonly) cv::Vec6f& nativeRef; +#endif + +#pragma mark - Constructors + +/** +* Create zero-initialize vecior +*/ +-(instancetype)init; + +/** +* Create vector with specified element values +* @param v0 First element +* @param v1 Second element +* @param v2 Third element +* @param v3 Fourth element +* @param v4 Fifth element +* @param v5 Sixth element +*/ +-(instancetype)initWithV0:(float)v0 v1:(float)v1 v2:(float)v2 v3:(float)v3 v4:(float)v4 v5:(float)v5; + +/** +* Create vector with specified element values +* @param vals array of element values +*/ +-(instancetype)initWithVals:(NSArray*)vals; +#ifdef __cplusplus ++(instancetype)fromNative:(cv::Vec6f&)vec6f; +#endif + +/** +* Update vector with specified element values +* @param vals array of element values +*/ +-(void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +/** +* Get vector as an array +*/ +-(NSArray*)get; + + +#pragma mark - Common Methods + +/** +* Compare for equality +* @param other Object to compare +*/ +-(BOOL)isEqual:(nullable id)other; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Float6.mm b/modules/core/misc/objc/common/Float6.mm new file mode 100644 index 0000000000..3d9ff803f4 --- /dev/null +++ b/modules/core/misc/objc/common/Float6.mm @@ -0,0 +1,115 @@ +// +// Float6.mm +// +// Created by Giles Payne on 2020/02/05. +// + +#import "Float6.h" +#import "Mat.h" + +@implementation Float6 { + cv::Vec6f native; +} + +-(float)v0 { + return native[0]; +} + +-(void)setV0:(float)v { + native[0] = v; +} + +-(float)v1 { + return native[1]; +} + +-(void)setV1:(float)v { + native[1] = v; +} + +-(float)v2 { + return native[2]; +} + +-(void)setV2:(float)v { + native[2] = v; +} + +-(float)v3 { + return native[3]; +} + +-(void)setV3:(float)v { + native[3] = v; +} + +-(float)v4 { + return native[4]; +} + +-(void)setV4:(float)v { + native[4] = v; +} + +-(float)v5 { + return native[5]; +} + +-(void)setV5:(float)v { + native[5] = v; +} + +-(instancetype)init { + return [self initWithV0:0.0 v1:0.0 v2:0.0 v3:0.0 v4:0.0 v5:0.0]; +} + +-(instancetype)initWithV0:(float)v0 v1:(float)v1 v2:(float)v2 v3:(float)v3 v4:(float)v4 v5:(float)v5 { + self = [super init]; + if (self) { + self.v0 = v0; + self.v1 = v1; + self.v2 = v2; + self.v3 = v3; + self.v4 = v4; + self.v5 = v5; + } + return self; +} + +-(instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + ++(instancetype)fromNative:(cv::Vec6f&)vec6f { + return [[Float6 alloc] initWithV0:vec6f[0] v1:vec6f[1] v2:vec6f[2] v3:vec6f[3] v4:vec6f[4] v5:vec6f[5]]; +} + +-(void)set:(NSArray*)vals { + self.v0 = (vals != nil && vals.count > 0) ? vals[0].floatValue : 0.0; + self.v1 = (vals != nil && vals.count > 1) ? vals[1].floatValue : 0.0; + self.v2 = (vals != nil && vals.count > 2) ? vals[2].floatValue : 0.0; + self.v3 = (vals != nil && vals.count > 3) ? vals[3].floatValue : 0.0; + self.v4 = (vals != nil && vals.count > 4) ? vals[4].floatValue : 0.0; + self.v5 = (vals != nil && vals.count > 5) ? vals[5].floatValue : 0.0; +} + +-(NSArray*)get { + return @[[NSNumber numberWithFloat:native[0]], [NSNumber numberWithFloat:native[1]], [NSNumber numberWithFloat:native[2]], [NSNumber numberWithFloat:native[3]]]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Float6 class]]) { + return NO; + } else { + Float6* point = (Float6*)other; + return self.v0 == point.v0 && self.v1 == point.v1 && self.v2 == point.v2 && self.v3 == point.v3 && self.v4 == point.v4 && self.v5 == point.v5; + } +} + +@end diff --git a/modules/core/misc/objc/common/FloatVector.h b/modules/core/misc/objc/common/FloatVector.h new file mode 100644 index 0000000000..9aa25927c7 --- /dev/null +++ b/modules/core/misc/objc/common/FloatVector.h @@ -0,0 +1,88 @@ +// +// FloatVector.h +// +// Created by Giles Payne on 2020/01/04. +// + +#pragma once + +#import +#ifdef __cplusplus +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** +* Utility class to wrap a `std::vector` +*/ +@interface FloatVector : NSObject + +#pragma mark - Constructors + +/** +* Create FloatVector and initialize with the contents of an NSData object +* @param data NSData containing raw float array +*/ +-(instancetype)initWithData:(NSData*)data; + +/** +* Create FloatVector and initialize with the contents of another FloatVector object +* @param src FloatVector containing data to copy +*/ +-(instancetype)initWithVector:(FloatVector*)src; + +#ifdef __OBJC__ +/** +* Create FloatVector from raw C array +* @param array The raw C array +* @elements elements The number of elements in the array +*/ +-(instancetype)initWithNativeArray:(float*)array elements:(NSInteger)elements; +#endif + +#ifdef __cplusplus +/** +* Create FloatVector from std::vector +* @param src The std::vector object to wrap +*/ +-(instancetype)initWithStdVector:(std::vector&)src; ++(instancetype)fromNative:(std::vector&)src; +#endif + +#pragma mark - Properties + +/** +* Length of the vector +*/ +@property(readonly) NSInteger length; + +#ifdef __OBJC__ +/** +* Raw C array +*/ +@property(readonly) float* nativeArray; +#endif + +#ifdef __cplusplus +/** +* The wrapped std::vector object +*/ +@property(readonly) std::vector& nativeRef; +#endif + +/** +* NSData object containing the raw float data +*/ +@property(readonly) NSData* data; + +#pragma mark - Accessor method + +/** +* Return array element +* @param index Index of the array element to return +*/ +-(float)get:(NSInteger)index; + +@end +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/FloatVector.mm b/modules/core/misc/objc/common/FloatVector.mm new file mode 100644 index 0000000000..626c465b9f --- /dev/null +++ b/modules/core/misc/objc/common/FloatVector.mm @@ -0,0 +1,76 @@ +// +// FloatVector.m +// +// Created by Giles Payne on 2020/01/04. +// + +#import "FloatVector.h" +#import + +@implementation FloatVector { + std::vector v; +} + +-(instancetype)initWithData:(NSData*)data { + self = [super init]; + if (self) { + if (data.length % sizeof(float) != 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Invalid data length" userInfo:nil]; + } + v.insert(v.begin(), (float*)data.bytes, (float*)data.bytes + data.length/sizeof(float)); + } + return self; +} + +-(instancetype)initWithVector:(FloatVector *)src { + self = [super init]; + if (self) { + v.insert(v.begin(), src.nativeRef.begin(), src.nativeRef.end()); + } + return self; +} + +-(NSInteger)length { + return v.size(); +} + +-(float*)nativeArray { + return (float*)v.data(); +} + +-(instancetype)initWithNativeArray:(float*)array elements:(NSInteger)elements { + self = [super init]; + if (self) { + v.insert(v.begin(), array, array + elements); + } + return self; +} + +- (std::vector&)nativeRef { + return v; +} + +-(instancetype)initWithStdVector:(std::vector&)src { + self = [super init]; + if (self) { + v.insert(v.begin(), src.begin(), src.end()); + } + return self; +} + ++(instancetype)fromNative:(std::vector&)src { + return [[FloatVector alloc] initWithStdVector:src]; +} + +-(float)get:(NSInteger)index { + if (index < 0 || index >= (long)v.size()) { + @throw [NSException exceptionWithName:NSRangeException reason:@"Invalid data length" userInfo:nil]; + } + return v[index]; +} + +-(NSData*)data { + return [NSData dataWithBytesNoCopy:v.data() length:(v.size() * sizeof(float)) freeWhenDone:NO]; +} + +@end diff --git a/modules/core/misc/objc/common/FloatVectorExt.swift b/modules/core/misc/objc/common/FloatVectorExt.swift new file mode 100644 index 0000000000..9dbe8a58eb --- /dev/null +++ b/modules/core/misc/objc/common/FloatVectorExt.swift @@ -0,0 +1,53 @@ +// +// FloatVectorExt.swift +// +// Created by Giles Payne on 2020/01/04. +// + +import Foundation + +public extension FloatVector { + convenience init(_ array:[Float]) { + let data = array.withUnsafeBufferPointer { Data(buffer: $0) } + self.init(data:data); + } + + subscript(index: Int) -> Float { + get { + return self.get(index) + } + } + + var array: [Float] { + get { + var ret = Array(repeating: 0, count: data.count/MemoryLayout.stride) + _ = ret.withUnsafeMutableBytes { data.copyBytes(to: $0) } + return ret + } + } +} + +extension FloatVector : Sequence { + public typealias Iterator = FloatVectorIterator + public func makeIterator() -> FloatVectorIterator { + return FloatVectorIterator(self) + } +} + +public struct FloatVectorIterator: IteratorProtocol { + public typealias Element = Float + let floatVector: FloatVector + var pos = 0 + + init(_ floatVector: FloatVector) { + self.floatVector = floatVector + } + + mutating public func next() -> Float? { + guard pos >= 0 && pos < floatVector.length + else { return nil } + + pos += 1 + return floatVector.get(pos - 1) + } +} diff --git a/modules/core/misc/objc/common/Int4.h b/modules/core/misc/objc/common/Int4.h new file mode 100644 index 0000000000..583feef0ad --- /dev/null +++ b/modules/core/misc/objc/common/Int4.h @@ -0,0 +1,99 @@ +// +// Int4.h +// +// Created by Giles Payne on 2020/02/05. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +@class Mat; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Simple wrapper for a vector of four `int` +*/ +@interface Int4 : NSObject + +#pragma mark - Properties + +/** +* First vector element +*/ +@property int v0; + +/** +* Second vector element +*/ +@property int v1; + +/** +* Third vector element +*/ +@property int v2; + +/** +* Fourth vector element +*/ +@property int v3; + +#ifdef __cplusplus +/** +* The wrapped vector +*/ +@property(readonly) cv::Vec4i& nativeRef; +#endif + +#pragma mark - Constructors + +/** +* Create zero-initialize vecior +*/ +-(instancetype)init; + +/** +* Create vector with specified element values +* @param v0 First element +* @param v1 Second element +* @param v2 Third element +* @param v3 Fourth element +*/ +-(instancetype)initWithV0:(int)v0 v1:(int)v1 v2:(int)v2 v3:(int)v3; + +/** +* Create vector with specified element values +* @param vals array of element values +*/ +-(instancetype)initWithVals:(NSArray*)vals; +#ifdef __cplusplus ++(instancetype)fromNative:(cv::Vec4i&)vec4i; +#endif + +/** +* Update vector with specified element values +* @param vals array of element values +*/ +-(void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +/** +* Get vector as an array +*/ +-(NSArray*)get; + +#pragma mark - Common Methods + +/** +* Compare for equality +* @param other Object to compare +*/ +-(BOOL)isEqual:(nullable id)other; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Int4.mm b/modules/core/misc/objc/common/Int4.mm new file mode 100644 index 0000000000..d3a2a3a4c9 --- /dev/null +++ b/modules/core/misc/objc/common/Int4.mm @@ -0,0 +1,95 @@ +// +// Int4.mm +// +// Created by Giles Payne on 2020/02/05. +// + +#import "Int4.h" +#import "Mat.h" + +@implementation Int4 { + cv::Vec4i native; +} + +-(int)v0 { + return native[0]; +} + +-(void)setV0:(int)v { + native[0] = v; +} + +-(int)v1 { + return native[1]; +} + +-(void)setV1:(int)v { + native[1] = v; +} + +-(int)v2 { + return native[2]; +} + +-(void)setV2:(int)v { + native[2] = v; +} + +-(int)v3 { + return native[3]; +} + +-(void)setV3:(int)v { + native[3] = v; +} + +-(instancetype)init { + return [self initWithV0:0 v1:0 v2:0 v3:0]; +} + +-(instancetype)initWithV0:(int)v0 v1:(int)v1 v2:(int)v2 v3:(int)v3 { + self = [super init]; + if (self) { + self.v0 = v0; + self.v1 = v1; + self.v2 = v2; + self.v3 = v3; + } + return self; +} + +-(instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + ++(instancetype)fromNative:(cv::Vec4i&)vec4i { + return [[Int4 alloc] initWithV0:vec4i[0] v1:vec4i[1] v2:vec4i[2] v3:vec4i[3]]; +} + +-(void)set:(NSArray*)vals { + self.v0 = (vals != nil && vals.count > 0) ? vals[0].intValue : 0; + self.v1 = (vals != nil && vals.count > 1) ? vals[1].intValue : 0; + self.v2 = (vals != nil && vals.count > 2) ? vals[2].intValue : 0; + self.v3 = (vals != nil && vals.count > 3) ? vals[3].intValue : 0; +} + +-(NSArray*)get { + return @[[NSNumber numberWithFloat:native[0]], [NSNumber numberWithFloat:native[1]], [NSNumber numberWithFloat:native[2]], [NSNumber numberWithFloat:native[3]]]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Int4 class]]) { + return NO; + } else { + Int4* point = (Int4*)other; + return self.v0 == point.v0 && self.v1 == point.v1 && self.v2 == point.v2 && self.v3 == point.v3; + } +} + +@end diff --git a/modules/core/misc/objc/common/IntVector.h b/modules/core/misc/objc/common/IntVector.h new file mode 100644 index 0000000000..e8c4acc45f --- /dev/null +++ b/modules/core/misc/objc/common/IntVector.h @@ -0,0 +1,88 @@ +// +// IntVector.h +// +// Created by Giles Payne on 2020/01/04. +// + +#pragma once + +#import +#ifdef __cplusplus +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** +* Utility class to wrap a `std::vector` +*/ +@interface IntVector : NSObject + +#pragma mark - Constructors + +/** +* Create IntVector and initialize with the contents of an NSData object +* @param data NSData containing raw int array +*/ +-(instancetype)initWithData:(NSData*)data; + +/** +* Create IntVector and initialize with the contents of another IntVector object +* @param src IntVector containing data to copy +*/ +-(instancetype)initWithVector:(IntVector*)src; + +#ifdef __OBJC__ +/** +* Create IntVector from raw C array +* @param array The raw C array +* @elements elements The number of elements in the array +*/ +-(instancetype)initWithNativeArray:(int*)array elements:(NSInteger)elements; +#endif + +#ifdef __cplusplus +/** +* Create IntVector from std::vector +* @param src The std::vector object to wrap +*/ +-(instancetype)initWithStdVector:(std::vector&)src; ++(instancetype)fromNative:(std::vector&)src; +#endif + +#pragma mark - Properties + +/** +* Length of the vector +*/ +@property(readonly) NSInteger length; + +#ifdef __OBJC__ +/** +* Raw C array +*/ +@property(readonly) int* nativeArray; +#endif + +#ifdef __cplusplus +/** +* The wrapped std::vector object +*/ +@property(readonly) std::vector& nativeRef; +#endif + +/** +* NSData object containing the raw int data +*/ +@property(readonly) NSData* data; + +#pragma mark - Accessor method + +/** +* Return array element +* @param index Index of the array element to return +*/ +-(int)get:(NSInteger)index; + +@end +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/IntVector.mm b/modules/core/misc/objc/common/IntVector.mm new file mode 100644 index 0000000000..112a5dcbc1 --- /dev/null +++ b/modules/core/misc/objc/common/IntVector.mm @@ -0,0 +1,76 @@ +// +// IntVector.m +// +// Created by Giles Payne on 2020/01/04. +// + +#import "IntVector.h" +#import + +@implementation IntVector { + std::vector v; +} + +-(instancetype)initWithData:(NSData*)data { + self = [super init]; + if (self) { + if (data.length % sizeof(int) != 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Invalid data length" userInfo:nil]; + } + v.insert(v.begin(), (int*)data.bytes, (int*)data.bytes + data.length/sizeof(int)); + } + return self; +} + +-(instancetype)initWithVector:(IntVector*)src { + self = [super init]; + if (self) { + v.insert(v.begin(), src.nativeRef.begin(), src.nativeRef.end()); + } + return self; +} + +-(NSInteger)length { + return v.size(); +} + +-(int*)nativeArray { + return (int*)v.data(); +} + +-(instancetype)initWithNativeArray:(int*)array elements:(NSInteger)elements { + self = [super init]; + if (self) { + v.insert(v.begin(), array, array + elements); + } + return self; +} + +- (std::vector&)nativeRef { + return v; +} + +-(instancetype)initWithStdVector:(std::vector&)src { + self = [super init]; + if (self) { + v.insert(v.begin(), src.begin(), src.end()); + } + return self; +} + ++(instancetype)fromNative:(std::vector&)src { + return [[IntVector alloc] initWithStdVector:src]; +} + +-(int)get:(NSInteger)index { + if (index < 0 || index >= (long)v.size()) { + @throw [NSException exceptionWithName:NSRangeException reason:@"Invalid data length" userInfo:nil]; + } + return v[index]; +} + +-(NSData*)data { + return [NSData dataWithBytesNoCopy:v.data() length:(v.size() * sizeof(int)) freeWhenDone:NO]; +} + +@end diff --git a/modules/core/misc/objc/common/IntVectorExt.swift b/modules/core/misc/objc/common/IntVectorExt.swift new file mode 100644 index 0000000000..4d5c53886a --- /dev/null +++ b/modules/core/misc/objc/common/IntVectorExt.swift @@ -0,0 +1,53 @@ +// +// IntVectorExt.swift +// +// Created by Giles Payne on 2020/01/04. +// + +import Foundation + +public extension IntVector { + convenience init(_ array:[Int32]) { + let data = array.withUnsafeBufferPointer { Data(buffer: $0) } + self.init(data:data); + } + + subscript(index: Int) -> Int32 { + get { + return self.get(index) + } + } + + var array: [Int32] { + get { + var ret = Array(repeating: 0, count: data.count/MemoryLayout.stride) + _ = ret.withUnsafeMutableBytes { data.copyBytes(to: $0) } + return ret + } + } +} + +extension IntVector : Sequence { + public typealias Iterator = IntVectorIterator + public func makeIterator() -> IntVectorIterator { + return IntVectorIterator(self) + } +} + +public struct IntVectorIterator: IteratorProtocol { + public typealias Element = Int32 + let intVector: IntVector + var pos = 0 + + init(_ intVector: IntVector) { + self.intVector = intVector + } + + mutating public func next() -> Int32? { + guard pos >= 0 && pos < intVector.length + else { return nil } + + pos += 1 + return intVector.get(pos - 1) + } +} diff --git a/modules/core/misc/objc/common/KeyPoint.h b/modules/core/misc/objc/common/KeyPoint.h new file mode 100644 index 0000000000..680aafa72e --- /dev/null +++ b/modules/core/misc/objc/common/KeyPoint.h @@ -0,0 +1,98 @@ +// +// KeyPoint.h +// +// Created by Giles Payne on 2019/10/08. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +@class Point2f; + +NS_ASSUME_NONNULL_BEGIN +/** +* Object representing a point feature found by one of many available keypoint detectors, such as Harris corner detector, FAST, StarDetector, SURF, SIFT etc. +*/ +@interface KeyPoint : NSObject + +#pragma mark - Properties + +/** +* Coordinates of the keypoint. +*/ +@property Point2f* pt; + +/** +* Diameter of the useful keypoint adjacent area. +*/ +@property float size; + +/** +* Computed orientation of the keypoint (-1 if not applicable). +*/ +@property float angle; + +/** +* The response, by which the strongest keypoints have been selected. Can +* be used for further sorting or subsampling. +*/ +@property float response; + +/** +* Octave (pyramid layer), from which the keypoint has been extracted. +*/ +@property int octave; + +/** +* Object ID, that can be used to cluster keypoints by an object they +* belong to. +*/ +@property int classId; + +#ifdef __cplusplus +@property(readonly) cv::KeyPoint& nativeRef; +#endif + +#pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithX:(float)x y:(float)y size:(float)size angle:(float)angle response:(float)response octave:(int)octave classId:(int)classId; +- (instancetype)initWithX:(float)x y:(float)y size:(float)size angle:(float)angle response:(float)response octave:(int)octave; +- (instancetype)initWithX:(float)x y:(float)y size:(float)size angle:(float)angle response:(float)response; +- (instancetype)initWithX:(float)x y:(float)y size:(float)size angle:(float)angle; +- (instancetype)initWithX:(float)x y:(float)y size:(float)size; +#ifdef __cplusplus ++ (instancetype)fromNative:(cv::KeyPoint&)keyPoint; +#endif + +#pragma mark - Common Methods + +/** +* Clone object +*/ +- (KeyPoint*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)other; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString*)description; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/KeyPoint.mm b/modules/core/misc/objc/common/KeyPoint.mm new file mode 100644 index 0000000000..8117910516 --- /dev/null +++ b/modules/core/misc/objc/common/KeyPoint.mm @@ -0,0 +1,95 @@ +// +// KeyPoint.m +// +// Created by Giles Payne on 2019/12/25. +// + +#import "KeyPoint.h" +#import "Point2f.h" +#import "CVObjcUtil.h" + +@implementation KeyPoint { + cv::KeyPoint native; +} + +- (cv::KeyPoint&)nativeRef { + native.pt.x = self.pt.x; + native.pt.y = self.pt.y; + native.size = self.size; + native.angle = self.angle; + native.response = self.response; + native.octave = self.octave; + native.class_id = self.classId; + return native; +} + +- (instancetype)init { + return [self initWithX:0 y:0 size:0]; +} + +- (instancetype)initWithX:(float)x y:(float)y size:(float)size angle:(float)angle response:(float)response octave:(int)octave classId:(int)classId { + self = [super init]; + if (self != nil) { + self.pt = [[Point2f alloc] initWithX:x y:y]; + self.size = size; + self.angle = angle; + self.response = response; + self.octave = octave; + self.classId = classId; + } + return self; +} + +- (instancetype)initWithX:(float)x y:(float)y size:(float)size angle:(float)angle response:(float)response octave:(int)octave { + return [self initWithX:x y:y size:size angle:angle response:response octave:octave classId:-1]; +} + +- (instancetype)initWithX:(float)x y:(float)y size:(float)size angle:(float)angle response:(float)response { + return [self initWithX:x y:y size:size angle:angle response:response octave:0]; +} + +- (instancetype)initWithX:(float)x y:(float)y size:(float)size angle:(float)angle { + return [self initWithX:x y:y size:size angle:angle response:0]; +} + +- (instancetype)initWithX:(float)x y:(float)y size:(float)size { + return [self initWithX:x y:y size:size angle:-1]; +} + ++ (instancetype)fromNative:(cv::KeyPoint&)keyPoint { + return [[KeyPoint alloc] initWithX:keyPoint.pt.x y:keyPoint.pt.y size:keyPoint.size angle:keyPoint.angle response:keyPoint.response octave:keyPoint.octave classId:keyPoint.class_id]; +} + +- (KeyPoint*)clone { + return [[KeyPoint alloc] initWithX:self.pt.x y:self.pt.y size:self.size angle:self.angle response:self.response octave:self.octave classId:self.classId]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[KeyPoint class]]) { + return NO; + } else { + KeyPoint* keyPoint = (KeyPoint*)other; + return [self.pt isEqual:keyPoint.pt] && self.size == keyPoint.size && self.angle == keyPoint.angle && self.response == keyPoint.response && self.octave == keyPoint.octave && self.classId == keyPoint.classId; + } +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + result = prime * result + FLOAT_TO_BITS(self.pt.x); + result = prime * result + FLOAT_TO_BITS(self.pt.y); + result = prime * result + FLOAT_TO_BITS(self.size); + result = prime * result + FLOAT_TO_BITS(self.angle); + result = prime * result + FLOAT_TO_BITS(self.response); + result = prime * result + self.octave; + result = prime * result + self.classId; + return result; +} + +- (NSString*)description { + return [NSString stringWithFormat:@"KeyPoint { pt: %@, size: %f, angle: %f, response: %f, octave: %d, classId: %d}", self.pt.description, self.size, self.angle, self.response, self.octave, self.classId]; +} + +@end diff --git a/modules/core/misc/objc/common/Mat.h b/modules/core/misc/objc/common/Mat.h new file mode 100644 index 0000000000..e1b249e87d --- /dev/null +++ b/modules/core/misc/objc/common/Mat.h @@ -0,0 +1,157 @@ +// +// Mat.h +// +// Created by Giles Payne on 2019/10/06. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +@class Size2i; +@class Scalar; +@class Range; +@class Rect2i; +@class Point2i; + +NS_ASSUME_NONNULL_BEGIN + +/** +* The class Mat represents an n-dimensional dense numerical single-channel or multi-channel array. +*/ +@interface Mat : NSObject + +#ifdef __cplusplus +@property(readonly) cv::Mat* nativePtr; +@property(readonly) cv::Mat& nativeRef; +#endif + +#pragma mark - Constructors + +- (instancetype)init; +- (void)dealloc; +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; ++ (instancetype)fromNativePtr:(cv::Mat*)nativePtr; ++ (instancetype)fromNative:(cv::Mat&)nativeRef; +#endif +- (instancetype)initWithRows:(int)rows cols:(int)cols type:(int)type; +- (instancetype)initWithRows:(int)rows cols:(int)cols type:(int)type data:(NSData*)data; +- (instancetype)initWithRows:(int)rows cols:(int)cols type:(int)type data:(NSData*)data step:(long)step; +- (instancetype)initWithSize:(Size2i*)size type:(int)type; +- (instancetype)initWithSizes:(NSArray*)sizes type:(int)type; +- (instancetype)initWithRows:(int)rows cols:(int)cols type:(int)type scalar:(Scalar*)scalar; +- (instancetype)initWithSize:(Size2i*)size type:(int)type scalar:(Scalar*)scalar; +- (instancetype)initWithSizes:(NSArray*)sizes type:(int)type scalar:(Scalar*)scalar; +- (instancetype)initWithMat:(Mat*)mat rowRange:(Range*)rowRange colRange:(Range*)colRange; +- (instancetype)initWithMat:(Mat*)mat rowRange:(Range*)rowRange; +- (instancetype)initWithMat:(Mat*)mat ranges:(NSArray*)ranges; +- (instancetype)initWithMat:(Mat*)mat rect:(Rect2i*)roi; + +#pragma mark - Mat operations + +- (Mat*)adjustRoiTop:(int)dtop bottom:(int)dbottom left:(int)dleft right:(int)dright NS_SWIFT_NAME(adjustRoi(top:bottom:left:right:)); +- (void)assignTo:(Mat*)mat type:(int)type; +- (void)assignTo:(Mat*)mat; +- (BOOL)isSameMat:(Mat*)mat; +- (int)channels; +- (int)checkVector:(int)elemChannels depth:(int)depth requireContinuous:(BOOL) requireContinuous NS_SWIFT_NAME(checkVector(elemChannels:depth:requireContinuous:)); +- (int)checkVector:(int)elemChannels depth:(int)depth NS_SWIFT_NAME(checkVector(elemChannels:depth:)); +- (int)checkVector:(int)elemChannels NS_SWIFT_NAME(checkVector(elemChannels:)); +- (Mat*)clone; +- (Mat*)col:(int)x; +- (Mat*)colRange:(int)start end:(int)end NS_SWIFT_NAME(colRange(start:end:)); +- (Mat*)colRange:(Range*)range; +- (int)dims; +- (int)cols; +- (void)convertTo:(Mat*)mat rtype:(int)rtype alpha:(double)alpha beta:(double)beta; +- (void)convertTo:(Mat*)mat rtype:(int)rtype alpha:(double)alpha; +- (void)convertTo:(Mat*)mat rtype:(int)rtype; +- (void)copyTo:(Mat*)mat; +- (void)copyTo:(Mat*)mat mask:(Mat*)mask; +- (void)create:(int)rows cols:(int)cols type:(int)type NS_SWIFT_NAME(create(rows:cols:type:)); +- (void)create:(Size2i*)size type:(int)type NS_SWIFT_NAME(create(size:type:)); +- (void)createEx:(NSArray*)sizes type:(int)type NS_SWIFT_NAME(create(sizes:type:)); +- (void)copySize:(Mat*)mat; +- (Mat*)cross:(Mat*)mat; +- (int)depth; +- (Mat*)diag:(int)diagonal; +- (Mat*)diag; ++ (Mat*)diag:(Mat*)diagonal; +- (double)dot:(Mat*)mat; +- (long)elemSize; +- (long)elemSize1; +- (BOOL)empty; ++ (Mat*)eye:(int)rows cols:(int)cols type:(int)type NS_SWIFT_NAME(eye(rows:cols:type:)); ++ (Mat*)eye:(Size2i*)size type:(int)type NS_SWIFT_NAME(eye(size:type:)); +- (Mat*)inv:(int)method; +- (Mat*)inv; +- (BOOL)isContinuous; +- (BOOL)isSubmatrix; +- (void)locateROI:(Size2i*)wholeSize ofs:(Point2i*)offset NS_SWIFT_NAME(locateROI(wholeSize:offset:)); +- (Mat*)mul:(Mat*)mat scale:(double)scale; +- (Mat*)mul:(Mat*)mat; ++ (Mat*)ones:(int)rows cols:(int)cols type:(int)type NS_SWIFT_NAME(ones(rows:cols:type:)); ++ (Mat*)ones:(Size2i*)size type:(int)type NS_SWIFT_NAME(ones(size:type:)); ++ (Mat*)onesEx:(NSArray*)sizes type:(int)type NS_SWIFT_NAME(ones(sizes:type:)); +- (void)push_back:(Mat*)mat; +- (Mat*)reshape:(int)channels rows:(int)rows NS_SWIFT_NAME(reshape(channels:rows:)); +- (Mat*)reshape:(int)channels NS_SWIFT_NAME(reshape(channels:)); +- (Mat*)reshape:(int)channels newshape:(NSArray*)newshape NS_SWIFT_NAME(reshape(channels:newshape:)); +- (Mat*)row:(int)y; +- (Mat*)rowRange:(int)start end:(int)end NS_SWIFT_NAME(rowRange(start:end:)); +- (Mat*)rowRange:(Range*)range; +- (int)rows; +- (Mat*)setToScalar:(Scalar*)scalar NS_SWIFT_NAME(setTo(scalar:)); +- (Mat*)setToScalar:(Scalar*)scalar mask:(Mat*)mask NS_SWIFT_NAME(setTo(scalar:mask:)); +- (Mat*)setToValue:(Mat*)value mask:(Mat*)mask NS_SWIFT_NAME(setTo(value:mask:)); +- (Mat*)setToValue:(Mat*)value NS_SWIFT_NAME(setTo(value:)); +- (Size2i*)size; +- (int)size:(int)dim; +- (long)step1:(int)dim; +- (long)step1; +- (Mat*)submat:(int)rowStart rowEnd:(int)rowEnd colStart:(int)colStart colEnd:(int)colEnd NS_SWIFT_NAME(submat(rowStart:rowEnd:colStart:colEnd:)); +- (Mat*)submat:(Range*)rowRange colRange:(Range*)colRange NS_SWIFT_NAME(submat(rowRange:colRange:)); +- (Mat*)submat:(NSArray*)ranges NS_SWIFT_NAME(submat(ranges:)); +- (Mat*)submatRoi:(Rect2i*)roi NS_SWIFT_NAME(submat(roi:)); +- (Mat*)t; +- (long)total; +- (int)type; ++ (Mat*)zeros:(int)rows cols:(int)cols type:(int)type; ++ (Mat*)zeros:(Size2i*)size type:(int)type; ++ (Mat*)zerosEx:(NSArray*)sizes type:(int)type NS_SWIFT_NAME(zeros(sizes:type:)); +- (NSString*)description; +- (NSString*)dump; +- (int)height; +- (int)width; + +#pragma mark - Accessors + +- (int)put:(int)row col:(int)col data:(NSArray*)data NS_REFINED_FOR_SWIFT; +- (int)put:(NSArray*)indices data:(NSArray*)data NS_REFINED_FOR_SWIFT; +- (int)get:(int)row col:(int)col data:(NSMutableArray*)data NS_REFINED_FOR_SWIFT; +- (int)get:(NSArray*)indices data:(NSMutableArray*)data NS_REFINED_FOR_SWIFT; + +- (NSArray*)get:(int)row col:(int)col NS_REFINED_FOR_SWIFT; +- (NSArray*)get:(NSArray*)indices NS_REFINED_FOR_SWIFT; + +- (int)get:(NSArray*)indices count:(int)count byteBuffer:(char*)buffer NS_REFINED_FOR_SWIFT; +- (int)get:(NSArray*)indices count:(int)count doubleBuffer:(double*)buffer NS_REFINED_FOR_SWIFT; +- (int)get:(NSArray*)indices count:(int)count floatBuffer:(float*)buffer NS_REFINED_FOR_SWIFT; +- (int)get:(NSArray*)indices count:(int)count intBuffer:(int*)buffer NS_REFINED_FOR_SWIFT; +- (int)get:(NSArray*)indices count:(int)count shortBuffer:(short*)buffer NS_REFINED_FOR_SWIFT; + +- (int)put:(NSArray*)indices count:(int)count byteBuffer:(const char*)buffer NS_REFINED_FOR_SWIFT; +- (int)put:(NSArray*)indices count:(int)count doubleBuffer:(const double*)buffer NS_REFINED_FOR_SWIFT; +- (int)put:(NSArray*)indices count:(int)count floatBuffer:(const float*)buffer NS_REFINED_FOR_SWIFT; +- (int)put:(NSArray*)indices count:(int)count intBuffer:(const int*)buffer NS_REFINED_FOR_SWIFT; +- (int)put:(NSArray*)indices count:(int)count shortBuffer:(const short*)buffer NS_REFINED_FOR_SWIFT; + + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Mat.mm b/modules/core/misc/objc/common/Mat.mm new file mode 100644 index 0000000000..c75b7d8a94 --- /dev/null +++ b/modules/core/misc/objc/common/Mat.mm @@ -0,0 +1,913 @@ +// +// Mat.m +// +// Created by Giles Payne on 2019/10/06. +// + +#import "Mat.h" +#import "Size2i.h" +#import "Scalar.h" +#import "Range.h" +#import "Rect2i.h" +#import "Point2i.h" +#import "CvType.h" +#import "CVObjcUtil.h" + +// return true if we have reached the final index +static bool incIdx(cv::Mat* mat, std::vector& indices) { + for (int dim = mat->dims-1; dim>=0; dim--) { + indices[dim] = (indices[dim] + 1) % mat->size[dim]; + if (indices[dim] != 0) { + return false; + } + } + return true; +} + +// returns true if final index was reached +static bool updateIdx(cv::Mat* mat, std::vector& indices, int inc) { + for (int index = 0; index < inc; index++) { + if (incIdx(mat, indices)) { + return true; + } + } + return false; +} + +@implementation Mat { + NSData* _nsdata; +} + +- (cv::Mat&)nativeRef { + return *(cv::Mat*)_nativePtr; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _nativePtr = new cv::Mat(); + } + return self; +} + +- (void)dealloc { + if (_nativePtr != NULL) { + _nativePtr->release(); + delete _nativePtr; + } + _nsdata = NULL; +} + +- (instancetype)initWithNativeMat:(cv::Mat*)nativePtr { + self = [super init]; + if (self) { + _nativePtr = new cv::Mat(*nativePtr); + } + return self; +} + ++ (instancetype)fromNativePtr:(cv::Mat*)nativePtr { + return [[Mat alloc] initWithNativeMat:nativePtr]; +} + ++ (instancetype)fromNative:(cv::Mat&)nativeRef { + return [[Mat alloc] initWithNativeMat:&nativeRef]; +} + +- (instancetype)initWithRows:(int)rows cols:(int)cols type:(int)type { + self = [super init]; + if (self) { + _nativePtr = new cv::Mat(rows, cols, type); + } + return self; +} + +- (instancetype)initWithRows:(int)rows cols:(int)cols type:(int)type data:(NSData*)data { + self = [super init]; + if (self) { + _nativePtr = new cv::Mat(rows, cols, type, (void*)data.bytes); + _nsdata = data; // hold onto a reference otherwise this object might be deallocated + } + return self; +} + +- (instancetype)initWithRows:(int)rows cols:(int)cols type:(int)type data:(NSData*)data step:(long)step { + self = [super init]; + if (self) { + _nativePtr = new cv::Mat(rows, cols, type, (void*)data.bytes, step); + _nsdata = data; // hold onto a reference otherwise this object might be deallocated + } + return self; +} + +- (instancetype)initWithSize:(Size2i*)size type:(int)type { + self = [super init]; + if (self) { + _nativePtr = new cv::Mat(size.width, size.height, type); + } + return self; +} + +- (instancetype)initWithSizes:(NSArray*)sizes type:(int)type { + self = [super init]; + if (self) { + std::vector vSizes; + for (NSNumber* size in sizes) { + vSizes.push_back(size.intValue); + } + _nativePtr = new cv::Mat((int)sizes.count, vSizes.data(), type); + } + return self; +} + +- (instancetype)initWithRows:(int)rows cols:(int)cols type:(int)type scalar:(Scalar*)scalar { + self = [super init]; + if (self) { + cv::Scalar scalerTemp(scalar.val[0].doubleValue, scalar.val[1].doubleValue, scalar.val[2].doubleValue, scalar.val[3].doubleValue); + _nativePtr = new cv::Mat(rows, cols, type, scalerTemp); + } + return self; +} + +- (instancetype)initWithSize:(Size2i*)size type:(int)type scalar:(Scalar *)scalar { + self = [super init]; + if (self) { + cv::Scalar scalerTemp(scalar.val[0].doubleValue, scalar.val[1].doubleValue, scalar.val[2].doubleValue, scalar.val[3].doubleValue); + _nativePtr = new cv::Mat(size.width, size.height, type, scalerTemp); + } + return self; +} + +- (instancetype)initWithSizes:(NSArray*)sizes type:(int)type scalar:(Scalar *)scalar { + self = [super init]; + if (self) { + cv::Scalar scalerTemp(scalar.val[0].doubleValue, scalar.val[1].doubleValue, scalar.val[2].doubleValue, scalar.val[3].doubleValue); + std::vector vSizes; + for (NSNumber* size in sizes) { + vSizes.push_back(size.intValue); + } + _nativePtr = new cv::Mat((int)sizes.count, vSizes.data(), type, scalerTemp); + } + return self; +} + +- (instancetype)initWithMat:(Mat*)mat rowRange:(Range*)rowRange colRange:(Range*)colRange { + self = [super init]; + if (self) { + cv::Range rows(rowRange.start, rowRange.end); + cv::Range cols(colRange.start, colRange.end); + _nativePtr = new cv::Mat(*(cv::Mat*)mat.nativePtr, rows, cols); + } + return self; +} + +- (instancetype)initWithMat:(Mat*)mat rowRange:(Range*)rowRange { + self = [super init]; + if (self) { + cv::Range rows(rowRange.start, rowRange.end); + _nativePtr = new cv::Mat(*(cv::Mat*)mat.nativePtr, rows); + } + return self; +} + +- (instancetype)initWithMat:(Mat*)mat ranges:(NSArray*)ranges { + self = [super init]; + if (self) { + std::vector tempRanges; + for (Range* range in ranges) { + tempRanges.push_back(cv::Range(range.start, range.end)); + } + _nativePtr = new cv::Mat(mat.nativePtr->operator()(tempRanges)); + } + return self; +} + +- (instancetype)initWithMat:(Mat*)mat rect:(Rect2i*)roi { + self = [super init]; + if (self) { + cv::Range rows(roi.y, roi.y + roi.height); + cv::Range cols(roi.x, roi.x + roi.width); + _nativePtr = new cv::Mat(*(cv::Mat*)mat.nativePtr, rows, cols); + } + return self; +} + +- (BOOL)isSameMat:(Mat*)mat { + return self.nativePtr == mat.nativePtr; +} + +- (Mat*)adjustRoiTop:(int)dtop bottom:(int)dbottom left:(int)dleft right:(int)dright { + cv::Mat adjusted = _nativePtr->adjustROI(dtop, dbottom, dleft, dright); + return [[Mat alloc] initWithNativeMat:new cv::Mat(adjusted)]; +} + +- (void)assignTo:(Mat*)mat type:(int)type { + _nativePtr->assignTo(*(cv::Mat*)mat.nativePtr, type); +} + +- (void)assignTo:(Mat*)mat { + _nativePtr->assignTo(*(cv::Mat*)mat.nativePtr); +} + +- (int)channels { + return _nativePtr->channels(); +} + +- (int)checkVector:(int)elemChannels depth:(int)depth requireContinuous:(BOOL) requireContinuous { + return _nativePtr->checkVector(elemChannels, depth, requireContinuous); +} + +- (int)checkVector:(int)elemChannels depth:(int)depth { + return _nativePtr->checkVector(elemChannels, depth); +} + +- (int)checkVector:(int)elemChannels { + return _nativePtr->checkVector(elemChannels); +} + +- (Mat*)clone { + return [[Mat alloc] initWithNativeMat:(new cv::Mat(_nativePtr->clone()))]; +} + +- (Mat*)col:(int)x { + return [[Mat alloc] initWithNativeMat:(new cv::Mat(_nativePtr->col(x)))]; +} + +- (Mat*)colRange:(int)start end:(int)end { + return [[Mat alloc] initWithNativeMat:(new cv::Mat(_nativePtr->colRange(start, end)))]; +} + +- (Mat*)colRange:(Range*)range { + return [[Mat alloc] initWithNativeMat:(new cv::Mat(_nativePtr->colRange(range.start, range.end)))]; +} + +- (int)dims { + return _nativePtr->dims; +} + +- (int)cols { + return _nativePtr->cols; +} + +- (void)convertTo:(Mat*)mat rtype:(int)rtype alpha:(double)alpha beta:(double)beta { + _nativePtr->convertTo(*(cv::Mat*)mat->_nativePtr, rtype, alpha, beta); +} + +- (void)convertTo:(Mat*)mat rtype:(int)rtype alpha:(double)alpha { + _nativePtr->convertTo(*(cv::Mat*)mat->_nativePtr, rtype, alpha); +} + +- (void)convertTo:(Mat*)mat rtype:(int)rtype { + _nativePtr->convertTo(*(cv::Mat*)mat->_nativePtr, rtype); +} + +- (void)copyTo:(Mat*)mat { + _nativePtr->copyTo(*(cv::Mat*)mat->_nativePtr); +} + +- (void)copyTo:(Mat*)mat mask:(Mat*)mask { + _nativePtr->copyTo(*(cv::Mat*)mat->_nativePtr, *(cv::Mat*)mask->_nativePtr); +} + +- (void)create:(int)rows cols:(int)cols type:(int)type { + _nativePtr->create(rows, cols, type); +} + +- (void)create:(Size2i*)size type:(int)type { + cv::Size tempSize(size.width, size.height); + _nativePtr->create(tempSize, type); +} + +- (void)createEx:(NSArray*)sizes type:(int)type { + std::vector tempSizes; + for (NSNumber* size in sizes) { + tempSizes.push_back(size.intValue); + } + _nativePtr->create((int)tempSizes.size(), tempSizes.data(), type); +} + +- (void)copySize:(Mat*)mat { + _nativePtr->copySize(*(cv::Mat*)mat.nativePtr); +} + +- (Mat*)cross:(Mat*)mat { + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->cross(*(cv::Mat*)mat.nativePtr))]; +} + +- (int)depth { + return _nativePtr->depth(); +} + +- (Mat*)diag:(int)diagonal { + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->diag(diagonal))]; +} + +- (Mat*)diag { + return [self diag:0]; +} + ++ (Mat*)diag:(Mat*)diagonal { + return [[Mat alloc] initWithNativeMat:new cv::Mat(cv::Mat::diag(*(cv::Mat*)diagonal.nativePtr))]; +} + +- (double)dot:(Mat*)mat { + return _nativePtr->dot(*(cv::Mat*)mat.nativePtr); +} + +- (long)elemSize { + return _nativePtr->elemSize(); +} + +- (long)elemSize1 { + return _nativePtr->elemSize1(); +} + +- (BOOL)empty { + return _nativePtr->empty(); +} + ++ (Mat*)eye:(int)rows cols:(int)cols type:(int)type { + return [[Mat alloc] initWithNativeMat:new cv::Mat(cv::Mat::eye(rows, cols, type))]; +} + ++ (Mat*)eye:(Size2i*)size type:(int)type { + cv::Size tempSize(size.width, size.height); + return [[Mat alloc] initWithNativeMat:new cv::Mat(cv::Mat::eye(tempSize, type))]; +} + +- (Mat*)inv:(int)method { + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->inv(method))]; +} + +- (Mat*)inv { + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->inv())]; +} + +- (BOOL)isContinuous { + return _nativePtr->isContinuous(); +} + +- (BOOL)isSubmatrix { + return _nativePtr->isSubmatrix(); +} + +- (void)locateROI:(Size2i*)wholeSize ofs:(Point2i*)ofs { + cv::Size tempWholeSize; + cv::Point tempOfs; + _nativePtr->locateROI(tempWholeSize, tempOfs); + if (wholeSize != nil) { + wholeSize.width = tempWholeSize.width; + wholeSize.height = tempWholeSize.height; + } + if (ofs != nil) { + ofs.x = tempOfs.x; + ofs.y = tempOfs.y; + } +} + +- (Mat*)mul:(Mat*)mat scale:(double)scale { + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->mul(*(cv::Mat*)mat.nativePtr, scale))]; +} + +- (Mat*)mul:(Mat*)mat { + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->mul(*(cv::Mat*)mat.nativePtr))]; +} + ++ (Mat*)ones:(int)rows cols:(int)cols type:(int)type { + return [[Mat alloc] initWithNativeMat:new cv::Mat(cv::Mat::ones(rows, cols, type))]; +} + ++ (Mat*)ones:(Size2i*)size type:(int)type { + cv::Size tempSize(size.width, size.height); + return [[Mat alloc] initWithNativeMat:new cv::Mat(cv::Mat::ones(tempSize, type))]; +} + ++ (Mat*)onesEx:(NSArray*)sizes type:(int)type { + std::vector tempSizes; + for (NSNumber* size in sizes) { + tempSizes.push_back(size.intValue); + } + return [[Mat alloc] initWithNativeMat:new cv::Mat(cv::Mat::ones((int)tempSizes.size(), tempSizes.data(), type))]; +} + +- (void)push_back:(Mat*)mat { + _nativePtr->push_back(*(cv::Mat*)mat.nativePtr); +} + +- (Mat*)reshape:(int)channels rows:(int)rows { + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->reshape(channels, rows))]; +} + +- (Mat*)reshape:(int)channels { + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->reshape(channels))]; +} + +- (Mat*)reshape:(int)channels newshape:(NSArray*)newshape { + std::vector tempNewshape; + for (NSNumber* size in newshape) { + tempNewshape.push_back(size.intValue); + } + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->reshape(channels, tempNewshape))]; +} + +- (Mat*)row:(int)y { + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->row(y))]; +} + +- (Mat*)rowRange:(int)start end:(int)end { + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->rowRange(start, end))]; +} + +- (Mat*)rowRange:(Range*)range { + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->rowRange(range.start, range.end))]; +} + +- (int)rows { + return _nativePtr->rows; +} + +- (Mat*)setToScalar:(Scalar*)scalar { + cv::Scalar tempScalar(scalar.val[0].doubleValue, scalar.val[1].doubleValue, scalar.val[2].doubleValue, scalar.val[3].doubleValue); + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->operator=(tempScalar))]; +} + +- (Mat*)setToScalar:(Scalar*)scalar mask:(Mat*)mask { + cv::Scalar tempScalar(scalar.val[0].doubleValue, scalar.val[1].doubleValue, scalar.val[2].doubleValue, scalar.val[3].doubleValue); + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->setTo(tempScalar, *(cv::Mat*)mask.nativePtr))]; +} + +- (Mat*)setToValue:(Mat*)value mask:(Mat*)mask { + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->setTo(*(cv::Mat*)value.nativePtr, *(cv::Mat*)mask.nativePtr))]; +} + +- (Mat*)setToValue:(Mat*)value { + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->setTo(*(cv::Mat*)value.nativePtr))]; +} + +- (Size2i*)size { + return [[Size2i alloc] initWithWidth:_nativePtr->size().width height:_nativePtr->size().height]; +} + +- (int)size:(int)dimIndex { + return _nativePtr->size[dimIndex]; +} + +- (long)step1:(int)dimIndex { + return _nativePtr->step1(dimIndex); +} + +- (long)step1 { + return _nativePtr->step1(); +} + +- (Mat*)submat:(int)rowStart rowEnd:(int)rowEnd colStart:(int)colStart colEnd:(int)colEnd { + Range* rowRange = [[Range alloc] initWithStart:rowStart end:rowEnd]; + Range* colRange = [[Range alloc] initWithStart:colStart end:colEnd]; + return [self submat:rowRange colRange:colRange]; +} + +- (Mat*)submat:(Range*)rowRange colRange:(Range*)colRange { + cv::Range tempRowRange(rowRange.start, rowRange.end); + cv::Range tempColRange(colRange.start, colRange.end); + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->operator()(tempRowRange, tempColRange))]; +} + +- (Mat*)submat:(NSArray*)ranges { + std::vector tempRanges; + for (Range* range in ranges) { + tempRanges.push_back(cv::Range(range.start, range.end)); + } + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->operator()(tempRanges))]; +} + +- (Mat*)submatRoi:(Rect2i*)roi { + cv::Rect tempRoi(roi.x, roi.y, roi.width, roi.height); + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->operator()(tempRoi))]; +} + +- (Mat*)t { + return [[Mat alloc] initWithNativeMat:new cv::Mat(_nativePtr->t())]; +} + +- (long)total { + return _nativePtr->total(); +} + +- (int)type { + return _nativePtr->type(); +} + ++ (Mat*)zeros:(int)rows cols:(int)cols type:(int)type { + return [[Mat alloc] initWithNativeMat:new cv::Mat(cv::Mat::zeros(rows, cols, type))]; +} + ++ (Mat*)zeros:(Size2i*)size type:(int)type { + cv::Size tempSize(size.width, size.height); + return [[Mat alloc] initWithNativeMat:new cv::Mat(cv::Mat::zeros(tempSize, type))]; +} + ++ (Mat*)zerosEx:(NSArray*)sizes type:(int)type { + std::vector tempSizes; + for (NSNumber* size in sizes) { + tempSizes.push_back(size.intValue); + } + return [[Mat alloc] initWithNativeMat:new cv::Mat(cv::Mat::zeros((int)tempSizes.size(), tempSizes.data(), type))]; +} + +- (NSString*)dimsDescription { + if (_nativePtr->dims <= 0) { + return @"-1*-1*"; + } else { + NSMutableString* ret = [NSMutableString string]; + for (int index=0; index<_nativePtr->dims; index++) { + [ret appendFormat:@"%d*", _nativePtr->size[index]]; + } + return ret; + } +} + +- (NSString*)description { + NSString* dimDesc = [self dimsDescription]; + return [NSString stringWithFormat:@"Mat [ %@%@, isCont=%s, isSubmat=%s, nativeObj=0x%p, dataAddr=0x%p ]", dimDesc, [CvType typeToString:_nativePtr->type()], _nativePtr->isContinuous()?"YES":"NO", _nativePtr->isSubmatrix()?"YES":"NO", (void*)_nativePtr, (void*)_nativePtr->data]; +} + +- (NSString*)dump { + NSMutableString* ret = [NSMutableString string]; + cv::Ptr formatted = cv::Formatter::get()->format(*_nativePtr); + for(const char* format = formatted->next(); format; format = formatted->next()) { + [ret appendFormat:@"%s", format]; + } + return ret; +} + +template void putData(uchar* dataDest, int count, T (^readData)(int)) { + T* tDataDest = (T*)dataDest; + for (int index = 0; index < count; index++) { + tDataDest[index] = readData(index); + } +} + +- (void)put:(uchar*)dest data:(NSArray*)data offset:(int)offset count:(int)count { + int depth = _nativePtr->depth(); + if (depth == CV_8U) { + putData(dest, count, ^uchar (int index) { return cv::saturate_cast(data[offset + index].doubleValue);} ); + } else if (depth == CV_8S) { + putData(dest, count, ^char (int index) { return cv::saturate_cast(data[offset + index].doubleValue);} ); + } else if (depth == CV_16U) { + putData(dest, count, ^ushort (int index) { return cv::saturate_cast(data[offset + index].doubleValue);} ); + } else if (depth == CV_16S) { + putData(dest, count, ^short (int index) { return cv::saturate_cast(data[offset + index].doubleValue);} ); + } else if (depth == CV_32S) { + putData(dest, count, ^int32_t (int index) { return cv::saturate_cast(data[offset + index].doubleValue);} ); + } else if (depth == CV_32F) { + putData(dest, count, ^float (int index) { return cv::saturate_cast(data[offset + index].doubleValue);} ); + } else if (depth == CV_64F) { + putData(dest, count, ^double (int index) { return data[offset + index].doubleValue;} ); + } +} + +- (int)put:(NSArray*)indices data:(NSArray*)data { + cv::Mat* mat = _nativePtr; + int type = mat->type(); + int rawValueSize = (int)(mat->elemSize() / mat->channels()); + if (data == nil || data.count % [CvType channels:type] != 0) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Provided data element number (%lu) should be multiple of the Mat channels count (%d)", (unsigned long)(data == nil ? 0 : data.count), [CvType channels:type]] + userInfo:nil]; + @throw exception; + } + std::vector tempIndices; + for (NSNumber* index in indices) { + tempIndices.push_back(index.intValue); + } + for (int index = 0; index < mat->dims; index++) { + if (mat->size[index]<=tempIndices[index]) { + return 0; // indexes out of range + } + } + + int arrayAvailable = (int)data.count; + int matAvailable = getMatAvailable(mat, tempIndices); + int available = MIN(arrayAvailable, matAvailable); + int copyOffset = 0; + int copyCount = MIN((mat->size[mat->dims - 1] - tempIndices[mat->dims - 1]) * mat->channels(), available); + int result = (int)(available * rawValueSize); + + while (available > 0) { + [self put:mat->ptr(tempIndices.data()) data:data offset:(int)copyOffset count:copyCount]; + if (updateIdx(mat, tempIndices, copyCount / mat->channels())) { + break; + } + available -= copyCount; + copyOffset += copyCount; + copyCount = MIN(mat->size[mat->dims-1] * mat->channels(), available); + } + return result; +} + +- (int)put:(int)row col:(int)col data:(NSArray*)data { + NSArray* indices = @[[NSNumber numberWithInt:row], [NSNumber numberWithInt:col]]; + return [self put:indices data:data]; +} + +template void getData(uchar* dataSource, int count, void (^writeData)(int,T)) { + T* tDataSource = (T*)dataSource; + for (int index = 0; index < count; index++) { + writeData(index, tDataSource[index]); + } +} + +- (void)get:(uchar*)source data:(NSMutableArray*)data offset:(int)offset count:(int)count { + int depth = _nativePtr->depth(); + if (depth == CV_8U) { + getData(source, count, ^void (int index, uchar value) { data[offset + index] = [[NSNumber alloc] initWithUnsignedChar:value]; } ); + } else if (depth == CV_8S) { + getData(source, count, ^void (int index, char value) { data[offset + index] = [[NSNumber alloc] initWithChar:value]; } ); + } else if (depth == CV_16U) { + getData(source, count, ^void (int index, ushort value) { data[offset + index] = [[NSNumber alloc] initWithUnsignedShort:value]; } ); + } else if (depth == CV_16S) { + getData(source, count, ^void (int index, short value) { data[offset + index] = [[NSNumber alloc] initWithShort:value]; } ); + } else if (depth == CV_32S) { + getData(source, count, ^void (int index, int32_t value) { data[offset + index] = [[NSNumber alloc] initWithInt:value]; } ); + } else if (depth == CV_32F) { + getData(source, count, ^void (int index, float value) { data[offset + index] = [[NSNumber alloc] initWithFloat:value]; } ); + } else if (depth == CV_64F) { + getData(source, count, ^void (int index, double value) { data[offset + index] = [[NSNumber alloc] initWithDouble:value]; } ); + } +} + +- (int)get:(NSArray*)indices data:(NSMutableArray*)data { + cv::Mat* mat = _nativePtr; + int type = mat->type(); + if (data == nil || data.count % [CvType channels:type] != 0) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Provided data element number (%lu) should be multiple of the Mat channels count (%d)", (unsigned long)(data == nil ? 0 : data.count), [CvType channels:type]] + userInfo:nil]; + @throw exception; + } + std::vector tempIndices; + for (NSNumber* index in indices) { + tempIndices.push_back(index.intValue); + } + for (int index = 0; index < mat->dims; index++) { + if (mat->size[index]<=tempIndices[index]) { + return 0; // indexes out of range + } + } + + int arrayAvailable = (int)data.count; + int copyOffset = 0; + int matAvailable = getMatAvailable(mat, tempIndices); + int available = MIN(arrayAvailable, matAvailable); + int copyCount = MIN((mat->size[mat->dims - 1] - tempIndices[mat->dims - 1]) * mat->channels(), available); + int result = (int)(available * mat->elemSize() / mat->channels()); + + while (available > 0) { + [self get:mat->ptr(tempIndices.data()) data:data offset:(int)copyOffset count:copyCount]; + if (updateIdx(mat, tempIndices, copyCount / mat->channels())) { + break; + } + available -= copyCount; + copyOffset += copyCount; + copyCount = MIN(mat->size[mat->dims-1] * mat->channels(), available); + } + return result; +} + +- (int)get:(int)row col:(int)col data:(NSMutableArray*)data { + NSArray* indices = @[[NSNumber numberWithInt:row], [NSNumber numberWithInt:col]]; + return [self get:indices data:data]; +} + +- (NSArray*)get:(int)row col:(int)col { + NSMutableArray* result = [NSMutableArray new]; + for (int index = 0; index<_nativePtr->channels(); index++) { + [result addObject:@0]; + } + [self get:row col:col data:result]; + return result; +} + +- (NSArray*)get:(NSArray*)indices { + NSMutableArray* result = [NSMutableArray new]; + for (int index = 0; index<_nativePtr->channels(); index++) { + [result addObject:@0]; + } + [self get:indices data:result]; + return result; +} + +template void getData(uchar* source, void (^writeData)(int,T), int dataOffset, int dataLength) { + T* tSource = (T*)source; + for (int index = 0; index < dataLength; index++) { + writeData(dataOffset+index, tSource[index]); + } +} + +int getMatAvailable(cv::Mat* mat, std::vector& indices) { + int blockSize = 1; + int unavailableCount = 0; + for (int index = mat->dims - 1; index >= 0; index--) { + unavailableCount += blockSize * indices[index]; + blockSize *= mat->size[index]; + } + return (int)(mat->channels() * (mat->total() - unavailableCount)); +} + +template int getData(NSArray* indices, cv::Mat* mat, int count, T* tBuffer) { + std::vector tempIndices; + for (NSNumber* index in indices) { + tempIndices.push_back(index.intValue); + } + for (int index = 0; index < mat->dims; index++) { + if (mat->size[index]<=tempIndices[index]) { + return 0; // indexes out of range + } + } + + int arrayAvailable = count; + int matAvailable = getMatAvailable(mat, tempIndices); + int available = MIN(arrayAvailable, matAvailable); + int result = (int)(available * mat->elemSize() / mat->channels()); + if (mat->isContinuous()) { + memcpy(tBuffer, mat->ptr(tempIndices.data()), available * sizeof(T)); + } else { + int copyOffset = 0; + int copyCount = MIN((mat->size[mat->dims - 1] - tempIndices[mat->dims - 1]) * mat->channels(), available); + while (available > 0) { + memcpy(tBuffer + copyOffset, mat->ptr(tempIndices.data()), copyCount * sizeof(T)); + if (updateIdx(mat, tempIndices, copyCount / mat->channels())) { + break; + } + available -= copyCount; + copyOffset += copyCount * sizeof(T); + copyCount = MIN(mat->size[mat->dims-1] * mat->channels(), available); + } + } + return result; +} + +- (int)get:(NSArray*)indices count:(int)count byteBuffer:(char*)buffer { + int depth = _nativePtr->depth(); + if (depth != CV_8U && depth != CV_8S) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Invalid depth (%@). Valid depths for this call are CV_8U or CV_8S.", [CvType typeToString:depth]] + userInfo:nil]; + @throw exception; + } + return getData(indices, _nativePtr, count, buffer); +} + +- (int)get:(NSArray*)indices count:(int)count doubleBuffer:(double*)buffer { + int depth = _nativePtr->depth(); + if (depth != CV_64F) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Invalid depth (%@). Valid depth for this call is CV_64F.", [CvType typeToString:depth]] + userInfo:nil]; + @throw exception; + } + return getData(indices, _nativePtr, count, buffer); +} + +- (int)get:(NSArray*)indices count:(int)count floatBuffer:(float*)buffer { + int depth = _nativePtr->depth(); + if (depth != CV_32F) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Invalid depth (%@). Valid depth for this call is CV_32F.", [CvType typeToString:depth]] + userInfo:nil]; + @throw exception; + } + return getData(indices, _nativePtr, count, buffer); +} + +- (int)get:(NSArray*)indices count:(int)count intBuffer:(int*)buffer { + int depth = _nativePtr->depth(); + if (depth != CV_32S) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Invalid depth (%@). Valid depth for this call is CV_32S.", [CvType typeToString:depth]] + userInfo:nil]; + @throw exception; + } + return getData(indices, _nativePtr, count, buffer); +} + +- (int)get:(NSArray*)indices count:(int)count shortBuffer:(short*)buffer { + int depth = _nativePtr->depth(); + if (depth != CV_16S && depth != CV_16U) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Invalid depth (%@). Valid depths for this call are CV_16S and CV_16U.", [CvType typeToString:depth]] + userInfo:nil]; + @throw exception; + } + return getData(indices, _nativePtr, count, buffer); +} + +template int putData(NSArray* indices, cv::Mat* mat, int count, const T* tBuffer) { + std::vector tempIndices; + for (NSNumber* index in indices) { + tempIndices.push_back(index.intValue); + } + for (int index = 0; index < mat->dims; index++) { + if (mat->size[index]<=tempIndices[index]) { + return 0; // indexes out of range + } + } + + int arrayAvailable = count; + int matAvailable = getMatAvailable(mat, tempIndices); + int available = MIN(arrayAvailable, matAvailable); + int result = (int)(available * mat->elemSize() / mat->channels()); + if (mat->isContinuous()) { + memcpy(mat->ptr(tempIndices.data()), tBuffer, available * sizeof(T)); + } else { + int copyOffset = 0; + int copyCount = MIN((mat->size[mat->dims - 1] - tempIndices[mat->dims - 1]) * mat->channels(), available); + while (available > 0) { + memcpy(mat->ptr(tempIndices.data()), tBuffer + copyOffset, copyCount * sizeof(T)); + if (updateIdx(mat, tempIndices, copyCount / mat->channels())) { + break; + } + available -= copyCount; + copyOffset += copyCount * sizeof(T); + copyCount = MIN(mat->size[mat->dims-1] * (int)mat->channels(), available); + } + } + return result; +} + +- (int)put:(NSArray*)indices count:(int)count byteBuffer:(const char*)buffer { + int depth = _nativePtr->depth(); + if (depth != CV_8U && depth != CV_8S) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Invalid depth (%@). Valid depths for this call are CV_8U or CV_8S.", [CvType typeToString:depth]] + userInfo:nil]; + @throw exception; + } + return putData(indices, _nativePtr, count, buffer); +} + +- (int)put:(NSArray*)indices count:(int)count doubleBuffer:(const double*)buffer { + int depth = _nativePtr->depth(); + if (depth != CV_64F) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Invalid depth (%@). Valid depth for this call is CV_64F.", [CvType typeToString:depth]] + userInfo:nil]; + @throw exception; + } + return putData(indices, _nativePtr, count, buffer); +} + +- (int)put:(NSArray*)indices count:(int)count floatBuffer:(const float*)buffer { + int depth = _nativePtr->depth(); + if (depth != CV_32F) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Invalid depth (%@). Valid depth for this call is CV_32F.", [CvType typeToString:depth]] + userInfo:nil]; + @throw exception; + } + return putData(indices, _nativePtr, count, buffer); +} + +- (int)put:(NSArray*)indices count:(int)count intBuffer:(const int*)buffer { + int depth = _nativePtr->depth(); + if (depth != CV_32S) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Invalid depth (%@). Valid depth for this call is CV_32S.", [CvType typeToString:depth]] + userInfo:nil]; + @throw exception; + } + return putData(indices, _nativePtr, count, buffer); +} + +- (int)put:(NSArray*)indices count:(int)count shortBuffer:(const short*)buffer { + int depth = _nativePtr->depth(); + if (depth != CV_16S && depth != CV_16U) { + NSException* exception = [NSException + exceptionWithName:@"UnsupportedOperationException" + reason:[NSString stringWithFormat:@"Invalid depth (%@). Valid depths for this call are CV_16S and CV_16U.", [CvType typeToString:depth]] + userInfo:nil]; + @throw exception; + } + return putData(indices, _nativePtr, count, buffer); +} + +- (int)height { + return [self rows]; +} + +- (int)width { + return [self cols]; +} + +@end diff --git a/modules/core/misc/objc/common/MatExt.swift b/modules/core/misc/objc/common/MatExt.swift new file mode 100644 index 0000000000..f6b3072345 --- /dev/null +++ b/modules/core/misc/objc/common/MatExt.swift @@ -0,0 +1,244 @@ +// +// MatExt.swift +// +// Created by Giles Payne on 2020/01/19. +// + +import Foundation + +let OpenCVErrorDomain = "OpenCVErrorDomain" + +enum OpenCVError : Int { + case IncompatibleDataType = 10001 + case IncompatibleBufferSize +} + +func throwIncompatibleDataType(typeName: String) throws { + throw NSError( + domain: OpenCVErrorDomain, + code: OpenCVError.IncompatibleDataType.rawValue, + userInfo: [ + NSLocalizedDescriptionKey: "Incompatible Mat type \(typeName)" + ] + ) +} + +func throwIncompatibleBufferSize(count: Int, channels: Int32) throws { + throw NSError( + domain: OpenCVErrorDomain, + code: OpenCVError.IncompatibleBufferSize.rawValue, + userInfo: [ + NSLocalizedDescriptionKey: "Provided data element number \(count) should be multiple of the Mat channels count \(channels)" + ] + ) +} + +public extension Mat { + + convenience init(rows:Int32, cols:Int32, type:Int32, data:[Int8]) { + let dataObject = data.withUnsafeBufferPointer { Data(buffer: $0) } + self.init(rows: rows, cols: cols, type: type, data: dataObject) + } + + convenience init(rows:Int32, cols:Int32, type:Int32, data:[Int8], step:Int) { + let dataObject = data.withUnsafeBufferPointer { Data(buffer: $0) } + self.init(rows: rows, cols: cols, type: type, data: dataObject, step:step) + } + + @discardableResult func get(indices:[Int32], data:inout [Int8]) throws -> Int32 { + let channels = CvType.channels(Int32(type())) + if Int32(data.count) % channels != 0 { + try throwIncompatibleBufferSize(count: data.count, channels: channels) + } else if depth() != CvType.CV_8U && depth() != CvType.CV_8S { + try throwIncompatibleDataType(typeName: CvType.type(toString: type())) + } + let count = Int32(data.count) + return data.withUnsafeMutableBufferPointer { body in + return __get(indices as [NSNumber], count: count, byteBuffer: body.baseAddress!) + } + } + + @discardableResult func get(indices:[Int32], data:inout [Double]) throws -> Int32 { + let channels = CvType.channels(Int32(type())) + if Int32(data.count) % channels != 0 { + try throwIncompatibleBufferSize(count: data.count, channels: channels) + } else if depth() != CvType.CV_64F { + try throwIncompatibleDataType(typeName: CvType.type(toString: type())) + } + let count = Int32(data.count) + return data.withUnsafeMutableBufferPointer { body in + return __get(indices as [NSNumber], count: count, doubleBuffer: body.baseAddress!) + } + } + + @discardableResult func get(indices:[Int32], data:inout [Float]) throws -> Int32 { + let channels = CvType.channels(Int32(type())) + if Int32(data.count) % channels != 0 { + try throwIncompatibleBufferSize(count: data.count, channels: channels) + } else if depth() != CvType.CV_32F { + try throwIncompatibleDataType(typeName: CvType.type(toString: type())) + } + let count = Int32(data.count) + return data.withUnsafeMutableBufferPointer { body in + return __get(indices as [NSNumber], count: count, floatBuffer: body.baseAddress!) + } + } + + @discardableResult func get(indices:[Int32], data:inout [Int32]) throws -> Int32 { + let channels = CvType.channels(Int32(type())) + if Int32(data.count) % channels != 0 { + try throwIncompatibleBufferSize(count: data.count, channels: channels) + } else if depth() != CvType.CV_32S { + try throwIncompatibleDataType(typeName: CvType.type(toString: type())) + } + let count = Int32(data.count) + return data.withUnsafeMutableBufferPointer { body in + return __get(indices as [NSNumber], count: count, intBuffer: body.baseAddress!) + } + } + + @discardableResult func get(indices:[Int32], data:inout [Int16]) throws -> Int32 { + let channels = CvType.channels(Int32(type())) + if Int32(data.count) % channels != 0 { + try throwIncompatibleBufferSize(count: data.count, channels: channels) + } else if depth() != CvType.CV_16U && depth() != CvType.CV_16S { + try throwIncompatibleDataType(typeName: CvType.type(toString: type())) + } + let count = Int32(data.count) + return data.withUnsafeMutableBufferPointer { body in + return __get(indices as [NSNumber], count: count, shortBuffer: body.baseAddress!) + } + } + + @discardableResult func get(row: Int32, col: Int32, data:inout [Int8]) throws -> Int32 { + return try get(indices: [row, col], data: &data) + } + + @discardableResult func get(row: Int32, col: Int32, data:inout [Double]) throws -> Int32 { + return try get(indices: [row, col], data: &data) + } + + @discardableResult func get(row: Int32, col: Int32, data:inout [Float]) throws -> Int32 { + return try get(indices: [row, col], data: &data) + } + + @discardableResult func get(row: Int32, col: Int32, data:inout [Int32]) throws -> Int32 { + return try get(indices: [row, col], data: &data) + } + + @discardableResult func get(row: Int32, col: Int32, data:inout [Int16]) throws -> Int32 { + return try get(indices: [row, col], data: &data) + } + + @discardableResult func put(indices:[Int32], data:[Int8]) throws -> Int32 { + let channels = CvType.channels(Int32(type())) + if Int32(data.count) % channels != 0 { + try throwIncompatibleBufferSize(count: data.count, channels: channels) + } else if depth() != CvType.CV_8U && depth() != CvType.CV_8S { + try throwIncompatibleDataType(typeName: CvType.type(toString: type())) + } + let count = Int32(data.count) + return data.withUnsafeBufferPointer { body in + return __put(indices as [NSNumber], count: count, byteBuffer: body.baseAddress!) + } + } + + @discardableResult func put(indices:[Int32], data:[Int8], offset: Int, length: Int32) throws -> Int32 { + let channels = CvType.channels(Int32(type())) + if Int32(data.count) % channels != 0 { + try throwIncompatibleBufferSize(count: data.count, channels: channels) + } else if depth() != CvType.CV_8U && depth() != CvType.CV_8S { + try throwIncompatibleDataType(typeName: CvType.type(toString: type())) + } + return data.withUnsafeBufferPointer { body in + return __put(indices as [NSNumber], count: length, byteBuffer: body.baseAddress! + offset) + } + } + + // unlike other put:indices:data functions this one (with [Double]) should convert input values to correct type + @discardableResult func put(indices:[Int32], data:[Double]) throws -> Int32 { + let channels = CvType.channels(Int32(type())) + if Int32(data.count) % channels != 0 { + try throwIncompatibleBufferSize(count: data.count, channels: channels) + } + if depth() == CvType.CV_64F { + let count = Int32(data.count) + return data.withUnsafeBufferPointer { body in + return __put(indices as [NSNumber], count: count, doubleBuffer: body.baseAddress!) + } + } else { + return __put(indices as [NSNumber], data: data as [NSNumber]) + } + } + + @discardableResult func put(indices:[Int32], data:[Float]) throws -> Int32 { + let channels = CvType.channels(Int32(type())) + if Int32(data.count) % channels != 0 { + try throwIncompatibleBufferSize(count: data.count, channels: channels) + } else if depth() != CvType.CV_32F { + try throwIncompatibleDataType(typeName: CvType.type(toString: type())) + } + let count = Int32(data.count) + return data.withUnsafeBufferPointer { body in + return __put(indices as [NSNumber], count: count, floatBuffer: body.baseAddress!) + } + } + + @discardableResult func put(indices:[Int32], data:[Int32]) throws -> Int32 { + let channels = CvType.channels(Int32(type())) + if Int32(data.count) % channels != 0 { + try throwIncompatibleBufferSize(count: data.count, channels: channels) + } else if depth() != CvType.CV_32S { + try throwIncompatibleDataType(typeName: CvType.type(toString: type())) + } + let count = Int32(data.count) + return data.withUnsafeBufferPointer { body in + return __put(indices as [NSNumber], count: count, intBuffer: body.baseAddress!) + } + } + + @discardableResult func put(indices:[Int32], data:[Int16]) throws -> Int32 { + let channels = CvType.channels(Int32(type())) + if Int32(data.count) % channels != 0 { + try throwIncompatibleBufferSize(count: data.count, channels: channels) + } else if depth() != CvType.CV_16U && depth() != CvType.CV_16S { + try throwIncompatibleDataType(typeName: CvType.type(toString: type())) + } + let count = Int32(data.count) + return data.withUnsafeBufferPointer { body in + return __put(indices as [NSNumber], count: count, shortBuffer: body.baseAddress!) + } + } + + @discardableResult func put(row: Int32, col: Int32, data:[Int8]) throws -> Int32 { + return try put(indices: [row, col], data: data) + } + + @discardableResult func put(row: Int32, col: Int32, data: [Int8], offset: Int, length: Int32) throws -> Int32 { + return try put(indices: [row, col], data: data, offset: offset, length: length) + } + + @discardableResult func put(row: Int32, col: Int32, data: [Double]) throws -> Int32 { + return try put(indices: [row, col], data: data) + } + + @discardableResult func put(row: Int32, col: Int32, data: [Float]) throws -> Int32 { + return try put(indices: [row, col], data: data) + } + + @discardableResult func put(row: Int32, col: Int32, data: [Int32]) throws -> Int32 { + return try put(indices: [row, col], data: data) + } + + @discardableResult func put(row: Int32, col: Int32, data: [Int16]) throws -> Int32 { + return try put(indices: [row, col], data: data) + } + + @discardableResult func get(row: Int32, col: Int32) -> [Double] { + return get(indices: [row, col]) + } + + @discardableResult func get(indices: [Int32]) -> [Double] { + return __get(indices as [NSNumber]) as! [Double] + } +} diff --git a/modules/core/misc/objc/common/MatOfByte.h b/modules/core/misc/objc/common/MatOfByte.h new file mode 100644 index 0000000000..27d360ee36 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfByte.h @@ -0,0 +1,62 @@ +// +// MatOfByte.h +// +// Created by Giles Payne on 2019/12/26. +// + +#pragma once + +#import "Mat.h" + +NS_ASSUME_NONNULL_BEGIN + +/** +* Mat representation of an array of bytes +*/ +@interface MatOfByte : Mat + +#pragma mark - Constructors + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; +#endif + +/** +* Create MatOfByte from Mat object +* @param mat Mat object from which to create MatOfByte +*/ +- (instancetype)initWithMat:(Mat*)mat; + +/** +* Create MatOfByte from array +* @param array Array from which to create MatOfByte +*/ +- (instancetype)initWithArray:(NSArray*)array; + +#pragma mark - Methods + +/** +* Allocate specified number of elements +* @param elemNumber Number of elements +*/ +- (void)alloc:(int)elemNumber; + +/** +* Populate Mat with elements of an array +* @param array Array with which to populate the Mat +*/ +- (void)fromArray:(NSArray*)array; + +/** +* Output Mat elements as an array +*/ +- (NSArray*)toArray; + +/** +* Total number of values in Mat +*/ +- (int)length; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MatOfByte.mm b/modules/core/misc/objc/common/MatOfByte.mm new file mode 100644 index 0000000000..3ed03ae707 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfByte.mm @@ -0,0 +1,69 @@ +// +// MatOfByte.mm +// +// Created by Giles Payne on 2019/12/26. +// + +#import "MatOfByte.h" +#import "Range.h" +#import "CvType.h" +#import "ArrayUtil.h" + +@implementation MatOfByte + +static const int _depth = CV_8U; +static const int _channels = 1; + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat { + self = [super initWithNativeMat:nativeMat]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} +#endif + +- (instancetype)initWithMat:(Mat*)mat { + self = [super initWithMat:mat rowRange:[Range all]]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + +- (instancetype)initWithArray:(NSArray*)array { + self = [super init]; + if (self) { + [self fromArray:array]; + } + return self; +} + +- (void)alloc:(int)elemNumber { + if (elemNumber>0) { + [super create:elemNumber cols:1 type:[CvType makeType:_depth channels:_channels]]; + } +} + +- (void)fromArray:(NSArray*)array { + [self alloc:(int)array.count / _channels]; + [self put:0 col:0 data:array]; +} + +- (NSArray*)toArray { + int length = [self length]; + NSMutableArray* data = createArrayWithSize(length, @0.0); + [self get:0 col:0 data:data]; + return data; +} + +- (int)length { + int num = [self checkVector:_channels depth:_depth]; + if (num < 0) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Incompatible Mat" userInfo:nil]; + } + return num * _channels; +} + +@end diff --git a/modules/core/misc/objc/common/MatOfDMatch.h b/modules/core/misc/objc/common/MatOfDMatch.h new file mode 100644 index 0000000000..97507103ee --- /dev/null +++ b/modules/core/misc/objc/common/MatOfDMatch.h @@ -0,0 +1,64 @@ +// +// MatOfDMatch.h +// +// Created by Giles Payne on 2019/12/27. +// + +#pragma once + +#import "Mat.h" + +@class DMatch; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Mat representation of an array of DMatch objects +*/ +@interface MatOfDMatch : Mat + +#pragma mark - Constructors + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; +#endif + +/** +* Create MatOfDMatch from Mat object +* @param mat Mat object from which to create MatOfDMatch +*/ +- (instancetype)initWithMat:(Mat*)mat; + +/** +* Create MatOfDMatch from array +* @param array Array from which to create MatOfDMatch +*/ +- (instancetype)initWithArray:(NSArray*)array; + +#pragma mark - Methods + +/** +* Allocate specified number of elements +* @param elemNumber Number of elements +*/ +- (void)alloc:(int)elemNumber; + +/** +* Populate Mat with elements of an array +* @param array Array with which to populate the Mat +*/ +- (void)fromArray:(NSArray*)array; + +/** +* Output Mat elements as an array of DMatch objects +*/ +- (NSArray*)toArray; + +/** +* Total number of values in Mat +*/ +- (int)length; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MatOfDMatch.mm b/modules/core/misc/objc/common/MatOfDMatch.mm new file mode 100644 index 0000000000..0ba7ab9184 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfDMatch.mm @@ -0,0 +1,83 @@ +// +// MatOfDMatch.m +// +// Created by Giles Payne on 2019/12/27. +// + +#import "MatOfDMatch.h" +#import "Range.h" +#import "DMatch.h" +#import "CvType.h" +#import "ArrayUtil.h" + +@implementation MatOfDMatch + +static const int _depth = CV_32F; +static const int _channels = 4; + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat { + self = [super initWithNativeMat:nativeMat]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} +#endif + +- (instancetype)initWithMat:(Mat*)mat { + self = [super initWithMat:mat rowRange:[Range all]]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + +- (instancetype)initWithArray:(NSArray*)array { + self = [super init]; + if (self) { + [self fromArray:array]; + } + return self; +} + +- (void)alloc:(int)elemNumber { + if (elemNumber>0) { + [super create:elemNumber cols:1 type:[CvType makeType:_depth channels:_channels]]; + } +} + +- (void)fromArray:(NSArray*)array { + NSMutableArray* data = [[NSMutableArray alloc] initWithCapacity:array.count * _channels]; + for (int index = 0; index < (int)array.count; index++) { + data[_channels * index] = [NSNumber numberWithFloat:array[index].queryIdx]; + data[_channels * index + 1] = [NSNumber numberWithFloat:array[index].trainIdx]; + data[_channels * index + 2] = [NSNumber numberWithFloat:array[index].imgIdx]; + data[_channels * index + 3] = [NSNumber numberWithFloat:array[index].distance]; + } + [self alloc:(int)array.count]; + [self put:0 col:0 data:data]; +} + +- (NSArray*)toArray { + int length = [self length] / _channels; + NSMutableArray* ret = createArrayWithSize(length, [DMatch new]); + if (length > 0) { + NSMutableArray* data = createArrayWithSize([self length], @0.0); + [self get:0 col:0 data:data]; + for (int index = 0; index < length; index++) { + ret[index] = [[DMatch alloc] initWithQueryIdx:data[index * _channels].intValue trainIdx:data[index * _channels + 1].intValue imgIdx:data[index * _channels + 2].intValue distance:data[index * _channels + 3].floatValue]; + } + } + return ret; +} + +- (int)length { + int num = [self checkVector:_channels depth:_depth]; + if (num < 0) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Incompatible Mat" userInfo:nil]; + } + return num * _channels; +} + +@end diff --git a/modules/core/misc/objc/common/MatOfDouble.h b/modules/core/misc/objc/common/MatOfDouble.h new file mode 100644 index 0000000000..16a925a982 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfDouble.h @@ -0,0 +1,63 @@ +// +// MatOfDouble.h +// +// Created by Giles Payne on 2019/12/26. +// + +#pragma once + +#import "Mat.h" + +NS_ASSUME_NONNULL_BEGIN + +/** +* Mat representation of an array of doubles +*/ +@interface MatOfDouble : Mat + +#pragma mark - Constructors + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; +#endif + +/** +* Create MatOfDouble from Mat object +* @param mat Mat object from which to create MatOfDouble +*/ +- (instancetype)initWithMat:(Mat*)mat; + +/** +* Create MatOfDouble from array +* @param array Array from which to create MatOfDouble +*/ +- (instancetype)initWithArray:(NSArray*)array; + +#pragma mark - Methods + +/** +* Allocate specified number of elements +* @param elemNumber Number of elements +*/ +- (void)alloc:(int)elemNumber; + +/** +* Populate Mat with elements of an array +* @param array Array with which to populate the Mat +*/ +- (void)fromArray:(NSArray*)array; + +/** +* Output Mat elements as an array +*/ +- (NSArray*)toArray; + + +/** +* Total number of values in Mat +*/ +- (int)length; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MatOfDouble.mm b/modules/core/misc/objc/common/MatOfDouble.mm new file mode 100644 index 0000000000..ac6e0bac3f --- /dev/null +++ b/modules/core/misc/objc/common/MatOfDouble.mm @@ -0,0 +1,69 @@ +// +// MatOfDouble.mm +// +// Created by Giles Payne on 2019/12/26. +// + +#import "MatOfDouble.h" +#import "Range.h" +#import "CvType.h" +#import "ArrayUtil.h" + +@implementation MatOfDouble + +static const int _depth = CV_64F; +static const int _channels = 1; + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat { + self = [super initWithNativeMat:nativeMat]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} +#endif + +- (instancetype)initWithMat:(Mat*)mat { + self = [super initWithMat:mat rowRange:[Range all]]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + +- (instancetype)initWithArray:(NSArray*)array { + self = [super init]; + if (self) { + [self fromArray:array]; + } + return self; +} + +- (void)alloc:(int)elemNumber { + if (elemNumber>0) { + [super create:elemNumber cols:1 type:[CvType makeType:_depth channels:_channels]]; + } +} + +- (void)fromArray:(NSArray*)array { + [self alloc:(int)array.count / _channels]; + [self put:0 col:0 data:array]; +} + +- (NSArray*)toArray { + int length = [self length]; + NSMutableArray* data = createArrayWithSize(length, @0.0); + [self get:0 col:0 data:data]; + return data; +} + +- (int)length { + int num = [self checkVector:_channels depth:_depth]; + if (num < 0) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Incompatible Mat" userInfo:nil]; + } + return num * _channels; +} + +@end diff --git a/modules/core/misc/objc/common/MatOfFloat.h b/modules/core/misc/objc/common/MatOfFloat.h new file mode 100644 index 0000000000..110819d85f --- /dev/null +++ b/modules/core/misc/objc/common/MatOfFloat.h @@ -0,0 +1,60 @@ +// +// MatOfFloat.h +// +// Created by Giles Payne on 2019/12/26. +// + +#pragma once + +#import "Mat.h" + +NS_ASSUME_NONNULL_BEGIN + +/** +* Mat representation of an array of floats +*/ +@interface MatOfFloat : Mat + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; +#endif + +/** +* Create MatOfFloat from Mat object +* @param mat Mat object from which to create MatOfFloat +*/ +- (instancetype)initWithMat:(Mat*)mat; + +/** +* Create MatOfFloat from array +* @param array Array from which to create MatOfFloat +*/ +- (instancetype)initWithArray:(NSArray*)array; + +#pragma mark - Methods + +/** +* Allocate specified number of elements +* @param elemNumber Number of elements +*/ +- (void)alloc:(int)elemNumber; + +/** +* Populate Mat with elements of an array +* @param array Array with which to populate the Mat +*/ +- (void)fromArray:(NSArray*)array; + +/** +* Output Mat elements as an array +*/ +- (NSArray*)toArray; + +/** +* Total number of values in Mat +*/ +- (int)length; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MatOfFloat.mm b/modules/core/misc/objc/common/MatOfFloat.mm new file mode 100644 index 0000000000..bce4f25e99 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfFloat.mm @@ -0,0 +1,69 @@ +// +// MatOfFloat.mm +// +// Created by Giles Payne on 2019/12/26. +// + +#import "MatOfFloat.h" +#import "Range.h" +#import "CvType.h" +#import "ArrayUtil.h" + +@implementation MatOfFloat + +static const int _depth = CV_32F; +static const int _channels = 1; + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat { + self = [super initWithNativeMat:nativeMat]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} +#endif + +- (instancetype)initWithMat:(Mat*)mat { + self = [super initWithMat:mat rowRange:[Range all]]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + +- (instancetype)initWithArray:(NSArray*)array { + self = [super init]; + if (self) { + [self fromArray:array]; + } + return self; +} + +- (void)alloc:(int)elemNumber { + if (elemNumber>0) { + [super create:elemNumber cols:1 type:[CvType makeType:_depth channels:_channels]]; + } +} + +- (void)fromArray:(NSArray*)array { + [self alloc:(int)array.count / _channels]; + [self put:0 col:0 data:array]; +} + +- (NSArray*)toArray { + int length = [self length]; + NSMutableArray* data = createArrayWithSize(length, @0.0); + [self get:0 col:0 data:data]; + return data; +} + +- (int)length { + int num = [self checkVector:_channels depth:_depth]; + if (num < 0) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Incompatible Mat" userInfo:nil]; + } + return num * _channels; +} + +@end diff --git a/modules/core/misc/objc/common/MatOfFloat4.h b/modules/core/misc/objc/common/MatOfFloat4.h new file mode 100644 index 0000000000..852e04bfd1 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfFloat4.h @@ -0,0 +1,62 @@ +// +// MatOfFloat4.h +// +// Created by Giles Payne on 2019/12/26. +// + +#pragma once + +#import "Mat.h" + +NS_ASSUME_NONNULL_BEGIN + +/** +* Mat representation of an array of vectors of four floats +*/ +@interface MatOfFloat4 : Mat + +#pragma mark - Constructors + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; +#endif + +/** +* Create MatOfFloat4 from Mat object +* @param mat Mat object from which to create MatOfFloat4 +*/ +- (instancetype)initWithMat:(Mat*)mat; + +/** +* Create MatOfFloat4 from array +* @param array Array from which to create MatOfFloat4 +*/ +- (instancetype)initWithArray:(NSArray*)array; + +#pragma mark - Methods + +/** +* Allocate specified number of elements +* @param elemNumber Number of elements +*/ +- (void)alloc:(int)elemNumber; + +/** +* Populate Mat with elements of an array +* @param array Array with which to populate the Mat +*/ +- (void)fromArray:(NSArray*)array; + +/** +* Output Mat elements as an array +*/ +- (NSArray*)toArray; + +/** +* Total number of values in Mat +*/ +- (int)length; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MatOfFloat4.mm b/modules/core/misc/objc/common/MatOfFloat4.mm new file mode 100644 index 0000000000..331767126b --- /dev/null +++ b/modules/core/misc/objc/common/MatOfFloat4.mm @@ -0,0 +1,69 @@ +// +// MatOfFloat4.mm +// +// Created by Giles Payne on 2019/12/26. +// + +#import "MatOfFloat4.h" +#import "Range.h" +#import "CvType.h" +#import "ArrayUtil.h" + +@implementation MatOfFloat4 + +static const int _depth = CV_32F; +static const int _channels = 4; + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat { + self = [super initWithNativeMat:nativeMat]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} +#endif + +- (instancetype)initWithMat:(Mat*)mat { + self = [super initWithMat:mat rowRange:[Range all]]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + +- (instancetype)initWithArray:(NSArray*)array { + self = [super init]; + if (self) { + [self fromArray:array]; + } + return self; +} + +- (void)alloc:(int)elemNumber { + if (elemNumber>0) { + [super create:elemNumber cols:1 type:[CvType makeType:_depth channels:_channels]]; + } +} + +- (void)fromArray:(NSArray*)array { + [self alloc:(int)array.count / _channels]; + [self put:0 col:0 data:array]; +} + +- (NSArray*)toArray { + int length = [self length]; + NSMutableArray* data = [[NSMutableArray alloc] initWithCapacity:length]; + [self get:0 col:0 data:data]; + return data; +} + +- (int)length { + int num = [self checkVector:_channels depth:_depth]; + if (num < 0) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Incompatible Mat" userInfo:nil]; + } + return num * _channels; +} + +@end diff --git a/modules/core/misc/objc/common/MatOfFloat6.h b/modules/core/misc/objc/common/MatOfFloat6.h new file mode 100644 index 0000000000..c602f48d9e --- /dev/null +++ b/modules/core/misc/objc/common/MatOfFloat6.h @@ -0,0 +1,62 @@ +// +// MatOfFloat6.h +// +// Created by Giles Payne on 2019/12/26. +// + +#pragma once + +#import "Mat.h" + +NS_ASSUME_NONNULL_BEGIN + +/** +* Mat representation of an array of vectors of six floats +*/ +@interface MatOfFloat6 : Mat + +#pragma mark - Constructors + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; +#endif + +/** +* Create MatOfFloat6 from Mat object +* @param mat Mat object from which to create MatOfFloat6 +*/ +- (instancetype)initWithMat:(Mat*)mat; + +/** +* Create MatOfFloat6 from array +* @param array Array from which to create MatOfFloat6 +*/ +- (instancetype)initWithArray:(NSArray*)array; + +#pragma mark - Methods + +/** +* Allocate specified number of elements +* @param elemNumber Number of elements +*/ +- (void)alloc:(int)elemNumber; + +/** +* Populate Mat with elements of an array +* @param array Array with which to populate the Mat +*/ +- (void)fromArray:(NSArray*)array; + +/** +* Output Mat elements as an array +*/ +- (NSArray*)toArray; + +/** +* Total number of values in Mat +*/ +- (int)length; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MatOfFloat6.mm b/modules/core/misc/objc/common/MatOfFloat6.mm new file mode 100644 index 0000000000..80f8e32d35 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfFloat6.mm @@ -0,0 +1,69 @@ +// +// MatOfFloat6.mm +// +// Created by Giles Payne on 2019/12/26. +// + +#import "MatOfFloat6.h" +#import "Range.h" +#import "CvType.h" +#import "ArrayUtil.h" + +@implementation MatOfFloat6 + +static const int _depth = CV_32F; +static const int _channels = 6; + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat { + self = [super initWithNativeMat:nativeMat]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} +#endif + +- (instancetype)initWithMat:(Mat*)mat { + self = [super initWithMat:mat rowRange:[Range all]]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + +- (instancetype)initWithArray:(NSArray*)array { + self = [super init]; + if (self) { + [self fromArray:array]; + } + return self; +} + +- (void)alloc:(int)elemNumber { + if (elemNumber>0) { + [super create:elemNumber cols:1 type:[CvType makeType:_depth channels:_channels]]; + } +} + +- (void)fromArray:(NSArray*)array { + [self alloc:(int)array.count / _channels]; + [self put:0 col:0 data:array]; +} + +- (NSArray*)toArray { + int length = [self length]; + NSMutableArray* data = [[NSMutableArray alloc] initWithCapacity:length]; + [self get:0 col:0 data:data]; + return data; +} + +- (int)length { + int num = [self checkVector:_channels depth:_depth]; + if (num < 0) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Incompatible Mat" userInfo:nil]; + } + return num * _channels; +} + +@end diff --git a/modules/core/misc/objc/common/MatOfInt.h b/modules/core/misc/objc/common/MatOfInt.h new file mode 100644 index 0000000000..b7e218353a --- /dev/null +++ b/modules/core/misc/objc/common/MatOfInt.h @@ -0,0 +1,62 @@ +// +// MatOfInt.h +// +// Created by Giles Payne on 2019/12/26. +// + +#pragma once + +#import "Mat.h" + +NS_ASSUME_NONNULL_BEGIN + +/** +* Mat representation of an array of ints +*/ +@interface MatOfInt : Mat + +#pragma mark - Constructors + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; +#endif + +/** +* Create MatOfInt from Mat object +* @param mat Mat object from which to create MatOfInt +*/ +- (instancetype)initWithMat:(Mat*)mat; + +/** +* Create MatOfInt from array +* @param array Array from which to create MatOfInt +*/ +- (instancetype)initWithArray:(NSArray*)array; + +#pragma mark - Methods + +/** +* Allocate specified number of elements +* @param elemNumber Number of elements +*/ +- (void)alloc:(int)elemNumber; + +/** +* Populate Mat with elements of an array +* @param array Array with which to populate the Mat +*/ +- (void)fromArray:(NSArray*)array; + +/** +* Output Mat elements as an array +*/ +- (NSArray*)toArray; + +/** +* Total number of values in Mat +*/ +- (int)length; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MatOfInt.mm b/modules/core/misc/objc/common/MatOfInt.mm new file mode 100644 index 0000000000..4777b9c7e2 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfInt.mm @@ -0,0 +1,69 @@ +// +// MatOfInt.mm +// +// Created by Giles Payne on 2019/12/26. +// + +#import "MatOfInt.h" +#import "Range.h" +#import "CvType.h" +#import "ArrayUtil.h" + +@implementation MatOfInt + +static const int _depth = CV_32S; +static const int _channels = 1; + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat { + self = [super initWithNativeMat:nativeMat]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} +#endif + +- (instancetype)initWithMat:(Mat*)mat { + self = [super initWithMat:mat rowRange:[Range all]]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + +- (instancetype)initWithArray:(NSArray*)array { + self = [super init]; + if (self) { + [self fromArray:array]; + } + return self; +} + +- (void)alloc:(int)elemNumber { + if (elemNumber>0) { + [super create:elemNumber cols:1 type:[CvType makeType:_depth channels:_channels]]; + } +} + +- (void)fromArray:(NSArray*)array { + [self alloc:(int)array.count / _channels]; + [self put:0 col:0 data:array]; +} + +- (NSArray*)toArray { + int length = [self length]; + NSMutableArray* data = createArrayWithSize(length, @0.0); + [self get:0 col:0 data:data]; + return data; +} + +- (int)length { + int num = [self checkVector:_channels depth:_depth]; + if (num < 0) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Incompatible Mat" userInfo:nil]; + } + return num * _channels; +} + +@end diff --git a/modules/core/misc/objc/common/MatOfInt4.h b/modules/core/misc/objc/common/MatOfInt4.h new file mode 100644 index 0000000000..c1fb40bb4b --- /dev/null +++ b/modules/core/misc/objc/common/MatOfInt4.h @@ -0,0 +1,62 @@ +// +// MatOfInt4.h +// +// Created by Giles Payne on 2019/12/27. +// + +#pragma once + +#import "Mat.h" + +NS_ASSUME_NONNULL_BEGIN + +/** +* Mat representation of an array of vectors of four ints +*/ +@interface MatOfInt4 : Mat + +#pragma mark - Constructors + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; +#endif + +/** +* Create MatOfInt4 from Mat object +* @param mat Mat object from which to create MatOfInt4 +*/ +- (instancetype)initWithMat:(Mat*)mat; + +/** +* Create MatOfInt4 from array +* @param array Array from which to create MatOfInt4 +*/ +- (instancetype)initWithArray:(NSArray*)array; + +#pragma mark - Methods + +/** +* Allocate specified number of elements +* @param elemNumber Number of elements +*/ +- (void)alloc:(int)elemNumber; + +/** +* Populate Mat with elements of an array +* @param array Array with which to populate the Mat +*/ +- (void)fromArray:(NSArray*)array; + +/** +* Output Mat elements as an array +*/ +- (NSArray*)toArray; + +/** +* Total number of values in Mat +*/ +- (int)length; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MatOfInt4.mm b/modules/core/misc/objc/common/MatOfInt4.mm new file mode 100644 index 0000000000..b5d81fe251 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfInt4.mm @@ -0,0 +1,69 @@ +// +// MatOfInt4.mm +// +// Created by Giles Payne on 2019/12/27. +// + +#import "MatOfInt4.h" +#import "Range.h" +#import "CvType.h" +#import "ArrayUtil.h" + +@implementation MatOfInt4 + +static const int _depth = CV_32S; +static const int _channels = 4; + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat { + self = [super initWithNativeMat:nativeMat]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} +#endif + +- (instancetype)initWithMat:(Mat*)mat { + self = [super initWithMat:mat rowRange:[Range all]]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + +- (instancetype)initWithArray:(NSArray*)array { + self = [super init]; + if (self) { + [self fromArray:array]; + } + return self; +} + +- (void)alloc:(int)elemNumber { + if (elemNumber>0) { + [super create:elemNumber cols:1 type:[CvType makeType:_depth channels:_channels]]; + } +} + +- (void)fromArray:(NSArray*)array { + [self alloc:(int)array.count / _channels]; + [self put:0 col:0 data:array]; +} + +- (NSArray*)toArray { + int length = [self length]; + NSMutableArray* data = createArrayWithSize(length, @0.0); + [self get:0 col:0 data:data]; + return data; +} + +- (int)length { + int num = [self checkVector:_channels depth:_depth]; + if (num < 0) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Incompatible Mat" userInfo:nil]; + } + return num * _channels; +} + +@end diff --git a/modules/core/misc/objc/common/MatOfKeyPoint.h b/modules/core/misc/objc/common/MatOfKeyPoint.h new file mode 100644 index 0000000000..769e836e83 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfKeyPoint.h @@ -0,0 +1,64 @@ +// +// MatOfKeyPoint.h +// +// Created by Giles Payne on 2019/12/27. +// + +#pragma once + +#import "Mat.h" + +@class KeyPoint; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Mat representation of an array of KeyPoint objects +*/ +@interface MatOfKeyPoint : Mat + +#pragma mark - Constructors + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; +#endif + +/** +* Create MatOfKeyPoint from Mat object +* @param mat Mat object from which to create MatOfKeyPoint +*/ +- (instancetype)initWithMat:(Mat*)mat; + +/** +* Create MatOfKeyPoint from array +* @param array Array from which to create MatOfKeyPoint +*/ +- (instancetype)initWithArray:(NSArray*)array; + +#pragma mark - Methods + +/** +* Allocate specified number of elements +* @param elemNumber Number of elements +*/ +- (void)alloc:(int)elemNumber; + +/** +* Populate Mat with elements of an array +* @param array Array with which to populate the Mat +*/ +- (void)fromArray:(NSArray*)array; + +/** +* Output Mat elements as an array of KeyPoint objects +*/ +- (NSArray*)toArray; + +/** +* Total number of values in Mat +*/ +- (int)length; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MatOfKeyPoint.mm b/modules/core/misc/objc/common/MatOfKeyPoint.mm new file mode 100644 index 0000000000..c6d92faea9 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfKeyPoint.mm @@ -0,0 +1,87 @@ +// +// MatOfKeyPoint.m +// +// Created by Giles Payne on 2019/12/27. +// + +#import "MatOfKeyPoint.h" +#import "Range.h" +#import "Point2f.h" +#import "KeyPoint.h" +#import "CvType.h" +#import "ArrayUtil.h" + +@implementation MatOfKeyPoint + +static const int _depth = CV_32F; +static const int _channels = 7; + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat { + self = [super initWithNativeMat:nativeMat]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} +#endif + +- (instancetype)initWithMat:(Mat*)mat { + self = [super initWithMat:mat rowRange:[Range all]]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + +- (instancetype)initWithArray:(NSArray*)array { + self = [super init]; + if (self) { + [self fromArray:array]; + } + return self; +} + +- (void)alloc:(int)elemNumber { + if (elemNumber>0) { + [super create:elemNumber cols:1 type:[CvType makeType:_depth channels:_channels]]; + } +} + +- (void)fromArray:(NSArray*)array { + NSMutableArray* data = [[NSMutableArray alloc] initWithCapacity:array.count * _channels]; + for (int index = 0; index < (int)array.count; index++) { + data[_channels * index] = [NSNumber numberWithFloat:array[index].pt.x]; + data[_channels * index + 1] = [NSNumber numberWithFloat:array[index].pt.y]; + data[_channels * index + 2] = [NSNumber numberWithFloat:array[index].size]; + data[_channels * index + 3] = [NSNumber numberWithFloat:array[index].angle]; + data[_channels * index + 4] = [NSNumber numberWithFloat:array[index].response]; + data[_channels * index + 5] = [NSNumber numberWithFloat:array[index].octave]; + data[_channels * index + 6] = [NSNumber numberWithFloat:array[index].classId]; + } + [self alloc:(int)array.count]; + [self put:0 col:0 data:data]; +} + +- (NSArray*)toArray { + int length = [self length] / _channels; + NSMutableArray* ret = createArrayWithSize(length, [KeyPoint new]); + if (length > 0) { + NSMutableArray* data = createArrayWithSize([self length], @0.0); + [self get:0 col:0 data:data]; + for (int index = 0; index < length; index++) { + ret[index] = [[KeyPoint alloc] initWithX:data[index * _channels].floatValue y:data[index * _channels + 1].floatValue size:data[index * _channels + 2].floatValue angle:data[index * _channels + 3].floatValue response:data[index * _channels + 4].floatValue octave:data[index * _channels + 5].intValue classId:data[index * _channels + 6].intValue]; + } + } + return ret; +} + +- (int)length { + int num = [self checkVector:_channels depth:_depth]; + if (num < 0) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Incompatible Mat" userInfo:nil]; + } + return num * _channels; +} + +@end diff --git a/modules/core/misc/objc/common/MatOfPoint2f.h b/modules/core/misc/objc/common/MatOfPoint2f.h new file mode 100644 index 0000000000..e6b6a2df99 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfPoint2f.h @@ -0,0 +1,64 @@ +// +// MatOfPoint2f.h +// +// Created by Giles Payne on 2019/12/27. +// + +#pragma once + +#import "Mat.h" + +NS_ASSUME_NONNULL_BEGIN + +@class Point2f; + +/** +* Mat representation of an array of Point2f objects +*/ +@interface MatOfPoint2f : Mat + +#pragma mark - Constructors + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; +#endif + +/** +* Create MatOfPoint2f from Mat object +* @param mat Mat object from which to create MatOfPoint2f +*/ +- (instancetype)initWithMat:(Mat*)mat; + +/** +* Create MatOfPoint2f from array +* @param array Array from which to create MatOfPoint2f +*/ +- (instancetype)initWithArray:(NSArray*)array; + +#pragma mark - Methods + +/** +* Allocate specified number of elements +* @param elemNumber Number of elements +*/ +- (void)alloc:(int)elemNumber; + +/** +* Populate Mat with elements of an array +* @param array Array with which to populate the Mat +*/ +- (void)fromArray:(NSArray*)array; + +/** +* Output Mat elements as an array of Point2f objects +*/ +- (NSArray*)toArray; + +/** +* Total number of values in Mat +*/ +- (int)length; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MatOfPoint2f.mm b/modules/core/misc/objc/common/MatOfPoint2f.mm new file mode 100644 index 0000000000..c1c082b5fe --- /dev/null +++ b/modules/core/misc/objc/common/MatOfPoint2f.mm @@ -0,0 +1,81 @@ +// +// MatOfPoint2f.mm +// +// Created by Giles Payne on 2019/12/27. +// + +#import "MatOfPoint2f.h" +#import "Range.h" +#import "Point2f.h" +#import "CvType.h" +#import "ArrayUtil.h" + +@implementation MatOfPoint2f + +static const int _depth = CV_32F; +static const int _channels = 2; + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat { + self = [super initWithNativeMat:nativeMat]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} +#endif + +- (instancetype)initWithMat:(Mat*)mat { + self = [super initWithMat:mat rowRange:[Range all]]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + +- (instancetype)initWithArray:(NSArray*)array { + self = [super init]; + if (self) { + [self fromArray:array]; + } + return self; +} + +- (void)alloc:(int)elemNumber { + if (elemNumber>0) { + [super create:elemNumber cols:1 type:[CvType makeType:_depth channels:_channels]]; + } +} + +- (void)fromArray:(NSArray*)array { + NSMutableArray* data = [[NSMutableArray alloc] initWithCapacity:array.count * _channels]; + for (int index = 0; index < (int)array.count; index++) { + data[_channels * index] = [NSNumber numberWithFloat:array[index].x]; + data[_channels * index + 1] = [NSNumber numberWithFloat:array[index].y]; + } + [self alloc:(int)array.count]; + [self put:0 col:0 data:data]; +} + +- (NSArray*)toArray { + int length = [self length] / _channels; + NSMutableArray* ret = createArrayWithSize(length, [Point2f new]); + if (length > 0) { + NSMutableArray* data = createArrayWithSize([self length], @0.0); + [self get:0 col:0 data:data]; + for (int index = 0; index < length; index++) { + ret[index] = [[Point2f alloc] initWithX:data[index * _channels].floatValue y:data[index * _channels + 1].floatValue]; + } + } + return ret; +} + +- (int)length { + int num = [self checkVector:_channels depth:_depth]; + if (num < 0) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Incompatible Mat" userInfo:nil]; + } + return num * _channels; +} + +@end diff --git a/modules/core/misc/objc/common/MatOfPoint2i.h b/modules/core/misc/objc/common/MatOfPoint2i.h new file mode 100644 index 0000000000..dea9710e31 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfPoint2i.h @@ -0,0 +1,65 @@ +// +// MatOfPoint2i.h +// +// Created by Giles Payne on 2019/12/27. +// + +#pragma once + +#import "Mat.h" + +@class Point2i; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Mat representation of an array of Point objects +*/ +NS_SWIFT_NAME(MatOfPoint) +@interface MatOfPoint2i : Mat + +#pragma mark - Constructors + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; +#endif + +/** +* Create MatOfPoint from Mat object +* @param mat Mat object from which to create MatOfPoint +*/ +- (instancetype)initWithMat:(Mat*)mat; + +/** +* Create MatOfPoint from array +* @param array Array from which to create MatOfPoint +*/ +- (instancetype)initWithArray:(NSArray*)array; + +#pragma mark - Methods + +/** +* Allocate specified number of elements +* @param elemNumber Number of elements +*/ +- (void)alloc:(int)elemNumber; + +/** +* Populate Mat with elements of an array +* @param array Array with which to populate the Mat +*/ +- (void)fromArray:(NSArray*)array; + +/** +* Output Mat elements as an array of Point objects +*/ +- (NSArray*)toArray; + +/** +* Total number of values in Mat +*/ +- (int)length; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MatOfPoint2i.mm b/modules/core/misc/objc/common/MatOfPoint2i.mm new file mode 100644 index 0000000000..7c202f6d42 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfPoint2i.mm @@ -0,0 +1,81 @@ +// +// MatOfPoint2i.mm +// +// Created by Giles Payne on 2019/12/27. +// + +#import "MatOfPoint2i.h" +#import "Range.h" +#import "Point2i.h" +#import "CvType.h" +#import "ArrayUtil.h" + +@implementation MatOfPoint2i + +static const int _depth = CV_32S; +static const int _channels = 2; + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat { + self = [super initWithNativeMat:nativeMat]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} +#endif + +- (instancetype)initWithMat:(Mat*)mat { + self = [super initWithMat:mat rowRange:[Range all]]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + +- (instancetype)initWithArray:(NSArray*)array { + self = [super init]; + if (self) { + [self fromArray:array]; + } + return self; +} + +- (void)alloc:(int)elemNumber { + if (elemNumber>0) { + [super create:elemNumber cols:1 type:[CvType makeType:_depth channels:_channels]]; + } +} + +- (void)fromArray:(NSArray*)array { + NSMutableArray* data = [[NSMutableArray alloc] initWithCapacity:array.count * _channels]; + for (int index = 0; index < (int)array.count; index++) { + data[_channels * index] = [NSNumber numberWithInt:array[index].x]; + data[_channels * index + 1] = [NSNumber numberWithInt:array[index].y]; + } + [self alloc:(int)array.count]; + [self put:0 col:0 data:data]; +} + +- (NSArray*)toArray { + int length = [self length] / _channels; + NSMutableArray* ret = createArrayWithSize(length, [Point2i new]); + if (length > 0) { + NSMutableArray* data = createArrayWithSize([self length], @0.0); + [self get:0 col:0 data:data]; + for (int index = 0; index < length; index++) { + ret[index] = [[Point2i alloc] initWithX:data[index * _channels].intValue y:data[index * _channels + 1].intValue]; + } + } + return ret; +} + +- (int)length { + int num = [self checkVector:_channels depth:_depth]; + if (num < 0) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Incompatible Mat" userInfo:nil]; + } + return num * _channels; +} + +@end diff --git a/modules/core/misc/objc/common/MatOfPoint3.h b/modules/core/misc/objc/common/MatOfPoint3.h new file mode 100644 index 0000000000..d1ab9b87c7 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfPoint3.h @@ -0,0 +1,64 @@ +// +// MatOfPoint3.h +// +// Created by Giles Payne on 2019/12/27. +// + +#pragma once + +#import "Mat.h" + +@class Point3i; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Mat representation of an array of Point3i objects +*/ +@interface MatOfPoint3 : Mat + +#pragma mark - Constructors + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; +#endif + +/** +* Create MatOfPoint3 from Mat object +* @param mat Mat object from which to create MatOfPoint3 +*/ +- (instancetype)initWithMat:(Mat*)mat; + +/** +* Create MatOfPoint3 from array +* @param array Array from which to create MatOfPoint3 +*/ +- (instancetype)initWithArray:(NSArray*)array; + +#pragma mark - Methods + +/** +* Allocate specified number of elements +* @param elemNumber Number of elements +*/ +- (void)alloc:(int)elemNumber; + +/** +* Populate Mat with elements of an array +* @param array Array with which to populate the Mat +*/ +- (void)fromArray:(NSArray*)array; + +/** +* Output Mat elements as an array of Point3i objects +*/ +- (NSArray*)toArray; + +/** +* Total number of values in Mat +*/ +- (int)length; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MatOfPoint3.mm b/modules/core/misc/objc/common/MatOfPoint3.mm new file mode 100644 index 0000000000..546194b8df --- /dev/null +++ b/modules/core/misc/objc/common/MatOfPoint3.mm @@ -0,0 +1,82 @@ +// +// MatOfPoint3.mm +// +// Created by Giles Payne on 2019/12/27. +// + +#import "MatOfPoint3.h" +#import "Range.h" +#import "Point3i.h" +#import "CvType.h" +#import "ArrayUtil.h" + +@implementation MatOfPoint3 + +static const int _depth = CV_32S; +static const int _channels = 3; + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat { + self = [super initWithNativeMat:nativeMat]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} +#endif + +- (instancetype)initWithMat:(Mat*)mat { + self = [super initWithMat:mat rowRange:[Range all]]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + +- (instancetype)initWithArray:(NSArray*)array { + self = [super init]; + if (self) { + [self fromArray:array]; + } + return self; +} + +- (void)alloc:(int)elemNumber { + if (elemNumber>0) { + [super create:elemNumber cols:1 type:[CvType makeType:_depth channels:_channels]]; + } +} + +- (void)fromArray:(NSArray*)array { + NSMutableArray* data = [[NSMutableArray alloc] initWithCapacity:array.count * _channels]; + for (int index = 0; index < (int)array.count; index++) { + data[_channels * index] = [NSNumber numberWithInt:array[index].x]; + data[_channels * index + 1] = [NSNumber numberWithInt:array[index].y]; + data[_channels * index + 2] = [NSNumber numberWithInt:array[index].z]; + } + [self alloc:(int)array.count]; + [self put:0 col:0 data:data]; +} + +- (NSArray*)toArray { + int length = [self length] / _channels; + NSMutableArray* ret = createArrayWithSize(length, [Point3i new]); + if (length > 0) { + NSMutableArray* data = createArrayWithSize([self length], @0.0); + [self get:0 col:0 data:data]; + for (int index = 0; index < length; index++) { + ret[index] = [[Point3i alloc] initWithX:data[index * _channels].intValue y:data[index * _channels + 1].intValue z:data[index * _channels + 2].intValue]; + } + } + return ret; +} + +- (int)length { + int num = [self checkVector:_channels depth:_depth]; + if (num < 0) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Incompatible Mat" userInfo:nil]; + } + return num * _channels; +} + +@end diff --git a/modules/core/misc/objc/common/MatOfPoint3f.h b/modules/core/misc/objc/common/MatOfPoint3f.h new file mode 100644 index 0000000000..e9ba7b37f7 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfPoint3f.h @@ -0,0 +1,64 @@ +// +// MatOfPoint3f.h +// +// Created by Giles Payne on 2019/12/27. +// + +#pragma once + +#import "Mat.h" + +@class Point3f; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Mat representation of an array of Point3f objects +*/ +@interface MatOfPoint3f : Mat + +#pragma mark - Constructors + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; +#endif + +/** +* Create MatOfPoint3f from Mat object +* @param mat Mat object from which to create MatOfPoint3f +*/ +- (instancetype)initWithMat:(Mat*)mat; + +/** +* Create MatOfPoint3f from array +* @param array Array from which to create MatOfPoint3f +*/ +- (instancetype)initWithArray:(NSArray*)array; + +#pragma mark - Methods + +/** +* Allocate specified number of elements +* @param elemNumber Number of elements +*/ +- (void)alloc:(int)elemNumber; + +/** +* Populate Mat with elements of an array +* @param array Array with which to populate the Mat +*/ +- (void)fromArray:(NSArray*)array; + +/** +* Output Mat elements as an array of Point3f objects +*/ +- (NSArray*)toArray; + +/** +* Total number of values in Mat +*/ +- (int)length; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MatOfPoint3f.mm b/modules/core/misc/objc/common/MatOfPoint3f.mm new file mode 100644 index 0000000000..ea34210cd1 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfPoint3f.mm @@ -0,0 +1,82 @@ +// +// MatOfPoint3f.mm +// +// Created by Giles Payne on 2019/12/27. +// + +#import "MatOfPoint3f.h" +#import "Range.h" +#import "Point3f.h" +#import "CvType.h" +#import "ArrayUtil.h" + +@implementation MatOfPoint3f + +static const int _depth = CV_32F; +static const int _channels = 3; + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat { + self = [super initWithNativeMat:nativeMat]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} +#endif + +- (instancetype)initWithMat:(Mat*)mat { + self = [super initWithMat:mat rowRange:[Range all]]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + +- (instancetype)initWithArray:(NSArray*)array { + self = [super init]; + if (self) { + [self fromArray:array]; + } + return self; +} + +- (void)alloc:(int)elemNumber { + if (elemNumber>0) { + [super create:elemNumber cols:1 type:[CvType makeType:_depth channels:_channels]]; + } +} + +- (void)fromArray:(NSArray*)array { + NSMutableArray* data = [[NSMutableArray alloc] initWithCapacity:array.count * _channels]; + for (int index = 0; index < (int)array.count; index++) { + data[_channels * index] = [NSNumber numberWithFloat:array[index].x]; + data[_channels * index + 1] = [NSNumber numberWithFloat:array[index].y]; + data[_channels * index + 2] = [NSNumber numberWithFloat:array[index].z]; + } + [self alloc:(int)array.count]; + [self put:0 col:0 data:data]; +} + +- (NSArray*)toArray { + int length = [self length] / _channels; + NSMutableArray* ret = createArrayWithSize(length, [Point3f new]); + if (length > 0) { + NSMutableArray* data = createArrayWithSize([self length], @0.0); + [self get:0 col:0 data:data]; + for (int index = 0; index < length; index++) { + ret[index] = [[Point3f alloc] initWithX:data[index * _channels].floatValue y:data[index * _channels + 1].floatValue z:data[index * _channels + 2].floatValue]; + } + } + return ret; +} + +- (int)length { + int num = [self checkVector:_channels depth:_depth]; + if (num < 0) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Incompatible Mat" userInfo:nil]; + } + return num * _channels; +} + +@end diff --git a/modules/core/misc/objc/common/MatOfRect2d.h b/modules/core/misc/objc/common/MatOfRect2d.h new file mode 100644 index 0000000000..4388fcfa11 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfRect2d.h @@ -0,0 +1,65 @@ +// +// MatOfRect2d.h +// +// Created by Giles Payne on 2019/12/27. +// + +#pragma once + +#import "Mat.h" + +@class Rect2d; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Mat representation of an array of Rect2d objects +*/ +@interface MatOfRect2d : Mat + +#pragma mark - Constructors + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; ++ (instancetype)fromNative:(cv::Mat*)nativeMat; +#endif + +/** +* Create MatOfRect2d from Mat object +* @param mat Mat object from which to create MatOfRect2d +*/ +- (instancetype)initWithMat:(Mat*)mat; + +/** +* Create MatOfRect2d from array +* @param array Array from which to create MatOfRect2d +*/ +- (instancetype)initWithArray:(NSArray*)array; + +#pragma mark - Methods + +/** +* Allocate specified number of elements +* @param elemNumber Number of elements +*/ +- (void)alloc:(int)elemNumber; + +/** +* Populate Mat with elements of an array +* @param array Array with which to populate the Mat +*/ +- (void)fromArray:(NSArray*)array; + +/** +* Output Mat elements as an array of Rect2d objects +*/ +- (NSArray*)toArray; + +/** +* Total number of values in Mat +*/ +- (int)length; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MatOfRect2d.mm b/modules/core/misc/objc/common/MatOfRect2d.mm new file mode 100644 index 0000000000..61490c3003 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfRect2d.mm @@ -0,0 +1,88 @@ +// +// MatOfRect2d.mm +// +// Created by Giles Payne on 2019/12/27. +// + +#import "MatOfRect2d.h" +#import "Range.h" +#import "Rect2d.h" +#import "CvType.h" +#import "ArrayUtil.h" + +@implementation MatOfRect2d + +static const int _depth = CV_64F; +static const int _channels = 4; + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat { + self = [super initWithNativeMat:nativeMat]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + ++ (instancetype)fromNative:(cv::Mat*)nativeMat { + return [[MatOfRect2d alloc] initWithNativeMat:nativeMat]; +} + +#endif + +- (instancetype)initWithMat:(Mat*)mat { + self = [super initWithMat:mat rowRange:[Range all]]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + +- (instancetype)initWithArray:(NSArray*)array { + self = [super init]; + if (self) { + [self fromArray:array]; + } + return self; +} + +- (void)alloc:(int)elemNumber { + if (elemNumber>0) { + [super create:elemNumber cols:1 type:[CvType makeType:_depth channels:_channels]]; + } +} + +- (void)fromArray:(NSArray*)array { + NSMutableArray* data = [[NSMutableArray alloc] initWithCapacity:array.count * _channels]; + for (int index = 0; index < (int)array.count; index++) { + data[_channels * index] = [NSNumber numberWithDouble:array[index].x]; + data[_channels * index + 1] = [NSNumber numberWithDouble:array[index].y]; + data[_channels * index + 2] = [NSNumber numberWithDouble:array[index].width]; + data[_channels * index + 3] = [NSNumber numberWithDouble:array[index].height]; + } + [self alloc:(int)array.count]; + [self put:0 col:0 data:data]; +} + +- (NSArray*)toArray { + int length = [self length] / _channels; + NSMutableArray* ret = createArrayWithSize(length, [Rect2d new]); + if (length > 0) { + NSMutableArray* data = createArrayWithSize([self length], @0.0); + [self get:0 col:0 data:data]; + for (int index = 0; index < length; index++) { + ret[index] = [[Rect2d alloc] initWithX:data[index * _channels].doubleValue y:data[index * _channels + 1].doubleValue width:data[index * _channels + 2].doubleValue height:data[index * _channels + 3].doubleValue]; + } + } + return ret; +} + +- (int)length { + int num = [self checkVector:_channels depth:_depth]; + if (num < 0) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Incompatible Mat" userInfo:nil]; + } + return num * _channels; +} + +@end diff --git a/modules/core/misc/objc/common/MatOfRect2i.h b/modules/core/misc/objc/common/MatOfRect2i.h new file mode 100644 index 0000000000..fa23338172 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfRect2i.h @@ -0,0 +1,65 @@ +// +// MatOfRect2i.h +// +// Created by Giles Payne on 2019/12/27. +// + +#pragma once + +#import "Mat.h" + +@class Rect2i; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Mat representation of an array of Rect objects +*/ +NS_SWIFT_NAME(MatOfRect) +@interface MatOfRect2i : Mat + +#pragma mark - Constructors + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; +#endif + +/** +* Create MatOfRect from Mat object +* @param mat Mat object from which to create MatOfRect +*/ +- (instancetype)initWithMat:(Mat*)mat; + +/** +* Create MatOfRect from array +* @param array Array from which to create MatOfRect +*/ +- (instancetype)initWithArray:(NSArray*)array; + +#pragma mark - Methods + +/** +* Allocate specified number of elements +* @param elemNumber Number of elements +*/ +- (void)alloc:(int)elemNumber; + +/** +* Populate Mat with elements of an array +* @param array Array with which to populate the Mat +*/ +- (void)fromArray:(NSArray*)array; + +/** +* Output Mat elements as an array of Rect objects +*/ +- (NSArray*)toArray; + +/** +* Total number of values in Mat +*/ +- (int)length; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MatOfRect2i.mm b/modules/core/misc/objc/common/MatOfRect2i.mm new file mode 100644 index 0000000000..00e8b824d0 --- /dev/null +++ b/modules/core/misc/objc/common/MatOfRect2i.mm @@ -0,0 +1,83 @@ +// +// MatOfRect2i.m +// +// Created by Giles Payne on 2019/12/27. +// + +#import "MatOfRect2i.h" +#import "Range.h" +#import "Rect2i.h" +#import "CvType.h" +#import "ArrayUtil.h" + +@implementation MatOfRect2i + +static const int _depth = CV_32S; +static const int _channels = 4; + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat { + self = [super initWithNativeMat:nativeMat]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} +#endif + +- (instancetype)initWithMat:(Mat*)mat { + self = [super initWithMat:mat rowRange:[Range all]]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + +- (instancetype)initWithArray:(NSArray*)array { + self = [super init]; + if (self) { + [self fromArray:array]; + } + return self; +} + +- (void)alloc:(int)elemNumber { + if (elemNumber>0) { + [super create:elemNumber cols:1 type:[CvType makeType:_depth channels:_channels]]; + } +} + +- (void)fromArray:(NSArray*)array { + NSMutableArray* data = [[NSMutableArray alloc] initWithCapacity:array.count * _channels]; + for (int index = 0; index < (int)array.count; index++) { + data[_channels * index] = [NSNumber numberWithInt:array[index].x]; + data[_channels * index + 1] = [NSNumber numberWithInt:array[index].y]; + data[_channels * index + 2] = [NSNumber numberWithInt:array[index].width]; + data[_channels * index + 3] = [NSNumber numberWithInt:array[index].height]; + } + [self alloc:(int)array.count]; + [self put:0 col:0 data:data]; +} + +- (NSArray*)toArray { + int length = [self length] / _channels; + NSMutableArray* ret = createArrayWithSize(length, [Rect2i new]); + if (length > 0) { + NSMutableArray* data = createArrayWithSize([self length], @0.0); + [self get:0 col:0 data:data]; + for (int index = 0; index < length; index++) { + ret[index] = [[Rect2i alloc] initWithX:data[index * _channels].intValue y:data[index * _channels + 1].intValue width:data[index * _channels + 2].intValue height:data[index * _channels + 3].intValue]; + } + } + return ret; +} + +- (int)length { + int num = [self checkVector:_channels depth:_depth]; + if (num < 0) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Incompatible Mat" userInfo:nil]; + } + return num * _channels; +} + +@end diff --git a/modules/core/misc/objc/common/MatOfRotatedRect.h b/modules/core/misc/objc/common/MatOfRotatedRect.h new file mode 100644 index 0000000000..a9108e030e --- /dev/null +++ b/modules/core/misc/objc/common/MatOfRotatedRect.h @@ -0,0 +1,64 @@ +// +// MatOfRotatedRect.h +// +// Created by Giles Payne on 2019/12/27. +// + +#pragma once + +#import "Mat.h" + +@class RotatedRect; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Mat representation of an array of RotatedRect objects +*/ +@interface MatOfRotatedRect : Mat + +#pragma mark - Constructors + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat; +#endif + +/** +* Create MatOfRotatedRect from Mat object +* @param mat Mat object from which to create MatOfRotatedRect +*/ +- (instancetype)initWithMat:(Mat*)mat; + +/** +* Create MatOfRotatedRect from array +* @param array Array from which to create MatOfRotatedRect +*/ +- (instancetype)initWithArray:(NSArray*)array; + +#pragma mark - Methods + +/** +* Allocate specified number of elements +* @param elemNumber Number of elements +*/ +- (void)alloc:(int)elemNumber; + +/** +* Populate Mat with elements of an array +* @param array Array with which to populate the Mat +*/ +- (void)fromArray:(NSArray*)array; + +/** +* Output Mat elements as an array of RotatedRect objects +*/ +- (NSArray*)toArray; + +/** +* Total number of values in Mat +*/ +- (int)length; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MatOfRotatedRect.mm b/modules/core/misc/objc/common/MatOfRotatedRect.mm new file mode 100644 index 0000000000..ff88f3e6dd --- /dev/null +++ b/modules/core/misc/objc/common/MatOfRotatedRect.mm @@ -0,0 +1,87 @@ +// +// MatOfRotatedRect.mm +// +// Created by Giles Payne on 2019/12/27. +// + +#import "MatOfRotatedRect.h" +#import "Range.h" +#import "RotatedRect.h" +#import "Point2f.h" +#import "Size2f.h" +#import "CvType.h" +#import "ArrayUtil.h" + +@implementation MatOfRotatedRect + +static const int _depth = CV_32F; +static const int _channels = 5; + +#ifdef __cplusplus +- (instancetype)initWithNativeMat:(cv::Mat*)nativeMat { + self = [super initWithNativeMat:nativeMat]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} +#endif + +- (instancetype)initWithMat:(Mat*)mat { + self = [super initWithMat:mat rowRange:[Range all]]; + if (self && ![self empty] && [self checkVector:_channels depth:_depth] < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Incompatible Mat" userInfo:nil]; + } + return self; +} + +- (instancetype)initWithArray:(NSArray*)array { + self = [super init]; + if (self) { + [self fromArray:array]; + } + return self; +} + +- (void)alloc:(int)elemNumber { + if (elemNumber>0) { + [super create:elemNumber cols:1 type:[CvType makeType:_depth channels:_channels]]; + } +} + +- (void)fromArray:(NSArray*)array { + NSMutableArray* data = [[NSMutableArray alloc] initWithCapacity:array.count * _channels]; + for (int index = 0; index < (int)array.count; index++) { + data[_channels * index] = [NSNumber numberWithFloat:array[index].center.x]; + data[_channels * index + 1] = [NSNumber numberWithFloat:array[index].center.y]; + data[_channels * index + 2] = [NSNumber numberWithFloat:array[index].size.width]; + data[_channels * index + 3] = [NSNumber numberWithFloat:array[index].size.height]; + data[_channels * index + 4] = [NSNumber numberWithFloat:array[index].angle]; + + } + [self alloc:(int)array.count]; + [self put:0 col:0 data:data]; +} + +- (NSArray*)toArray { + int length = [self length] / _channels; + NSMutableArray* ret = createArrayWithSize(length, [RotatedRect new]); + if (length > 0) { + NSMutableArray* data = createArrayWithSize([self length], @0.0); + [self get:0 col:0 data:data]; + for (int index = 0; index < length; index++) { + ret[index] = [[RotatedRect alloc] initWithCenter:[[Point2f alloc] initWithX:data[index * _channels].floatValue y:data[index * _channels + 1].floatValue] size:[[Size2f alloc] initWithWidth:data[index * _channels + 2].floatValue height:data[index * _channels + 3].floatValue] angle:data[index * _channels + 4].floatValue]; + } + } + return ret; +} + +- (int)length { + int num = [self checkVector:_channels depth:_depth]; + if (num < 0) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Incompatible Mat" userInfo:nil]; + } + return num * _channels; +} + +@end diff --git a/modules/core/misc/objc/common/MinMaxLocResult.h b/modules/core/misc/objc/common/MinMaxLocResult.h new file mode 100644 index 0000000000..87593f2b18 --- /dev/null +++ b/modules/core/misc/objc/common/MinMaxLocResult.h @@ -0,0 +1,38 @@ +// +// MinMaxLocResult.h +// +// Created by Giles Payne on 2019/12/28. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +@class Point2i; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Result of operation to determine global minimum and maximum of an array +*/ +@interface MinMaxLocResult : NSObject + +#pragma mark - Properties + +@property double minVal; +@property double maxVal; +@property Point2i* minLoc; +@property Point2i* maxLoc; + +#pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithMinval:(double)minVal maxVal:(double)maxVal minLoc:(Point2i*)minLoc maxLoc:(Point2i*)maxLoc; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/MinMaxLocResult.mm b/modules/core/misc/objc/common/MinMaxLocResult.mm new file mode 100644 index 0000000000..1e4215bd3d --- /dev/null +++ b/modules/core/misc/objc/common/MinMaxLocResult.mm @@ -0,0 +1,27 @@ +// +// MinMaxLocResult.m +// +// Created by Giles Payne on 2019/12/28. +// + +#import "MinMaxLocResult.h" +#import "Point2i.h" + +@implementation MinMaxLocResult + +- (instancetype)init { + return [self initWithMinval:0 maxVal:0 minLoc:[Point2i new] maxLoc:[Point2i new]]; +} + +- (instancetype)initWithMinval:(double)minVal maxVal:(double)maxVal minLoc:(Point2i*)minLoc maxLoc:(Point2i*)maxLoc { + self = [super init]; + if (self) { + self.minVal = minVal; + self.maxVal = maxVal; + self.minLoc = minLoc; + self.maxLoc = maxLoc; + } + return self; +} + +@end diff --git a/modules/core/misc/objc/common/Point2d.h b/modules/core/misc/objc/common/Point2d.h new file mode 100644 index 0000000000..8187c074f8 --- /dev/null +++ b/modules/core/misc/objc/common/Point2d.h @@ -0,0 +1,87 @@ +// +// Point2d.h +// +// Created by Giles Payne on 2019/10/09. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +@class Rect2d; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Represents a two dimensional point the coordinate values of which are of type `double` +*/ +@interface Point2d : NSObject + +# pragma mark - Properties + +@property double x; +@property double y; +#ifdef __cplusplus +@property(readonly) cv::Point2d& nativeRef; +#endif + +# pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithX:(double)x y:(double)y; +- (instancetype)initWithVals:(NSArray*)vals; + +#ifdef __cplusplus ++ (instancetype)fromNative:(cv::Point2d&)point; +- (void)update:(cv::Point2d&)point; +#endif + +# pragma mark - Methods + +/** +* Calculate the dot product of this point and another point +* @param point The other point +*/ +- (double)dot:(Point2d*)point; + +/** +* Determine if the point lies with a specified rectangle +* @param rect The rectangle +*/ +- (BOOL)inside:(Rect2d*)rect; + +/** +* Set the point coordinates from the values of an array +* @param vals The array of values from which to set the coordinates +*/ +- (void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +# pragma mark - Common Methods + +/** +* Clone object +*/ +- (Point2d*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)other; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString*)description; +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Point2d.mm b/modules/core/misc/objc/common/Point2d.mm new file mode 100644 index 0000000000..6c75cd233b --- /dev/null +++ b/modules/core/misc/objc/common/Point2d.mm @@ -0,0 +1,107 @@ +// +// Point2d.m +// +// Created by Giles Payne on 2019/10/09. +// + +#import "Point2d.h" +#import "Rect2d.h" +#import "CVObjcUtil.h" + +@implementation Point2d { + cv::Point2d native; +} + +- (double)x { + return native.x; +} + +- (void)setX:(double)val { + native.x = val; +} + +- (double)y { + return native.y; +} + +- (void)setY:(double)val { + native.y = val; +} + +- (cv::Point2d&)nativeRef { + return native; +} + +- (instancetype)init { + return [self initWithX:0 y:0]; +} + +- (instancetype)initWithX:(double)x y:(double)y { + self = [super init]; + if (self) { + self.x = x; + self.y = y; + } + return self; +} + +- (instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + ++ (instancetype)fromNative:(cv::Point2d&)point { + return [[Point2d alloc] initWithX:point.x y:point.y]; +} + +- (void)update:(cv::Point2d&)point { + self.x = point.x; + self.y = point.y; +} + +- (Point2d*) clone { + return [[Point2d alloc] initWithX:self.x y:self.y]; +} + +- (double)dot:(Point2d*)point { + return self.x * point.x + self.y * point.y; +} + +- (void)set:(NSArray*)vals { + self.x = (vals != nil && vals.count > 0) ? vals[0].doubleValue : 0; + self.y = (vals != nil && vals.count > 1) ? vals[1].doubleValue : 0; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Point2d class]]) { + return NO; + } else { + Point2d* point = (Point2d*)other; + return self.x == point.x && self.y == point.y; + } +} + +- (BOOL)inside:(Rect2d*)rect { + return [rect contains:self]; +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + int64_t temp = DOUBLE_TO_BITS(self.x); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + temp = DOUBLE_TO_BITS(self.y); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + return result; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Point2d {%lf,%lf}", self.x, self.y]; +} + +@end diff --git a/modules/core/misc/objc/common/Point2f.h b/modules/core/misc/objc/common/Point2f.h new file mode 100644 index 0000000000..6d7d0f9732 --- /dev/null +++ b/modules/core/misc/objc/common/Point2f.h @@ -0,0 +1,87 @@ +// +// Point2f.h +// +// Created by Giles Payne on 2019/10/09. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +@class Rect2f; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Represents a two dimensional point the coordinate values of which are of type `float` +*/ +@interface Point2f : NSObject + +# pragma mark - Properties + +@property float x; +@property float y; +#ifdef __cplusplus +@property(readonly) cv::Point2f& nativeRef; +#endif + +# pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithX:(float)x y:(float)y; +- (instancetype)initWithVals:(NSArray*)vals; + +#ifdef __cplusplus ++ (instancetype)fromNative:(cv::Point2f&)point; +- (void)update:(cv::Point2f&)point; +#endif + +# pragma mark - Methods + +/** +* Calculate the dot product of this point and another point +* @param point The other point +*/ +- (double)dot:(Point2f*)point; + +/** +* Determine if the point lies with a specified rectangle +* @param rect The rectangle +*/ +- (BOOL)inside:(Rect2f*)rect; + +/** +* Set the point coordinates from the values of an array +* @param vals The array of values from which to set the coordinates +*/ +- (void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +# pragma mark - Common Methods + +/** +* Clone object +*/ +- (Point2f*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)other; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString*)description; +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Point2f.mm b/modules/core/misc/objc/common/Point2f.mm new file mode 100644 index 0000000000..a43ed805b0 --- /dev/null +++ b/modules/core/misc/objc/common/Point2f.mm @@ -0,0 +1,105 @@ +// +// Point2f.m +// +// Created by Giles Payne on 2019/10/09. +// + +#import "Point2f.h" +#import "Rect2f.h" +#import "CVObjcUtil.h" + +@implementation Point2f { + cv::Point2f native; +} + +- (float)x { + return native.x; +} + +- (void)setX:(float)val { + native.x = val; +} + +- (float)y { + return native.y; +} + +- (void)setY:(float)val { + native.y = val; +} + +- (cv::Point2f&)nativeRef { + return native; +} + +- (instancetype)init { + return [self initWithX:0 y:0]; +} + +- (instancetype)initWithX:(float)x y:(float)y { + self = [super init]; + if (self) { + self.x = x; + self.y = y; + } + return self; +} + +- (instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + ++ (instancetype)fromNative:(cv::Point2f&)point { + return [[Point2f alloc] initWithX:point.x y:point.y]; +} + +- (void)update:(cv::Point2f&)point { + self.x = point.x; + self.y = point.y; +} + +- (Point2f*) clone { + return [[Point2f alloc] initWithX:self.x y:self.y]; +} + +- (double)dot:(Point2f*)point { + return self.x * point.x + self.y * point.y; +} + +- (void)set:(NSArray*)vals { + self.x = (vals != nil && vals.count > 0) ? vals[0].doubleValue : 0; + self.y = (vals != nil && vals.count > 1) ? vals[1].doubleValue : 0; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Point2f class]]) { + return NO; + } else { + Point2f* point = (Point2f*)other; + return self.x == point.x && self.y == point.y; + } +} + +- (BOOL)inside:(Rect2f *)rect { + return [rect contains:self]; +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + result = prime * result + FLOAT_TO_BITS(self.x); + result = prime * result + FLOAT_TO_BITS(self.x); + return result; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Point2f {%f,%f}", self.x, self.y]; +} + +@end diff --git a/modules/core/misc/objc/common/Point2i.h b/modules/core/misc/objc/common/Point2i.h new file mode 100644 index 0000000000..ac545d4ecc --- /dev/null +++ b/modules/core/misc/objc/common/Point2i.h @@ -0,0 +1,88 @@ +// +// Point2i.h +// +// Created by Giles Payne on 2019/10/09. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +@class Rect2i; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Represents a two dimensional point the coordinate values of which are of type `int` +*/ +NS_SWIFT_NAME(Point) +@interface Point2i : NSObject + +# pragma mark - Properties + +@property int x; +@property int y; +#ifdef __cplusplus +@property(readonly) cv::Point2i& nativeRef; +#endif + +# pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithX:(int)x y:(int)y; +- (instancetype)initWithVals:(NSArray*)vals; + +#ifdef __cplusplus ++ (instancetype)fromNative:(cv::Point2i&)point; +- (void)update:(cv::Point2i&)point; +#endif + +# pragma mark - Methods + +/** +* Calculate the dot product of this point and another point +* @param point The other point +*/ +- (double)dot:(Point2i*)point; + +/** +* Determine if the point lies with a specified rectangle +* @param rect The rectangle +*/ +- (BOOL)inside:(Rect2i*)rect; + +/** +* Set the point coordinates from the values of an array +* @param vals The array of values from which to set the coordinates +*/ +- (void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +# pragma mark - Common Methods + +/** +* Clone object +*/ +- (Point2i*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)other; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString*)description; +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Point2i.mm b/modules/core/misc/objc/common/Point2i.mm new file mode 100644 index 0000000000..bfbbc15e3a --- /dev/null +++ b/modules/core/misc/objc/common/Point2i.mm @@ -0,0 +1,105 @@ +// +// Point2i.m +// +// Created by Giles Payne on 2019/10/09. +// + +#import "Point2i.h" +#import "Rect2i.h" +#import "CVObjcUtil.h" + +@implementation Point2i { + cv::Point2i native; +} + +- (int)x { + return native.x; +} + +- (void)setX:(int)val { + native.x = val; +} + +- (int)y { + return native.y; +} + +- (void)setY:(int)val { + native.y = val; +} + +- (cv::Point2i&)nativeRef { + return native; +} + +- (instancetype)init { + return [self initWithX:0 y:0]; +} + +- (instancetype)initWithX:(int)x y:(int)y { + self = [super init]; + if (self) { + self.x = x; + self.y = y; + } + return self; +} + +- (instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + ++ (instancetype)fromNative:(cv::Point2i&)point { + return [[Point2i alloc] initWithX:point.x y:point.y]; +} + +- (void)update:(cv::Point2i&)point { + self.x = point.x; + self.y = point.y; +} + +- (Point2i*) clone { + return [[Point2i alloc] initWithX:self.x y:self.y]; +} + +- (double)dot:(Point2i*)point { + return self.x * point.x + self.y * point.y; +} + +- (void)set:(NSArray*)vals { + self.x = (vals != nil && vals.count > 0) ? vals[0].doubleValue : 0; + self.y = (vals != nil && vals.count > 1) ? vals[1].doubleValue : 0; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Point2i class]]) { + return NO; + } else { + Point2i* point = (Point2i*)other; + return self.x == point.x && self.y == point.y; + } +} + +- (BOOL)inside:(Rect2i*)rect { + return [rect contains:self]; +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + result = prime * result + self.x; + result = prime * result + self.y; + return result; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Point2i {%d,%d}", self.x, self.y]; +} + +@end diff --git a/modules/core/misc/objc/common/Point3d.h b/modules/core/misc/objc/common/Point3d.h new file mode 100644 index 0000000000..271fbb1bd0 --- /dev/null +++ b/modules/core/misc/objc/common/Point3d.h @@ -0,0 +1,84 @@ +// +// Point3d.h +// +// Created by Giles Payne on 2019/10/09. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +@class Point2d; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Represents a three dimensional point the coordinate values of which are of type `double` +*/ +@interface Point3d : NSObject + +# pragma mark - Properties + +@property double x; +@property double y; +@property double z; +#ifdef __cplusplus +@property(readonly) cv::Point3d& nativeRef; +#endif + +# pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithX:(double)x y:(double)y z:(double)z; +- (instancetype)initWithPoint:(Point2d*)point; +- (instancetype)initWithVals:(NSArray*)vals; + +# pragma mark - Methods + +/** +* Calculate the dot product of this point and another point +* @param point The other point +*/ +- (double)dot:(Point3d*)point; + +/** +* Calculate the cross product of this point and another point +* @param point The other point +*/ +- (Point3d*)cross:(Point3d*)point; + +/** +* Set the point coordinates from the values of an array +* @param vals The array of values from which to set the coordinates +*/ +- (void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +# pragma mark - Common Methods + +/** +* Clone object +*/ +- (Point3d*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)other; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString *)description; +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Point3d.mm b/modules/core/misc/objc/common/Point3d.mm new file mode 100644 index 0000000000..a4770ddec4 --- /dev/null +++ b/modules/core/misc/objc/common/Point3d.mm @@ -0,0 +1,114 @@ +// +// Point3d.mm +// +// Created by Giles Payne on 2019/10/09. +// + +#import "Point3d.h" +#import "Point2d.h" +#import "CVObjcUtil.h" + +@implementation Point3d { + cv::Point3d native; +} + +- (double)x { + return native.x; +} + +- (void)setX:(double)val { + native.x = val; +} + +- (double)y { + return native.y; +} + +- (void)setY:(double)val { + native.y = val; +} + +- (double)z { + return native.z; +} + +- (void)setZ:(double)val { + native.z = val; +} + +- (cv::Point3d&)nativeRef { + return native; +} + +- (instancetype)init { + return [self initWithX:0 y:0 z:0]; +} + +- (instancetype)initWithX:(double)x y:(double)y z:(double)z { + self = [super init]; + if (self) { + self.x = x; + self.y = y; + self.z = z; + } + return self; +} + +- (instancetype)initWithPoint:(Point2d*)point { + return [self initWithX:point.x y:point.y z:0]; +} + +- (instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + +- (void)set:(NSArray*)vals { + self.x = (vals != nil && vals.count > 0) ? vals[0].doubleValue : 0.0; + self.y = (vals != nil && vals.count > 1) ? vals[1].doubleValue : 0.0; + self.z = (vals != nil && vals.count > 2) ? vals[2].doubleValue : 0.0; +} + +- (Point3d*) clone { + return [[Point3d alloc] initWithX:self.x y:self.y z:self.z]; +} + +- (double)dot:(Point3d*)point { + return self.x * point.x + self.y * point.y + self.z * point.z; +} + +- (Point3d*)cross:(Point3d*)point { + return [[Point3d alloc] initWithX:(self.y * point.z - self.z * point.y) y:(self.z * point.x - self.x * point.z) z:(self.x * point.y - self.y * point.x)]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Point3d class]]) { + return NO; + } else { + Point3d* point = (Point3d*)other; + return self.x == point.x && self.y == point.y && self.z == point.z; + } +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + int64_t temp = DOUBLE_TO_BITS(self.x); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + temp = DOUBLE_TO_BITS(self.y); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + temp = DOUBLE_TO_BITS(self.z); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + return result; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Point3 {%lf,%lf,%lf}", self.x, self.y, self.z]; +} + +@end diff --git a/modules/core/misc/objc/common/Point3f.h b/modules/core/misc/objc/common/Point3f.h new file mode 100644 index 0000000000..8f02aaeb4f --- /dev/null +++ b/modules/core/misc/objc/common/Point3f.h @@ -0,0 +1,85 @@ +// +// Point3f.h +// +// Created by Giles Payne on 2019/10/09. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +@class Point2f; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Represents a three dimensional point the coordinate values of which are of type `float` +*/ +@interface Point3f : NSObject + +# pragma mark - Properties + +@property float x; +@property float y; +@property float z; +#ifdef __cplusplus +@property(readonly) cv::Point3f& nativeRef; +#endif + +# pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithX:(float)x y:(float)y z:(float)z; +- (instancetype)initWithPoint:(Point2f*)point; +- (instancetype)initWithVals:(NSArray*)vals; + + +# pragma mark - Methods + +/** +* Calculate the dot product of this point and another point +* @param point The other point +*/ +- (double)dot:(Point3f*)point; + +/** +* Calculate the cross product of this point and another point +* @param point The other point +*/ +- (Point3f*)cross:(Point3f*)point; + +/** +* Set the point coordinates from the values of an array +* @param vals The array of values from which to set the coordinates +*/ +- (void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +# pragma mark - Common Methods + +/** +* Clone object +*/ +- (Point3f*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)other; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString *)description; +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Point3f.mm b/modules/core/misc/objc/common/Point3f.mm new file mode 100644 index 0000000000..bd03230f4b --- /dev/null +++ b/modules/core/misc/objc/common/Point3f.mm @@ -0,0 +1,111 @@ +// +// Point3f.mm +// +// Created by Giles Payne on 2019/10/09. +// + +#import "Point3f.h" +#import "Point2f.h" +#import "CVObjcUtil.h" + +@implementation Point3f { + cv::Point3f native; +} + +- (float)x { + return native.x; +} + +- (void)setX:(float)val { + native.x = val; +} + +- (float)y { + return native.y; +} + +- (void)setY:(float)val { + native.y = val; +} + +- (float)z { + return native.z; +} + +- (void)setZ:(float)val { + native.z = val; +} + +- (cv::Point3f&)nativeRef { + return native; +} + +- (instancetype)init { + return [self initWithX:0 y:0 z:0]; +} + +- (instancetype)initWithX:(float)x y:(float)y z:(float)z { + self = [super init]; + if (self) { + self.x = x; + self.y = y; + self.z = z; + } + return self; +} + +- (instancetype)initWithPoint:(Point2f*)point { + return [self initWithX:point.x y:point.y z:0]; +} + +- (instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + +- (void)set:(NSArray*)vals { + self.x = (vals != nil && vals.count > 0) ? vals[0].floatValue : 0.0; + self.y = (vals != nil && vals.count > 1) ? vals[1].floatValue : 0.0; + self.z = (vals != nil && vals.count > 2) ? vals[2].floatValue : 0.0; +} + +- (Point3f*) clone { + return [[Point3f alloc] initWithX:self.x y:self.y z:self.z]; +} + +- (double)dot:(Point3f*)point { + return self.x * point.x + self.y * point.y + self.z * point.z; +} + +- (Point3f*)cross:(Point3f*)point { + return [[Point3f alloc] initWithX:(self.y * point.z - self.z * point.y) y:(self.z * point.x - self.x * point.z) z:(self.x * point.y - self.y * point.x)]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Point3f class]]) { + return NO; + } else { + Point3f* point = (Point3f*)other; + return self.x == point.x && self.y == point.y && self.z == point.z; + } +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + result = prime * result + FLOAT_TO_BITS(self.x); + result = prime * result + FLOAT_TO_BITS(self.y); + result = prime * result + FLOAT_TO_BITS(self.z); + return result; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Point3f {%f,%f,%f}", self.x, self.y, self.z]; +} + +@end diff --git a/modules/core/misc/objc/common/Point3i.h b/modules/core/misc/objc/common/Point3i.h new file mode 100644 index 0000000000..5323cb8029 --- /dev/null +++ b/modules/core/misc/objc/common/Point3i.h @@ -0,0 +1,84 @@ +// +// Point3i.h +// +// Created by Giles Payne on 2019/10/09. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +@class Point2i; + +NS_ASSUME_NONNULL_BEGIN + +/** +* Represents a three dimensional point the coordinate values of which are of type `int` +*/ +@interface Point3i : NSObject + +# pragma mark - Properties + +@property int x; +@property int y; +@property int z; +#ifdef __cplusplus +@property(readonly) cv::Point3i& nativeRef; +#endif + +# pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithX:(int)x y:(int)y z:(int)z; +- (instancetype)initWithPoint:(Point2i*)point; +- (instancetype)initWithVals:(NSArray*)vals; + +# pragma mark - Methods + +/** +* Calculate the dot product of this point and another point +* @param point The other point +*/ +- (double)dot:(Point3i*)point; + +/** +* Calculate the cross product of this point and another point +* @param point The other point +*/ +- (Point3i*)cross:(Point3i*)point; + +/** +* Set the point coordinates from the values of an array +* @param vals The array of values from which to set the coordinates +*/ +- (void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +# pragma mark - Common Methods + +/** +* Clone object +*/ +- (Point3i*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)other; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString *)description; +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Point3i.mm b/modules/core/misc/objc/common/Point3i.mm new file mode 100644 index 0000000000..8218f2b3c5 --- /dev/null +++ b/modules/core/misc/objc/common/Point3i.mm @@ -0,0 +1,111 @@ +// +// Point3i.mm +// +// Created by Giles Payne on 2019/10/09. +// + +#import "Point3i.h" +#import "Point2i.h" +#import "CVObjcUtil.h" + +@implementation Point3i { + cv::Point3i native; +} + +- (int)x { + return native.x; +} + +- (void)setX:(int)val { + native.x = val; +} + +- (int)y { + return native.y; +} + +- (void)setY:(int)val { + native.y = val; +} + +- (int)z { + return native.z; +} + +- (void)setZ:(int)val { + native.z = val; +} + +- (cv::Point3i&)nativeRef { + return native; +} + +- (instancetype)init { + return [self initWithX:0 y:0 z:0]; +} + +- (instancetype)initWithX:(int)x y:(int)y z:(int)z { + self = [super init]; + if (self) { + self.x = x; + self.y = y; + self.z = z; + } + return self; +} + +- (instancetype)initWithPoint:(Point2i*)point { + return [self initWithX:point.x y:point.y z:0]; +} + +- (instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + +- (void)set:(NSArray*)vals { + self.x = (vals != nil && vals.count > 0) ? vals[0].intValue : 0; + self.y = (vals != nil && vals.count > 1) ? vals[1].intValue : 0; + self.z = (vals != nil && vals.count > 2) ? vals[2].intValue : 0; +} + +- (Point3i*) clone { + return [[Point3i alloc] initWithX:self.x y:self.y z:self.z]; +} + +- (double)dot:(Point3i*)point { + return self.x * point.x + self.y * point.y + self.z * point.z; +} + +- (Point3i*)cross:(Point3i*)point { + return [[Point3i alloc] initWithX:(self.y * point.z - self.z * point.y) y:(self.z * point.x - self.x * point.z) z:(self.x * point.y - self.y * point.x)]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Point3i class]]) { + return NO; + } else { + Point3i* point = (Point3i*)other; + return self.x == point.x && self.y == point.y && self.z == point.z; + } +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + result = prime * result + self.x; + result = prime * result + self.y; + result = prime * result + self.z; + return result; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Point3i {%d,%d,%d}", self.x, self.y, self.z]; +} + +@end diff --git a/modules/core/misc/objc/common/Range.h b/modules/core/misc/objc/common/Range.h new file mode 100644 index 0000000000..ba0aa549e3 --- /dev/null +++ b/modules/core/misc/objc/common/Range.h @@ -0,0 +1,93 @@ +// +// Range.h +// +// Created by Giles Payne on 2019/10/08. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** +* Represents a range of dimension indices +*/ +@interface Range : NSObject + +#pragma mark - Properties + +@property int start; +@property int end; + +#pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithStart:(int)start end:(int)end; +- (instancetype)initWithVals:(NSArray*)vals; + +#pragma mark - Methods + +/** +* The size of the range +*/ +- (int)size; + +/** +* Determines if the range is empty +*/ +- (BOOL)empty; + +/** +* Creates a range representing all possible indices for a particular dimension +*/ ++ (Range*)all; + +/** +* Calculates the intersection of the range with another range +* @param r1 The other range +*/ +- (Range*)intersection:(Range*)r1; + +/** +* Adjusts each of the range limts +* @param delta The amount of the adjustment +*/ +- (Range*)shift:(int)delta; + +/** +* Set the range limits from the values of an array +* @param vals The array of values from which to set the range limits +*/ +- (void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +# pragma mark - Common Methods + +/** +* Clone object +*/ +- (Range*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)object; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString*)description; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Range.m b/modules/core/misc/objc/common/Range.m new file mode 100644 index 0000000000..a4e155214e --- /dev/null +++ b/modules/core/misc/objc/common/Range.m @@ -0,0 +1,86 @@ +// +// Range.m +// +// Created by Giles Payne on 2019/10/08. +// + +#import "Range.h" + +@implementation Range + +- (instancetype)init { + return [self initWithStart:0 end: 0]; +} + +- (instancetype)initWithStart:(int)start end:(int)end { + self = [super init]; + if (self != nil) { + self.start = start; + self.end = end; + } + return self; +} + +- (instancetype)initWithVals:(NSArray*)vals { + self = [self init]; + if (self != nil) { + [self set:vals]; + } + return self; +} + +- (void)set:(NSArray*)vals { + self.start = (vals != nil && vals.count > 0) ? vals[0].intValue : 0; + self.end = (vals != nil && vals.count > 1 ) ? vals[1].intValue : 0; +} + +- (int)size { + return [self empty] ? 0 : self.end - self.start; +} + +- (BOOL)empty { + return self.end <= self.start; +} + ++ (Range*)all { + return [[Range alloc] initWithStart:INT_MIN end:INT_MAX]; +} + +- (Range*)intersection:(Range*)r1 { + Range* out = [[Range alloc] initWithStart:MAX(r1.start, self.start) end:MIN(r1.end, self.end)]; + out.end = MAX(out.end, out.start); + return out; +} + +- (Range*)shift:(int)delta { + return [[Range alloc] initWithStart:self.start + delta end:self.end + delta]; +} + +- (Range*)clone { + return [[Range alloc] initWithStart:self.start end:self.end]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Range class]]) { + return NO; + } else { + Range* it = (Range*)other; + return self.start == it.start && self.end == it.end; + } +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + result = prime * result + self.start; + result = prime * result + self.end; + return result; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Range {%d, %d}", self.start, self.end]; +} + +@end diff --git a/modules/core/misc/objc/common/Rect2d.h b/modules/core/misc/objc/common/Rect2d.h new file mode 100644 index 0000000000..c1d944247e --- /dev/null +++ b/modules/core/misc/objc/common/Rect2d.h @@ -0,0 +1,111 @@ +// +// Rect.h +// +// Created by Giles Payne on 2019/10/09. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +@class Point2d; +@class Size2d; + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** +* Represents a rectange the coordinate and dimension values of which are of type `double` +*/ +@interface Rect2d : NSObject + +#pragma mark - Properties + +@property double x; +@property double y; +@property double width; +@property double height; +#ifdef __cplusplus +@property(readonly) cv::Rect2d& nativeRef; +#endif + +#pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithX:(double)x y:(double)y width:(double)width height:(double)height; +- (instancetype)initWithPoint:(Point2d*)point1 point:(Point2d*)point2; +- (instancetype)initWithPoint:(Point2d*)point size:(Size2d*)size; +- (instancetype)initWithVals:(NSArray*)vals; + +#ifdef __cplusplus ++ (instancetype)fromNative:(cv::Rect2d&)point; +#endif + +#pragma mark - Methods + +/** +* Returns the top left coordinate of the rectangle +*/ +- (Point2d*)tl; + +/** +* Returns the bottom right coordinate of the rectangle +*/ +- (Point2d*)br; + +/** +* Returns the size of the rectangle +*/ +- (Size2d*)size; + +/** +* Returns the area of the rectangle +*/ +- (double)area; + +/** +* Determines if the rectangle is empty +*/ +- (BOOL)empty; + +/** +* Determines if the rectangle contains a given point +* @param point The point +*/ +- (BOOL)contains:(Point2d*)point; + +/** +* Set the rectangle coordinates and dimensions from the values of an array +* @param vals The array of values from which to set the rectangle coordinates and dimensions +*/ +- (void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +#pragma mark - Common Methods + +/** +* Clone object +*/ +- (Rect2d*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)object; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString*)description; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Rect2d.mm b/modules/core/misc/objc/common/Rect2d.mm new file mode 100644 index 0000000000..20bd830ba5 --- /dev/null +++ b/modules/core/misc/objc/common/Rect2d.mm @@ -0,0 +1,155 @@ +// +// Rect2d.mm +// +// Created by Giles Payne on 2019/10/09. +// + +#import "Rect2d.h" +#import "Point2d.h" +#import "Size2d.h" +#import "CVObjcUtil.h" + +@implementation Rect2d { + cv::Rect2d native; +} + +- (double)x { + return native.x; +} + +- (void)setX:(double)val { + native.x = val; +} + +- (double)y { + return native.y; +} + +- (void)setY:(double)val { + native.y = val; +} + +- (double)width { + return native.width; +} + +- (void)setWidth:(double)val { + native.width = val; +} + +- (double)height { + return native.height; +} + +- (void)setHeight:(double)val { + native.height = val; +} + +- (cv::Rect2d&)nativeRef { + return native; +} + +- (instancetype)initWithX:(double)x y:(double)y width:(double)width height:(double)height { + self = [super init]; + if (self) { + self.x = x; + self.y = y; + self.width = width; + self.height = height; + } + return self; +} + +- (instancetype)init { + return [self initWithX:0 y:0 width:0 height:0]; +} + +- (instancetype)initWithPoint:(Point2d*)point1 point:(Point2d*)point2 { + int x = (point1.x < point2.x ? point1.x : point2.x); + int y = (point1.y < point2.y ? point1.y : point2.y); + int width = (point1.x > point2.x ? point1.x : point2.x) - x; + int height = (point1.y > point2.y ? point1.y : point2.y) - y; + return [self initWithX:x y:y width:width height:height]; +} + +- (instancetype)initWithPoint:(Point2d*)point size:(Size2d*)size { + return [self initWithX:point.x y:point.y width:size.width height:size.height]; +} + +- (instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + ++ (instancetype)fromNative:(cv::Rect2d&)rect { + return [[Rect2d alloc] initWithX:rect.x y:rect.y width:rect.width height:rect.height]; +} + +- (Rect2d*)clone { + return [[Rect2d alloc] initWithX:self.x y:self.y width:self.width height:self.height]; +} + +- (Point2d*)tl { + return [[Point2d alloc] initWithX:self.x y:self.y]; +} + +- (Point2d*)br { + return [[Point2d alloc] initWithX:self.x + self.width y:self.y + self.height]; +} + +- (Size2d*)size { + return [[Size2d alloc] initWithWidth:self.width height:self.height]; +} + +- (double)area { + return self.width * self.height; +} + +- (BOOL)empty { + return self.width <= 0 || self.height <= 0; +} + +- (BOOL)contains:(Point2d*)point { + return self.x <= point.x && point.x < self.x + self.width && self.y <= point.y && point.y < self.y + self.height; +} + +- (void)set:(NSArray*)vals { + self.x = (vals != nil && vals.count > 0) ? vals[0].intValue : 0; + self.y = (vals != nil && vals.count > 1) ? vals[1].intValue : 0; + self.width = (vals != nil && vals.count > 2) ? vals[2].intValue : 0; + self.height = (vals != nil && vals.count > 3) ? vals[3].intValue : 0; +} + +- (BOOL)isEqual:(id)other{ + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Rect2d class]]) { + return NO; + } else { + Rect2d* rect = (Rect2d*)other; + return self.x == rect.x && self.y == rect.y && self.width == rect.width && self.height == rect.height; + } +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + int64_t temp = DOUBLE_TO_BITS(self.x); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + temp = DOUBLE_TO_BITS(self.y); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + temp = DOUBLE_TO_BITS(self.width); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + temp = DOUBLE_TO_BITS(self.height); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + return result; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Rect2d {%lf,%lf,%lf,%lf}", self.x, self.y, self.width, self.height]; +} + +@end diff --git a/modules/core/misc/objc/common/Rect2f.h b/modules/core/misc/objc/common/Rect2f.h new file mode 100644 index 0000000000..9ed5f40981 --- /dev/null +++ b/modules/core/misc/objc/common/Rect2f.h @@ -0,0 +1,111 @@ +// +// Rect.h +// +// Created by Giles Payne on 2019/10/09. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +@class Point2f; +@class Size2f; + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** +* Represents a rectange the coordinate and dimension values of which are of type `float` +*/ +@interface Rect2f : NSObject + +#pragma mark - Properties + +@property float x; +@property float y; +@property float width; +@property float height; +#ifdef __cplusplus +@property(readonly) cv::Rect2f& nativeRef; +#endif + +#pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithX:(float)x y:(float)y width:(float)width height:(float)height; +- (instancetype)initWithPoint:(Point2f*)point1 point:(Point2f*)point2; +- (instancetype)initWithPoint:(Point2f*)point size:(Size2f*)size; +- (instancetype)initWithVals:(NSArray*)vals; + +#ifdef __cplusplus ++ (instancetype)fromNative:(cv::Rect2f&)point; +#endif + +#pragma mark - Methods + +/** +* Returns the top left coordinate of the rectangle +*/ +- (Point2f*)tl; + +/** +* Returns the bottom right coordinate of the rectangle +*/ +- (Point2f*)br; + +/** +* Returns the size of the rectangle +*/ +- (Size2f*)size; + +/** +* Returns the area of the rectangle +*/ +- (double)area; + +/** +* Determines if the rectangle is empty +*/ +- (BOOL)empty; + +/** +* Determines if the rectangle contains a given point +* @param point The point +*/ +- (BOOL)contains:(Point2f*)point; + +/** +* Set the rectangle coordinates and dimensions from the values of an array +* @param vals The array of values from which to set the rectangle coordinates and dimensions +*/ +- (void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +#pragma mark - Common Methods + +/** +* Clone object +*/ +- (Rect2f*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)object; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString*)description; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Rect2f.mm b/modules/core/misc/objc/common/Rect2f.mm new file mode 100644 index 0000000000..6f8b7c6fc4 --- /dev/null +++ b/modules/core/misc/objc/common/Rect2f.mm @@ -0,0 +1,151 @@ +// +// Rect2d.mm +// +// Created by Giles Payne on 2019/10/09. +// + +#import "Rect2f.h" +#import "Point2f.h" +#import "Size2f.h" +#import "CVObjcUtil.h" + +@implementation Rect2f { + cv::Rect2f native; +} + +- (float)x { + return native.x; +} + +- (void)setX:(float)val { + native.x = val; +} + +- (float)y { + return native.y; +} + +- (void)setY:(float)val { + native.y = val; +} + +- (float)width { + return native.width; +} + +- (void)setWidth:(float)val { + native.width = val; +} + +- (float)height { + return native.height; +} + +- (void)setHeight:(float)val { + native.height = val; +} + +- (cv::Rect2f&)nativeRef { + return native; +} + +- (instancetype)initWithX:(float)x y:(float)y width:(float)width height:(float)height { + self = [super init]; + if (self) { + self.x = x; + self.y = y; + self.width = width; + self.height = height; + } + return self; +} + +- (instancetype)init { + return [self initWithX:0 y:0 width:0 height:0]; +} + +- (instancetype)initWithPoint:(Point2f*)point1 point:(Point2f*)point2 { + int x = (point1.x < point2.x ? point1.x : point2.x); + int y = (point1.y < point2.y ? point1.y : point2.y); + int width = (point1.x > point2.x ? point1.x : point2.x) - x; + int height = (point1.y > point2.y ? point1.y : point2.y) - y; + return [self initWithX:x y:y width:width height:height]; +} + +- (instancetype)initWithPoint:(Point2f*)point size:(Size2f*)size { + return [self initWithX:point.x y:point.y width:size.width height:size.height]; +} + +- (instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + ++ (instancetype)fromNative:(cv::Rect2f&)rect { + return [[Rect2f alloc] initWithX:rect.x y:rect.y width:rect.width height:rect.height]; +} + +- (Rect2f*)clone { + return [[Rect2f alloc] initWithX:self.x y:self.y width:self.width height:self.height]; +} + +- (Point2f*)tl { + return [[Point2f alloc] initWithX:self.x y:self.y]; +} + +- (Point2f*)br { + return [[Point2f alloc] initWithX:self.x + self.width y:self.y + self.height]; +} + +- (Size2f*)size { + return [[Size2f alloc] initWithWidth:self.width height:self.height]; +} + +- (double)area { + return self.width * self.height; +} + +- (BOOL)empty { + return self.width <= 0 || self.height <= 0; +} + +- (BOOL)contains:(Point2f*)point { + return self.x <= point.x && point.x < self.x + self.width && self.y <= point.y && point.y < self.y + self.height; +} + +- (void)set:(NSArray*)vals { + self.x = (vals != nil && vals.count > 0) ? vals[0].floatValue : 0; + self.y = (vals != nil && vals.count > 1) ? vals[1].floatValue : 0; + self.width = (vals != nil && vals.count > 2) ? vals[2].floatValue : 0; + self.height = (vals != nil && vals.count > 3) ? vals[3].floatValue : 0; +} + +- (BOOL)isEqual:(id)other{ + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Rect2f class]]) { + return NO; + } else { + Rect2f* rect = (Rect2f*)other; + return self.x == rect.x && self.y == rect.y && self.width == rect.width && self.height == rect.height; + } +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + result = prime * result + FLOAT_TO_BITS(self.x); + result = prime * result + FLOAT_TO_BITS(self.y); + result = prime * result + FLOAT_TO_BITS(self.width); + result = prime * result + FLOAT_TO_BITS(self.height); + return result; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Rect2f {%lf,%lf,%lf,%lf}", self.x, self.y, self.width, self.height]; +} + +@end diff --git a/modules/core/misc/objc/common/Rect2i.h b/modules/core/misc/objc/common/Rect2i.h new file mode 100644 index 0000000000..a94a547226 --- /dev/null +++ b/modules/core/misc/objc/common/Rect2i.h @@ -0,0 +1,112 @@ +// +// Rect2i.h +// +// Created by Giles Payne on 2019/10/09. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +@class Point2i; +@class Size2i; + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** +* Represents a rectange the coordinate and dimension values of which are of type `int` +*/ +NS_SWIFT_NAME(Rect) +@interface Rect2i : NSObject + +#pragma mark - Properties + +@property int x; +@property int y; +@property int width; +@property int height; +#ifdef __cplusplus +@property(readonly) cv::Rect2i& nativeRef; +#endif + +#pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithX:(int)x y:(int)y width:(int)width height:(int)height; +- (instancetype)initWithPoint:(Point2i*)point1 point:(Point2i*)point2; +- (instancetype)initWithPoint:(Point2i*)point size:(Size2i*)size; +- (instancetype)initWithVals:(NSArray*)vals; + +#ifdef __cplusplus ++ (instancetype)fromNative:(cv::Rect&)point; +#endif + +#pragma mark - Methods + +/** +* Returns the top left coordinate of the rectangle +*/ +- (Point2i*)tl; + +/** +* Returns the bottom right coordinate of the rectangle +*/ +- (Point2i*)br; + +/** +* Returns the size of the rectangle +*/ +- (Size2i*)size; + +/** +* Returns the area of the rectangle +*/ +- (double)area; + +/** +* Determines if the rectangle is empty +*/ +- (BOOL)empty; + +/** +* Determines if the rectangle contains a given point +* @param point The point +*/ +- (BOOL)contains:(Point2i*)point; + +/** +* Set the rectangle coordinates and dimensions from the values of an array +* @param vals The array of values from which to set the rectangle coordinates and dimensions +*/ +- (void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +#pragma mark - Common Methods + +/** +* Clone object +*/ +- (Rect2i*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)object; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString*)description; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Rect2i.mm b/modules/core/misc/objc/common/Rect2i.mm new file mode 100644 index 0000000000..75bbd31664 --- /dev/null +++ b/modules/core/misc/objc/common/Rect2i.mm @@ -0,0 +1,150 @@ +// +// Rect2i.m +// +// Created by Giles Payne on 2019/10/09. +// + +#import "Rect2i.h" +#import "Point2i.h" +#import "Size2i.h" + +@implementation Rect2i { + cv::Rect2i native; +} + +- (int)x { + return native.x; +} + +- (void)setX:(int)val { + native.x = val; +} + +- (int)y { + return native.y; +} + +- (void)setY:(int)val { + native.y = val; +} + +- (int)width { + return native.width; +} + +- (void)setWidth:(int)val { + native.width = val; +} + +- (int)height { + return native.height; +} + +- (void)setHeight:(int)val { + native.height = val; +} + +- (cv::Rect&)nativeRef { + return native; +} + +- (instancetype)initWithX:(int)x y:(int)y width:(int)width height:(int)height { + self = [super init]; + if (self) { + self.x = x; + self.y = y; + self.width = width; + self.height = height; + } + return self; +} + +- (instancetype)init { + return [self initWithX:0 y:0 width:0 height:0]; +} + +- (instancetype)initWithPoint:(Point2i*)point1 point:(Point2i*)point2 { + int x = (point1.x < point2.x ? point1.x : point2.x); + int y = (point1.y < point2.y ? point1.y : point2.y); + int width = (point1.x > point2.x ? point1.x : point2.x) - x; + int height = (point1.y > point2.y ? point1.y : point2.y) - y; + return [self initWithX:x y:y width:width height:height]; +} + +- (instancetype)initWithPoint:(Point2i*)point size:(Size2i*)size { + return [self initWithX:point.x y:point.y width:size.width height:size.height]; +} + +- (instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + ++ (instancetype)fromNative:(cv::Rect&)rect { + return [[Rect2i alloc] initWithX:rect.x y:rect.y width:rect.width height:rect.height]; +} + +- (Rect2i*)clone { + return [[Rect2i alloc] initWithX:self.x y:self.y width:self.width height:self.height]; +} + +- (Point2i*)tl { + return [[Point2i alloc] initWithX:self.x y:self.y]; +} + +- (Point2i*)br { + return [[Point2i alloc] initWithX:self.x + self.width y:self.y + self.height]; +} + +- (Size2i*)size { + return [[Size2i alloc] initWithWidth:self.width height:self.height]; +} + +- (double)area { + return self.width * self.height; +} + +- (BOOL)empty { + return self.width <= 0 || self.height <= 0; +} + +- (BOOL)contains:(Point2i*)point { + return self.x <= point.x && point.x < self.x + self.width && self.y <= point.y && point.y < self.y + self.height; +} + +- (void)set:(NSArray*)vals { + self.x = (vals != nil && vals.count > 0) ? vals[0].intValue : 0; + self.y = (vals != nil && vals.count > 1) ? vals[1].intValue : 0; + self.width = (vals != nil && vals.count > 2) ? vals[2].intValue : 0; + self.height = (vals != nil && vals.count > 3) ? vals[3].intValue : 0; +} + +- (BOOL)isEqual:(id)other{ + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Rect2i class]]) { + return NO; + } else { + Rect2i* rect = (Rect2i*)other; + return self.x == rect.x && self.y == rect.y && self.width == rect.width && self.height == rect.height; + } +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + result = prime * result + self.x; + result = prime * result + self.y; + result = prime * result + self.width; + result = prime * result + self.height; + return result; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Rect2i {%d,%d,%d,%d}", self.x, self.y, self.width, self.height]; +} + +@end diff --git a/modules/core/misc/objc/common/RotatedRect.h b/modules/core/misc/objc/common/RotatedRect.h new file mode 100644 index 0000000000..5571e739ee --- /dev/null +++ b/modules/core/misc/objc/common/RotatedRect.h @@ -0,0 +1,86 @@ +// +// RotatedRect.h +// +// Created by Giles Payne on 2019/12/26. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +@class Point2f; +@class Size2f; +@class Rect2f; + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** +* Represents a rotated rectangle on a plane +*/ +@interface RotatedRect : NSObject + +#pragma mark - Properties + +@property Point2f* center; +@property Size2f* size; +@property double angle; +#ifdef __cplusplus +@property(readonly) cv::RotatedRect& nativeRef; +#endif + +#pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithCenter:(Point2f*)center size:(Size2f*)size angle:(double)angle; +- (instancetype)initWithVals:(NSArray*)vals; +#ifdef __cplusplus ++ (instancetype)fromNative:(cv::RotatedRect&)rotatedRect; +#endif + +#pragma mark - Methods +/** +* Returns the corner points of the rotated rectangle as an array +*/ +- (NSArray*)points; + +/** +* Returns the bounding (non-rotated) rectangle of the rotated rectangle +*/ +- (Rect2f*)boundingRect; + +/** +* Set the rotated rectangle coordinates, dimensions and angle of rotation from the values of an array +* @param vals The array of values from which to set the rotated rectangle coordinates, dimensions and angle of rotation +*/ +- (void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +#pragma mark - Common Methods + +/** +* Clone object +*/ +- (RotatedRect*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)object; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString*)description; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/RotatedRect.mm b/modules/core/misc/objc/common/RotatedRect.mm new file mode 100644 index 0000000000..e35002e1b3 --- /dev/null +++ b/modules/core/misc/objc/common/RotatedRect.mm @@ -0,0 +1,113 @@ +// +// RotatedRect.m +// +// Created by Giles Payne on 2019/12/26. +// + +#import "RotatedRect.h" +#import "Point2f.h" +#import "Size2f.h" +#import "Rect2f.h" +#import "CVObjcUtil.h" + +#include + +@implementation RotatedRect { + cv::RotatedRect native; +} + +- (cv::RotatedRect&)nativeRef { + native.center.x = self.center.x; + native.center.y = self.center.y; + native.size.width = self.size.width; + native.size.height = self.size.height; + native.angle = self.angle; + return native; +} + +- (instancetype)init { + return [self initWithCenter:[Point2f new] size:[Size2f new] angle:0.0]; +} + +- (instancetype)initWithCenter:(Point2f*)center size:(Size2f*)size angle:(double)angle { + self = [super init]; + if (self) { + self.center = center; + self.size = size; + self.angle = angle; + } + return self; +} + +- (instancetype)initWithVals:(NSArray*)vals { + self = [self init]; + if (self) { + [self set:vals]; + } + return self; +} + ++ (instancetype)fromNative:(cv::RotatedRect&)rotatedRect { + return [[RotatedRect alloc] initWithCenter:[Point2f fromNative:rotatedRect.center] size:[Size2f fromNative:rotatedRect.size] angle:rotatedRect.angle]; +} + +- (void)set:(NSArray*)vals { + self.center.x = (vals != nil && vals.count > 0) ? vals[0].floatValue : 0.0; + self.center.y = (vals != nil && vals.count > 1) ? vals[1].floatValue : 0.0; + self.size.width = (vals != nil && vals.count > 2) ? vals[2].floatValue : 0.0; + self.size.height = (vals != nil && vals.count > 3) ? vals[3].floatValue : 0.0; + self.angle = (vals != nil && vals.count > 4) ? vals[4].doubleValue : 0.0; +} + +- (NSArray*)points { + double angleRadians = self.angle * M_PI / 180.0; + double b = cos(angleRadians) * 0.5; + double a = sin(angleRadians) * 0.5f; + + Point2f* p0 = [[Point2f alloc] initWithX:self.center.x - a * self.size.height - b * self.size.width y:self.center.y + b * self.size.height - a * self.size.width]; + Point2f* p1 = [[Point2f alloc] initWithX:self.center.x + a * self.size.height - b * self.size.width y:self.center.y - b * self.size.height - a * self.size.width]; + Point2f* p2 = [[Point2f alloc] initWithX:2 * self.center.x - p0.x y:2 * self.center.y - p0.y]; + Point2f* p3 = [[Point2f alloc] initWithX:2 * self.center.x - p1.x y:2 * self.center.y - p1.y]; + return [NSArray arrayWithObjects:p0, p1, p2, p3, nil]; +} + +- (Rect2f*)boundingRect { + NSArray* pts = [self points]; + Rect2f* rect = [[Rect2f alloc] initWithX:(int)floor(MIN(MIN(MIN(pts[0].x, pts[1].x), pts[2].x), pts[3].x)) y:(int)floor(MIN(MIN(MIN(pts[0].y, pts[1].y), pts[2].y), pts[3].y)) width:(int)ceil(MAX(MAX(MAX(pts[0].x, pts[1].x), pts[2].x), pts[3].x)) height:(int)ceil(MAX(MAX(MAX(pts[0].y, pts[1].y), pts[2].y), pts[3].y))]; + rect.width -= rect.x - 1; + rect.height -= rect.y - 1; + return rect; +} + +- (RotatedRect*)clone { + return [[RotatedRect alloc] initWithCenter:[self.center clone] size:[self.size clone] angle:self.angle]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[RotatedRect class]]) { + return NO; + } else { + RotatedRect* rect = (RotatedRect*)other; + return [self.center isEqual:rect.center] && [self.size isEqual:rect.size] && self.angle == rect.angle; + } +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + result = prime * result + FLOAT_TO_BITS(self.center.x); + result = prime * result + FLOAT_TO_BITS(self.center.y); + result = prime * result + FLOAT_TO_BITS(self.size.width); + result = prime * result + FLOAT_TO_BITS(self.size.height); + int64_t temp = DOUBLE_TO_BITS(self.angle); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + return result; +} + +- (NSString*)description { + return [NSString stringWithFormat:@"RotatedRect {%@,%@,%lf}", self.center.description, self.size.description, self.angle]; +} + +@end diff --git a/modules/core/misc/objc/common/Scalar.h b/modules/core/misc/objc/common/Scalar.h new file mode 100644 index 0000000000..ebf54c48f6 --- /dev/null +++ b/modules/core/misc/objc/common/Scalar.h @@ -0,0 +1,96 @@ +// +// Scalar.h +// +// Created by Giles Payne on 2019/10/06. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** +* Represents a four element vector +*/ +@interface Scalar : NSObject + +#pragma mark - Properties + +@property(readonly) NSArray* val; +#ifdef __cplusplus +@property(readonly) cv::Scalar& nativeRef; +#endif + +#pragma mark - Constructors + +- (instancetype)initWithVals:(NSArray*)vals; +- (instancetype)initWithV0:(double)v0 v1:(double)v1 v2:(double)v2 v3:(double)v3 NS_SWIFT_NAME(init(_:_:_:_:)); +- (instancetype)initWithV0:(double)v0 v1:(double)v1 v2:(double)v2 NS_SWIFT_NAME(init(_:_:_:)); +- (instancetype)initWithV0:(double)v0 v1:(double)v1 NS_SWIFT_NAME(init(_:_:)); +- (instancetype)initWithV0:(double)v0 NS_SWIFT_NAME(init(_:)); +#ifdef __cplusplus ++ (instancetype)fromNative:(cv::Scalar&)nativeScalar; +#endif + +#pragma mark - Methods + +/** +* Creates a scalar with all elements of the same value +* @param v The value to set each element to +*/ ++ (Scalar*)all:(double)v; + +/** +* Calculates per-element product with another Scalar and a scale factor +* @param it The other Scalar +* @param scale The scale factor +*/ +- (Scalar*)mul:(Scalar*)it scale:(double)scale; + +/** +* Calculates per-element product with another Scalar +* @param it The other Scalar +*/ +- (Scalar*)mul:(Scalar*)it; + +/** +* Returns (v0, -v1, -v2, -v3) +*/ +- (Scalar*)conj; + +/** +* Returns true iff v1 == v2 == v3 == 0 +*/ +- (BOOL)isReal; + +#pragma mark - Common Methods + +/** +* Clone object +*/ +- (Scalar*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)object; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString*)description; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Scalar.mm b/modules/core/misc/objc/common/Scalar.mm new file mode 100644 index 0000000000..29a18a7991 --- /dev/null +++ b/modules/core/misc/objc/common/Scalar.mm @@ -0,0 +1,117 @@ +// +// Scalar.mm +// +// Created by Giles Payne on 2019/10/06. +// + +#import "Scalar.h" +#import "CVObjcUtil.h" + +double getVal(NSArray* vals, int index) { + return [vals count] > index ? vals[index].doubleValue : 0; +} + +@implementation Scalar { + cv::Scalar native; +} + +- (NSArray*)val { + return @[[NSNumber numberWithDouble:native.val[0]], [NSNumber numberWithDouble:native.val[1]], [NSNumber numberWithDouble:native.val[2]], [NSNumber numberWithDouble:native.val[3]]]; +} + +#ifdef __cplusplus +- (cv::Scalar&)nativeRef { + return native; +} +#endif + +- (instancetype)initWithVals:(NSArray *)vals { + return [self initWithV0:getVal(vals, 0) v1:getVal(vals, 1) v2:getVal(vals, 2) v3:getVal(vals, 3)]; +} + +- (instancetype)initWithV0:(double)v0 v1:(double)v1 v2:(double)v2 v3:(double)v3 { + self = [super init]; + if (self != nil) { + native.val[0] = v0; + native.val[1] = v1; + native.val[2] = v2; + native.val[3] = v3; + } + return self; +} + +- (instancetype)initWithV0:(double)v0 v1:(double)v1 v2:(double)v2 { + return [self initWithV0:v0 v1:v1 v2:v2 v3:0]; +} + +- (instancetype)initWithV0:(double)v0 v1:(double)v1 { + return [self initWithV0:v0 v1:v1 v2:0 v3:0]; +} + +- (instancetype)initWithV0:(double)v0 { + return [self initWithV0:v0 v1:0 v2:0 v3:0]; +} + +#ifdef __cplusplus ++ (instancetype)fromNative:(cv::Scalar&)nativeScalar { + return [[Scalar alloc] initWithV0:nativeScalar.val[0] v1:nativeScalar.val[1] v2:nativeScalar.val[2] v3:nativeScalar.val[3]]; +} +#endif + ++ (Scalar*)all:(double)v { + return [[Scalar alloc] initWithV0:v v1:v v2:v v3:v]; +} + +- (Scalar*)clone { + return [Scalar fromNative:self.nativeRef]; +} + +- (Scalar*)mul:(Scalar*)it scale:(double)scale { + return [[Scalar alloc] initWithV0:self.nativeRef.val[0]*it.nativeRef.val[0]*scale v1:self.nativeRef.val[1]*it.nativeRef.val[1]*scale v2:self.nativeRef.val[2]*it.nativeRef.val[2]*scale v3:self.nativeRef.val[3]*it.nativeRef.val[3]*scale]; +} + +- (Scalar*)mul:(Scalar*)it { + return [self mul:it scale:1]; +} + +- (Scalar*)conj { + return [[Scalar alloc] initWithV0:self.nativeRef.val[0] v1:-self.nativeRef.val[1] v2:-self.nativeRef.val[2] v3:-self.nativeRef.val[3]]; +} + +- (BOOL)isReal { + return self.nativeRef.val[1] == self.nativeRef.val[2] == self.nativeRef.val[3] == 0; +} + +- (BOOL)isEqual:(id)other +{ + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Scalar class]]) { + return NO; + } else { + Scalar* it = (Scalar*) other; + return it.nativeRef.val[0] == self.nativeRef.val[0] && it.nativeRef.val[1] == self.nativeRef.val[1] && it.nativeRef.val[2] == self.nativeRef.val[2] && it.nativeRef.val[3] == self.nativeRef.val[3]; + } +} + +- (NSUInteger)hash +{ + int prime = 31; + uint32_t result = 1; + int64_t temp = DOUBLE_TO_BITS(self.nativeRef.val[0]); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + temp = DOUBLE_TO_BITS(self.nativeRef.val[1]); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + temp = DOUBLE_TO_BITS(self.nativeRef.val[2]); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + temp = DOUBLE_TO_BITS(self.nativeRef.val[3]); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + return result; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"Scalar [%lf, %lf, %lf, %lf]", self.nativeRef.val[0], self.nativeRef.val[1], self.nativeRef.val[2], self.nativeRef.val[3]]; +} + +@end diff --git a/modules/core/misc/objc/common/Size2d.h b/modules/core/misc/objc/common/Size2d.h new file mode 100644 index 0000000000..233f4c29ae --- /dev/null +++ b/modules/core/misc/objc/common/Size2d.h @@ -0,0 +1,87 @@ +// +// Size2d.h +// +// Created by Giles Payne on 2019/10/06. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +@class Point2d; + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** +* Represents the dimensions of a rectangle the values of which are of type `double` +*/ +@interface Size2d : NSObject + +#pragma mark - Properties + +@property double width; +@property double height; +#ifdef __cplusplus +@property(readonly) cv::Size2d& nativeRef; +#endif + +#pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithWidth:(double)width height:(double)height; +- (instancetype)initWithPoint:(Point2d*)point; +- (instancetype)initWithVals:(NSArray*)vals; + +#ifdef __cplusplus ++ (instancetype)fromNative:(cv::Size2d&)size; +#endif ++ (instancetype)width:(double)width height:(double)height; + +#pragma mark - Methods + +/** +* Returns the area of a rectangle with corresponding dimensions +*/ +- (double)area; + +/** +* Determines if a rectangle with corresponding dimensions has area of zero +*/ +- (BOOL)empty; + +/** +* Set the dimensions from the values of an array +* @param vals The array of values from which to set the dimensions +*/ +- (void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +#pragma mark - Common Methods + +/** +* Clone object +*/ +- (Size2d*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)object; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString*)description; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Size2d.mm b/modules/core/misc/objc/common/Size2d.mm new file mode 100644 index 0000000000..cb9a8c25ea --- /dev/null +++ b/modules/core/misc/objc/common/Size2d.mm @@ -0,0 +1,110 @@ +// +// Size2d.mm +// +// Created by Giles Payne on 2019/10/06. +// + +#import "Size2d.h" +#import "Point2d.h" +#import "CVObjcUtil.h" + +@implementation Size2d { + cv::Size2d native; +} + +- (double)width { + return native.width; +} + +- (void)setWidth:(double)val { + native.width = val; +} + +- (double)height { + return native.height; +} + +- (void)setHeight:(double)val { + native.height = val; +} + +- (cv::Size2d&)nativeRef { + return native; +} + +- (instancetype)init { + return [self initWithWidth:0 height:0]; +} + +- (instancetype)initWithWidth:(double)width height:(double)height { + self = [super init]; + if (self) { + self.width = width; + self.height = height; + } + return self; +} + +- (instancetype)initWithPoint:(Point2d*)point { + return [self initWithWidth:point.x height:point.y]; +} + +- (instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + ++ (instancetype)fromNative:(cv::Size2d&)size { + return [[Size2d alloc] initWithWidth:size.width height:size.height]; +} + ++ (instancetype)width:(double)width height:(double)height { + return [[Size2d alloc] initWithWidth:width height:height]; +} + +- (double)area { + return self.width * self.height; +} + +- (BOOL)empty { + return self.width <= 0 || self.height <= 0; +} + +- (void)set:(NSArray*)vals { + self.width = (vals != nil && vals.count > 0) ? vals[0].doubleValue : 0; + self.height = (vals != nil && vals.count > 1) ? vals[1].doubleValue : 0; +} + +- (Size2d*)clone { + return [[Size2d alloc] initWithWidth:self.width height:self.height]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Size2d class]]) { + return NO; + } else { + Size2d* it = (Size2d*)other; + return self.width == it.width && self.height == it.height; + } +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + int64_t temp = DOUBLE_TO_BITS(self.height); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + temp = DOUBLE_TO_BITS(self.width); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + return result; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Size2d {%lf,%lf}", self.width, self.height]; +} + +@end diff --git a/modules/core/misc/objc/common/Size2f.h b/modules/core/misc/objc/common/Size2f.h new file mode 100644 index 0000000000..83f1bae016 --- /dev/null +++ b/modules/core/misc/objc/common/Size2f.h @@ -0,0 +1,87 @@ +// +// Size2f.h +// +// Created by Giles Payne on 2019/10/06. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +@class Point2f; + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** +* Represents the dimensions of a rectangle the values of which are of type `float` +*/ +@interface Size2f : NSObject + +#pragma mark - Properties + +@property float width; +@property float height; +#ifdef __cplusplus +@property(readonly) cv::Size2f& nativeRef; +#endif + +#pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithWidth:(float)width height:(float)height; +- (instancetype)initWithPoint:(Point2f*)point; +- (instancetype)initWithVals:(NSArray*)vals; + +#ifdef __cplusplus ++ (instancetype)fromNative:(cv::Size2f&)size; +#endif ++ (instancetype)width:(float)width height:(float)height; + +#pragma mark - Methods + +/** +* Returns the area of a rectangle with corresponding dimensions +*/ +- (double)area; + +/** +* Determines if a rectangle with corresponding dimensions has area of zero +*/ +- (BOOL)empty; + +/** +* Set the dimensions from the values of an array +* @param vals The array of values from which to set the dimensions +*/ +- (void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +#pragma mark - Common Methods + +/** +* Clone object +*/ +- (Size2f*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)object; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString*)description; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Size2f.mm b/modules/core/misc/objc/common/Size2f.mm new file mode 100644 index 0000000000..00ff5eec9f --- /dev/null +++ b/modules/core/misc/objc/common/Size2f.mm @@ -0,0 +1,108 @@ +// +// Size2f.mm +// +// Created by Giles Payne on 2019/10/06. +// + +#import "Size2f.h" +#import "Point2f.h" +#import "CVObjcUtil.h" + +@implementation Size2f { + cv::Size2f native; +} + +- (float)width { + return native.width; +} + +- (void)setWidth:(float)val { + native.width = val; +} + +- (float)height { + return native.height; +} + +- (void)setHeight:(float)val { + native.height = val; +} + +- (cv::Size2f&)nativeRef { + return native; +} + +- (instancetype)init { + return [self initWithWidth:0 height:0]; +} + +- (instancetype)initWithWidth:(float)width height:(float)height { + self = [super init]; + if (self) { + self.width = width; + self.height = height; + } + return self; +} + +- (instancetype)initWithPoint:(Point2f*)point { + return [self initWithWidth:point.x height:point.y]; +} + +- (instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + ++ (instancetype)fromNative:(cv::Size2f&)size { + return [[Size2f alloc] initWithWidth:size.width height:size.height]; +} + ++ (instancetype)width:(float)width height:(float)height { + return [[Size2f alloc] initWithWidth:width height:height]; +} + +- (double)area { + return self.width * self.height; +} + +- (BOOL)empty { + return self.width <= 0 || self.height <= 0; +} + +- (void)set:(NSArray*)vals { + self.width = (vals != nil && vals.count > 0) ? vals[0].floatValue : 0; + self.height = (vals != nil && vals.count > 1) ? vals[1].floatValue : 0; +} + +- (Size2f*)clone { + return [[Size2f alloc] initWithWidth:self.width height:self.height]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Size2f class]]) { + return NO; + } else { + Size2f* it = (Size2f*)other; + return self.width == it.width && self.height == it.height; + } +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + result = prime * result + FLOAT_TO_BITS(self.height); + result = prime * result + FLOAT_TO_BITS(self.width); + return result; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Size2f {%f,%f}", self.width, self.height]; +} + +@end diff --git a/modules/core/misc/objc/common/Size2i.h b/modules/core/misc/objc/common/Size2i.h new file mode 100644 index 0000000000..9a714da13e --- /dev/null +++ b/modules/core/misc/objc/common/Size2i.h @@ -0,0 +1,88 @@ +// +// Size2i.h +// +// Created by Giles Payne on 2019/10/06. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +@class Point2i; + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** +* Represents the dimensions of a rectangle the values of which are of type `int` +*/ +NS_SWIFT_NAME(Size) +@interface Size2i : NSObject + +#pragma mark - Properties + +@property int width; +@property int height; +#ifdef __cplusplus +@property(readonly) cv::Size2i& nativeRef; +#endif + +#pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithWidth:(int)width height:(int)height; +- (instancetype)initWithPoint:(Point2i*)point; +- (instancetype)initWithVals:(NSArray*)vals; + +#ifdef __cplusplus ++ (instancetype)fromNative:(cv::Size2i&)size; +#endif ++ (instancetype)width:(int)width height:(int)height; + +#pragma mark - Methods + +/** +* Returns the area of a rectangle with corresponding dimensions +*/ +- (double)area; + +/** +* Determines if a rectangle with corresponding dimensions has area of zero +*/ +- (BOOL)empty; + +/** +* Set the dimensions from the values of an array +* @param vals The array of values from which to set the dimensions +*/ +- (void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +#pragma mark - Common Methods + +/** +* Clone object +*/ +- (Size2i*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)object; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString*)description; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/Size2i.mm b/modules/core/misc/objc/common/Size2i.mm new file mode 100644 index 0000000000..a352c501fa --- /dev/null +++ b/modules/core/misc/objc/common/Size2i.mm @@ -0,0 +1,108 @@ +// +// Size2i.mm +// +// Created by Giles Payne on 2019/10/06. +// + +#import "Size2i.h" +#import "Point2i.h" +#import "CVObjcUtil.h" + +@implementation Size2i { + cv::Size2i native; +} + +- (int)width { + return native.width; +} + +- (void)setWidth:(int)val { + native.width = val; +} + +- (int)height { + return native.height; +} + +- (void)setHeight:(int)val { + native.height = val; +} + +- (cv::Size2i&)nativeRef { + return native; +} + +- (instancetype)init { + return [self initWithWidth:0 height:0]; +} + +- (instancetype)initWithWidth:(int)width height:(int)height { + self = [super init]; + if (self) { + self.width = width; + self.height = height; + } + return self; +} + +- (instancetype)initWithPoint:(Point2i*)point { + return [self initWithWidth:point.x height:point.y]; +} + +- (instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + ++ (instancetype)fromNative:(cv::Size2i&)size { + return [[Size2i alloc] initWithWidth:size.width height:size.height]; +} + ++ (instancetype)width:(int)width height:(int)height { + return [[Size2i alloc] initWithWidth:width height:height]; +} + +- (double)area { + return self.width * self.height; +} + +- (BOOL)empty { + return self.width <= 0 || self.height <= 0; +} + +- (void)set:(NSArray*)vals { + self.width = (vals != nil && vals.count > 0) ? vals[0].intValue : 0; + self.height = (vals != nil && vals.count > 1) ? vals[1].intValue : 0; +} + +- (Size2i*)clone { + return [[Size2i alloc] initWithWidth:self.width height:self.height]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[Size2i class]]) { + return NO; + } else { + Size2i* it = (Size2i*)other; + return self.width == it.width && self.height == it.height; + } +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + result = prime * result + self.height; + result = prime * result + self.width; + return result; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Size2i {%d,%d}", self.width, self.height]; +} + +@end diff --git a/modules/core/misc/objc/common/TermCriteria.h b/modules/core/misc/objc/common/TermCriteria.h new file mode 100644 index 0000000000..1e3055dd74 --- /dev/null +++ b/modules/core/misc/objc/common/TermCriteria.h @@ -0,0 +1,77 @@ +// +// TermCriteria.h +// +// Created by Giles Payne on 2019/10/08. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** +* Class representing termination criteria for iterative algorithms. +*/ +@interface TermCriteria : NSObject + +#pragma mark - Properties + +@property(class, readonly) int COUNT; +@property(class, readonly) int EPS; +@property(class, readonly) int MAX_ITER; + +@property int type; +@property int maxCount; +@property double epsilon; +#ifdef __cplusplus +@property(readonly) cv::TermCriteria& nativeRef; +#endif + +#pragma mark - Constructors + +- (instancetype)init; +- (instancetype)initWithType:(int)type maxCount:(int)maxCount epsilon:(double)epsilon; +- (instancetype)initWithVals:(NSArray*)vals; +#ifdef __cplusplus ++ (instancetype)fromNative:(cv::TermCriteria&)nativeTermCriteria; +#endif + +#pragma mark - Methods + +/** +* Set the termination criteria values from the values of an array +* @param vals The array of values from which to set the termination criteria values +*/ +- (void)set:(NSArray*)vals NS_SWIFT_NAME(set(vals:)); + +#pragma mark - Common Methods + +/** +* Clone object +*/ +- (TermCriteria*)clone; + +/** +* Compare for equality +* @param other Object to compare +*/ +- (BOOL)isEqual:(nullable id)object; + +/** +* Calculate hash value for this object +*/ +- (NSUInteger)hash; + +/** +* Returns a string that describes the contents of the object +*/ +- (NSString*)description; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/core/misc/objc/common/TermCriteria.mm b/modules/core/misc/objc/common/TermCriteria.mm new file mode 100644 index 0000000000..b7339cd523 --- /dev/null +++ b/modules/core/misc/objc/common/TermCriteria.mm @@ -0,0 +1,119 @@ +// +// TermCriteria.m +// +// Created by Giles Payne on 2019/12/25. +// + +#import "TermCriteria.h" +#import "CVObjcUtil.h" + +@implementation TermCriteria { + cv::TermCriteria native; +} + ++ (int)COUNT { + return 1; +} + ++ (int)EPS { + return 2; +} + ++ (int)MAX_ITER { + return 1; +} + +- (int)type { + return native.type; +} + +- (void)setType:(int)val { + native.type = val; +} + +- (int)maxCount { + return native.maxCount; +} + +- (void)setMaxCount:(int)val { + native.maxCount = val; +} + +- (double)epsilon { + return native.epsilon; +} + +- (void)setEpsilon:(double)val { + native.epsilon = val; +} + +#ifdef __cplusplus +- (cv::TermCriteria&)nativeRef { + return native; +} +#endif + +- (instancetype)init { + return [self initWithType:0 maxCount:0 epsilon:0.0]; +} + +- (instancetype)initWithType:(int)type maxCount:(int)maxCount epsilon:(double)epsilon { + self = [super init]; + if (self) { + self.type = type; + self.maxCount = maxCount; + self.epsilon = epsilon; + } + return self; +} + +- (instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + +#ifdef __cplusplus ++ (instancetype)fromNative:(cv::TermCriteria&)nativeTermCriteria { + return [[TermCriteria alloc] initWithType:nativeTermCriteria.type maxCount:nativeTermCriteria.maxCount epsilon:nativeTermCriteria.epsilon]; +} +#endif + +- (void)set:(NSArray*)vals { + self.type = (vals != nil && vals.count > 0) ? vals[0].intValue : 0; + self.maxCount = (vals != nil && vals.count > 1) ? vals[1].intValue : 0; + self.epsilon = (vals != nil && vals.count > 2) ? vals[2].doubleValue : 0.0; +} + +- (TermCriteria*)clone { + return [[TermCriteria alloc] initWithType:self.type maxCount:self.maxCount epsilon:self.epsilon]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } else if (![other isKindOfClass:[TermCriteria class]]) { + return NO; + } else { + TermCriteria* it = (TermCriteria*)other; + return self.type == it.type && self.maxCount == it.maxCount && self.epsilon == it.epsilon; + } +} + +- (NSUInteger)hash { + int prime = 31; + uint32_t result = 1; + result = prime * result + self.type; + result = prime * result + self.maxCount; + int64_t temp = DOUBLE_TO_BITS(self.epsilon); + result = prime * result + (int32_t) (temp ^ (temp >> 32)); + return result; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"TermCriteria { type: %d, maxCount: %d, epsilon: %lf}", self.type, self.maxCount, self.epsilon]; +} + +@end diff --git a/modules/core/misc/objc/filelist b/modules/core/misc/objc/filelist new file mode 100644 index 0000000000..34ad0374ec --- /dev/null +++ b/modules/core/misc/objc/filelist @@ -0,0 +1,4 @@ +include/opencv2/core/base.hpp +include/opencv2/core.hpp +include/opencv2/core/utility.hpp +misc/objc/manual/core_manual.hpp \ No newline at end of file diff --git a/modules/core/misc/objc/gen_dict.json b/modules/core/misc/objc/gen_dict.json new file mode 100644 index 0000000000..574c334145 --- /dev/null +++ b/modules/core/misc/objc/gen_dict.json @@ -0,0 +1,375 @@ +{ + "module_imports": ["Point2i", "MinMaxLocResult"], + "class_ignore_list" : [ + "FileNode", + "FileStorage", + "KDTree", + "KeyPoint", + "DMatch" + ], + "missing_consts" : { + "Core" : { + "private" : [], + "public" : [ + ["SVD_MODIFY_A", 1], ["SVD_NO_UV", 2], ["SVD_FULL_UV", 4], + ["FILLED", -1], + ["REDUCE_SUM", 0], ["REDUCE_AVG", 1], ["REDUCE_MAX", 2], ["REDUCE_MIN", 3] + ] + } + }, + "ManualFuncs" : { + "Core" : { + "minMaxLoc" : { + "declaration" : [ + "// C++: minMaxLoc(Mat src, double* minVal, double* maxVal=0, Point* minLoc=0, Point* maxLoc=0, InputArray mask=noArray())", + "+ (MinMaxLocResult*)minMaxLoc:(Mat*)src mask:(nullable Mat*)mask;", + "\n", + "+ (MinMaxLocResult*)minMaxLoc:(Mat*)src;", + "\n" + ], + "implementation" : [ + "// C++: minMaxLoc(Mat src, double* minVal, double* maxVal=0, Point* minLoc=0, Point* maxLoc=0, InputArray mask=noArray())", + "+ (MinMaxLocResult*)minMaxLoc:(Mat*)src mask:(nullable Mat*)mask {", + " double minVal, maxVal;", + " cv::Point minLoc, maxLoc;", + " cv::Mat& rSrc = src.nativeRef;", + " if (mask != nil) {", + " cv::Mat& rMask = mask.nativeRef;", + " cv::minMaxLoc(rSrc, &minVal, &maxVal, &minLoc, &maxLoc, rMask);", + " } else {", + " cv::minMaxLoc(rSrc, &minVal, &maxVal, &minLoc, &maxLoc);", + " }", + " return [[MinMaxLocResult alloc] initWithMinval:minVal maxVal:maxVal minLoc:[Point2i fromNative:minLoc] maxLoc:[Point2i fromNative:maxLoc]];", + "}", + "\n", + "+ (MinMaxLocResult*)minMaxLoc:(Mat*)src {", + " return [self minMaxLoc:src mask:nil];", + "}", + "\n" + ], + "objc_method_name" : "+minMaxLoc:mask:" + }, + "checkHardwareSupport" : {"declaration" : [""], "implementation" : [""] }, + "setUseOptimized" : {"declaration" : [""], "implementation" : [""] }, + "useOptimized" : {"declaration" : [""], "implementation" : [""] } + } + }, + "func_arg_fix" : { + "randu" : { "low" : {"ctype" : "double"}, + "high" : {"ctype" : "double"} }, + "randn" : { "mean" : {"ctype" : "double"}, + "stddev" : {"ctype" : "double"} }, + "inRange" : { "lowerb" : {"ctype" : "Scalar"}, + "upperb" : {"ctype" : "Scalar"} }, + "boundingRect" : { "points" : {"ctype" : "vector_Point"} }, + "hconcat" : { "src" : {"ctype" : "vector_Mat"} }, + "vconcat" : { "src" : {"ctype" : "vector_Mat"} }, + "checkRange" : {"pos" : {"ctype" : "*"} }, + "meanStdDev" : { "mean" : {"ctype" : "vector_double"}, + "stddev" : {"ctype" : "vector_double"} }, + "mixChannels" : { "dst" : {"attrib" : []} }, + "rotate" : { "rotateCode" : {"ctype" : "RotateFlags"} }, + "norm" : { "normType" : {"ctype" : "NormTypes"} }, + "batchDistance" : { "normType" : {"ctype" : "NormTypes"} }, + "normalize" : { "norm_type" : {"ctype" : "NormTypes"} }, + "compare" : { "cmpop" : {"ctype" : "CmpTypes"} }, + "copyMakeBorder" : { "borderType" : {"ctype" : "BorderTypes"} }, + "borderInterpolate" : { "borderType" : {"ctype" : "BorderTypes"} }, + "(void)divide:(double)scale src2:(Mat*)src2 dst:(Mat*)dst dtype:(int)dtype" : { "src2" : {"name" : "src"} } + }, + "type_dict" : { + "Algorithm": { + "objc_type": "Algorithm*" + }, + "CvSlice": { + "objc_type": "Range*" + }, + "CvTermCriteria": { + "objc_type": "TermCriteria*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[TermCriteria fromNative:%(n)s]" + }, + "DMatch": { + "objc_type": "DMatch*" + }, + "KeyPoint": { + "objc_type": "KeyPoint*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[KeyPoint fromNative:%(n)s]" + }, + "Mat": { + "objc_type": "Mat*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Mat fromNative:%(n)s]", + "from_cpp_ptr": "[Mat fromNativePtr:%(n)s]" + }, + "Moments": { + "objc_type": "Moments*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Moments fromNative:%(n)s]" + }, + "Point": { + "objc_type": "Point2i*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Point2i fromNative:%(n)s]" + }, + "Point2f": { + "objc_type": "Point2f*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Point2f fromNative:%(n)s]" + }, + "Point2d": { + "objc_type": "Point2d*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Point2d fromNative:%(n)s]" + }, + "Point3d": { + "objc_type": "Point3d*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Point3d fromNative:%(n)s]" + }, + "Point3f": { + "objc_type": "Point3f*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Point3f fromNative:%(n)s]" + }, + "Point3": { + "objc_type": "Point3i*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Point3i fromNative:%(n)s]" + }, + "Range": { + "objc_type": "Range*" + }, + "Rect": { + "objc_type": "Rect2i*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Rect2i fromNative:%(n)s]" + }, + "Rect2i": { + "objc_type": "Rect2i*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Rect2i fromNative:%(n)s]" + }, + "Rect2f": { + "objc_type": "Rect2f*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Rect2f fromNative:%(n)s]" + }, + "Rect2d": { + "objc_type": "Rect2d*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Rect2d fromNative:%(n)s]" + }, + "RotatedRect": { + "objc_type": "RotatedRect*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[RotatedRect fromNative:%(n)s]" + }, + "Scalar": { + "objc_type": "Scalar*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Scalar fromNative:%(n)s]" + }, + "Size": { + "objc_type": "Size2i*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Size2i fromNative:%(n)s]" + }, + "Size2f": { + "objc_type": "Size2f*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Size2f fromNative:%(n)s]" + }, + "Size2d": { + "objc_type": "Size2d*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Size2d fromNative:%(n)s]" + }, + "String": { + "objc_type": "NSString*", + "to_cpp": "cv::String(%(n)s.UTF8String)", + "from_cpp": "[NSString stringWithUTF8String:%(n)s.c_str()]" + }, + "std::string": { + "objc_type": "NSString*", + "to_cpp": "std::string(%(n)s.UTF8String)", + "from_cpp": "[NSString stringWithUTF8String:%(n)s.c_str()]" + }, + "TermCriteria": { + "objc_type": "TermCriteria*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[TermCriteria fromNative:%(n)s]" + }, + "Vec4f": { + "objc_type": "Float4*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Float4 fromNative:%(n)s]" + }, + "Vec4i": { + "objc_type": "Int4*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Int4 fromNative:%(n)s]" + }, + "Vec6f": { + "objc_type": "Float6*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Float6 fromNative:%(n)s]" + }, + "Vec2d": { + "objc_type": "Double2*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Double2 fromNative:%(n)s]" + }, + "Vec3d": { + "objc_type": "Double3*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[Double3 fromNative:%(n)s]" + }, + "c_string": { + "objc_type": "NSString*" + }, + "vector_DMatch": { + "objc_type": "DMatch*", + "v_type": "DMatch" + }, + "vector_KeyPoint": { + "objc_type": "KeyPoint*", + "v_type": "KeyPoint" + }, + "vector_Mat": { + "objc_type": "Mat*", + "v_type": "Mat" + }, + "vector_Point": { + "objc_type": "Point2i*", + "v_type": "Point" + }, + "vector_Point2f": { + "objc_type": "Point2f*", + "v_type": "Point2f" + }, + "vector_Point2d": { + "objc_type": "Point2d*", + "v_type": "Point2d" + }, + "vector_Point3": { + "objc_type": "Point3i*", + "v_type": "Point3i" + }, + "vector_Point3f": { + "objc_type": "Point3f*", + "v_type": "Point3f" + }, + "vector_Point3d": { + "objc_type": "Point3d*", + "v_type": "Point3d" + }, + "vector_Rect": { + "objc_type": "Rect2i*", + "v_type": "Rect2i" + }, + "vector_Rect2d": { + "objc_type": "Rect2d*", + "v_type": "Rect2d" + }, + "vector_RotatedRect": { + "objc_type": "RotatedRect*", + "v_type": "RotatedRect" + }, + "vector_String": { + "objc_type": "NSString*", + "v_type": "String" + }, + "vector_string": { + "objc_type": "NSString*", + "v_type": "std::string" + }, + "vector_Vec4f": { + "objc_type": "Float4*", + "v_type": "Vec4f" + }, + "vector_Vec4i": { + "objc_type": "Int4*", + "v_type": "Vec4i" + }, + "vector_Vec6f": { + "objc_type": "Float6*", + "v_type": "Vec6f" + }, + "vector_char": { + "objc_type": "ByteVector*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[ByteVector fromNative:%(n)s]", + "cast_to": "std::vector" + }, + "vector_double": { + "objc_type": "DoubleVector*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[DoubleVector fromNative:%(n)s]", + "cast_to": "std::vector" + }, + "vector_float": { + "objc_type": "FloatVector*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[FloatVector fromNative:%(n)s]", + "cast_to": "std::vector" + }, + "vector_int": { + "objc_type": "IntVector*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[IntVector fromNative:%(n)s]", + "cast_to": "std::vector" + }, + "vector_uchar": { + "objc_type": "ByteVector*", + "to_cpp": "(std::vector&)%(n)s.nativeRef", + "from_cpp": "[ByteVector fromNative:(std::vector&)%(n)s]", + "cast_to": "std::vector" + }, + "vector_vector_Mat": { + "objc_type": "Mat*", + "v_v_type": "Mat" + }, + "vector_vector_DMatch": { + "objc_type": "DMatch*", + "v_v_type": "DMatch" + }, + "vector_vector_KeyPoint": { + "objc_type": "KeyPoint*", + "v_v_type": "KeyPoint" + }, + "vector_vector_Point": { + "objc_type": "Point2i*", + "v_v_type": "Point" + }, + "vector_vector_Point2f": { + "objc_type": "Point2f*", + "v_v_type": "Point2f" + }, + "vector_vector_Point3f": { + "objc_type": "Point3f*", + "v_v_type": "Point3f" + }, + "vector_vector_char": { + "objc_type": "ByteVector*", + "v_type": "ByteVector" + }, + "ByteVector": { + "objc_type": "ByteVector*", + "cast_to": "std::vector" + }, + "IntVector": { + "objc_type": "IntVector*", + "cast_to": "std::vector" + }, + "FloatVector": { + "objc_type": "FloatVector*", + "cast_to": "std::vector" + }, + "DoubleVector": { + "objc_type": "DoubleVector*", + "cast_to": "std::vector" + } + } +} diff --git a/modules/core/misc/objc/manual/core_manual.hpp b/modules/core/misc/objc/manual/core_manual.hpp new file mode 100644 index 0000000000..28ba28d147 --- /dev/null +++ b/modules/core/misc/objc/manual/core_manual.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "opencv2/core.hpp" + +#ifdef OPENCV_BINDINGS_PARSER + +namespace cv +{ +CV_EXPORTS_W void add(InputArray src1, Scalar srcScalar, OutputArray dst, InputArray mask=noArray(), int dtype=-1); + +CV_EXPORTS_W void subtract(InputArray src1, Scalar srcScalar, OutputArray dst, InputArray mask=noArray(), int dtype=-1); + +CV_EXPORTS_W void multiply(InputArray src1, Scalar srcScalar, OutputArray dst, double scale=1, int dtype=-1); + +CV_EXPORTS_W void divide(InputArray src1, Scalar srcScalar, OutputArray dst, double scale=1, int dtype=-1); + +CV_EXPORTS_W void absdiff(InputArray src1, Scalar srcScalar, OutputArray dst); + +CV_EXPORTS_W void compare(InputArray src1, Scalar srcScalar, OutputArray dst, int cmpop); + +CV_EXPORTS_W void min(InputArray src1, Scalar srcScalar, OutputArray dst); + +CV_EXPORTS_W void max(InputArray src1, Scalar srcScalar, OutputArray dst); + +} +#endif diff --git a/modules/core/misc/objc/test/ConvertersTest.swift b/modules/core/misc/objc/test/ConvertersTest.swift new file mode 100755 index 0000000000..5d7efd588d --- /dev/null +++ b/modules/core/misc/objc/test/ConvertersTest.swift @@ -0,0 +1,109 @@ +// +// ConvertersTest.swift +// +// Created by Giles Payne on 2020/06/01. +// + +import XCTest +import OpenCV + +class ConvertersTest: OpenCVTestCase { + + func testPoint2iToMat() { + let pointsIn = [Point(x:3, y:4), Point(x:6, y:7), Point(x:7, y:6), Point(x:-78, y:14), Point(x:-93, y:700)] + let pointsOut = Converters.Mat_to_vector_Point(Converters.vector_Point_to_Mat(pointsIn)) + XCTAssertEqual(pointsIn, pointsOut) + } + + func testPoint2fToMat() { + let pointsIn = [Point2f(x:3.8, y:4.2), Point2f(x:6.01, y:7), Point2f(x:7, y:6), Point2f(x:-78, y:14), Point2f(x:-93, y:700)] + let pointsOut = Converters.Mat_to_vector_Point2f(Converters.vector_Point2f_to_Mat(pointsIn)) + XCTAssertEqual(pointsIn, pointsOut) + } + + func testPoint2dToMat() { + let pointsIn = [Point2d(x:3.80004, y:73.2), Point2d(x:16.01, y:7.1111), Point2d(x:3.14, y:6), Point2d(x:-78, y:14)] + let pointsOut = Converters.Mat_to_vector_Point2d(Converters.vector_Point2d_to_Mat(pointsIn)) + XCTAssertEqual(pointsIn, pointsOut) + } + + func testPoint3iToMat() { + let pointsIn = [Point3i(x:3, y:4, z:2), Point3i(x:6, y:7, z:1), Point3i(x:7, y:6, z:9), Point3i(x:-78, y:14, z:0), Point3i(x:-93, y:700, z:54)] + let pointsOut = Converters.Mat_to_vector_Point3i(Converters.vector_Point3i_to_Mat(pointsIn)) + XCTAssertEqual(pointsIn, pointsOut) + } + + func testPoint3fToMat() { + let pointsIn = [Point3f(x:3.8, y:4.2, z:1200), Point3f(x:6.01, y:7, z: 12), Point3f(x:7, y:6, z:8.88128), Point3f(x:-78, y:14, z:-1), Point3f(x:-93, y:700, z:200)] + let pointsOut = Converters.Mat_to_vector_Point3f(Converters.vector_Point3f_to_Mat(pointsIn)) + XCTAssertEqual(pointsIn, pointsOut) + } + + func testPoint3dToMat() { + let pointsIn = [Point3d(x:3.80004, y:73.2, z:1), Point3d(x:16.01, y:7.1111, z:2), Point3d(x:3.14, y:6, z:3), Point3d(x:-78, y:14, z:4)] + let pointsOut = Converters.Mat_to_vector_Point3d(Converters.vector_Point3d_to_Mat(pointsIn)) + XCTAssertEqual(pointsIn, pointsOut) + } + + func testFloatToMat() { + let floatsIn:[Float] = [23.8, 999.89, 93, 0.9, 12] + let floatsOut = Converters.Mat_to_vector_float(Converters.vector_float_to_Mat(floatsIn as [NSNumber])) as! [Float] + XCTAssertEqual(floatsIn, floatsOut) + } + + func testIntToMat() { + let intsIn:[Int32] = [23, 999, -93, 0, 4412] + let intsOut = Converters.Mat_to_vector_int(Converters.vector_int_to_Mat(intsIn as [NSNumber])) as! [Int32] + XCTAssertEqual(intsIn, intsOut) + } + + func testCharToMat() { + let charsIn:[Int8] = [23, -23, 93, 0, -127] + let charsOut = Converters.Mat_to_vector_char(Converters.vector_char_to_Mat(charsIn as [NSNumber])) as! [Int8] + XCTAssertEqual(charsIn, charsOut) + } + + func testUCharToMat() { + let ucharsIn:[UInt8] = [23, 190, 93, 0, 255] + let ucharsOut = Converters.Mat_to_vector_uchar(Converters.vector_uchar_to_Mat(ucharsIn as [NSNumber])) as! [UInt8] + XCTAssertEqual(ucharsIn, ucharsOut) + } + + func testDoubleToMat() { + let doublesIn:[Double] = [23.8, 999.89, 93, 0.9, 12] + let doublesOut = Converters.Mat_to_vector_double(Converters.vector_double_to_Mat(doublesIn as [NSNumber])) as! [Double] + XCTAssertEqual(doublesIn, doublesOut) + } + + func testRectToMat() { + let rectsIn = [Rect(x: 0, y: 0, width: 3, height: 4), Rect(x: 10, y: 23, width: 7, height: 6), Rect(x: 0, y: 1111110, width: 1, height: 4000)] + let rectsOut = Converters.Mat_to_vector_Rect(Converters.vector_Rect_to_Mat(rectsIn)) + XCTAssertEqual(rectsIn, rectsOut) + } + + func testRect2dToMat() { + let rectsIn = [Rect2d(x: 0.001, y: 0.00001, width: 3.2, height: 4.556555555), Rect2d(x: 10.009, y: -6623, width: 7.9, height: 6), Rect2d(x: 0, y: 1111.33110, width: 0.99999, height: 3999.999)] + let rectsOut = Converters.Mat_to_vector_Rect2d(Converters.vector_Rect2d_to_Mat(rectsIn)) + XCTAssertEqual(rectsIn, rectsOut) + } + + func testKeyPointToMat() { + let keyPointsIn = [KeyPoint(x: 8.99, y: 9.00, size: 3, angle: 3.23, response: 0.001, octave: 3, classId: 5), KeyPoint(x: 58.99, y: 9.488, size: 3.4, angle: 2.223, response: 0.006, octave: 4, classId: 7), KeyPoint(x: 7, y: 9.003, size: 12, angle: -3.23, response: 0.02, octave: 1, classId: 8)] + let keyPointsOut = Converters.Mat_to_vector_KeyPoint(Converters.vector_KeyPoint_to_Mat(keyPointsIn)) + XCTAssertEqual(keyPointsIn, keyPointsOut) + } + + func testDMatchToMat() { + let dmatchesIn = [DMatch(queryIdx: 2, trainIdx: 4, distance: 0.7), DMatch(queryIdx: 3, trainIdx: 7, distance: 0.1), DMatch(queryIdx: 4, trainIdx: 8, distance: 0.01)] + let dmatchesOut = Converters.Mat_to_vector_DMatch(Converters.vector_DMatch_to_Mat(dmatchesIn)) + XCTAssertEqual(dmatchesIn, dmatchesOut) + } + + func testRotatedRectToMat() { + let rectsIn = [RotatedRect(center: Point2f(x: 0.4, y: 0.9), size: Size2f(width: 3.0, height: 8.9), angle: 0.3342)] + let rectsOut = Converters.Mat_to_vector_RotatedRect(Converters.vector_RotatedRect_to_Mat(rectsIn)) + XCTAssertEqual(rectsIn[0].center, rectsOut[0].center) + XCTAssertEqual(rectsIn[0].size, rectsOut[0].size) + XCTAssertEqual(rectsIn[0].angle, rectsOut[0].angle, accuracy: OpenCVTestCase.EPS) + } +} diff --git a/modules/core/misc/objc/test/CoreTest.swift b/modules/core/misc/objc/test/CoreTest.swift new file mode 100644 index 0000000000..efa18bbd7f --- /dev/null +++ b/modules/core/misc/objc/test/CoreTest.swift @@ -0,0 +1,1710 @@ +// +// CoreTest.swift +// +// Created by Giles Payne on 2020/01/27. +// + +import XCTest +import OpenCV + +class CoreTest: OpenCVTestCase { + + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } + + func testAbsdiff() throws { + Core.absdiff(src1: gray128, src2: gray255, dst: dst) + + try assertMatEqual(gray127, dst) + } + + func testAddMatMatMat() throws { + Core.add(src1: gray128, src2: gray128, dst: dst) + + try assertMatEqual(gray255, dst) + } + + func testAddMatMatMatMatInt() throws { + Core.add(src1: gray0, src2: gray1, dst: dst, mask: gray1, dtype: CvType.CV_32F) + + XCTAssertEqual(CvType.CV_32F, dst.depth()) + try assertMatEqual(gray1_32f, dst, OpenCVTestCase.EPS) + } + + func testAddWeightedMatDoubleMatDoubleDoubleMat() throws { + Core.addWeighted(src1: gray1, alpha: 120.0, src2: gray127, beta: 1.0, gamma: 10.0, dst: dst) + + try assertMatEqual(gray255, dst) + } + + func testAddWeightedMatDoubleMatDoubleDoubleMatInt() throws { + Core.addWeighted(src1: gray1, alpha: 126.0, src2: gray127, beta: 1.0, gamma: 2.0, dst: dst, dtype: CvType.CV_32F) + + XCTAssertEqual(CvType.CV_32F, dst.depth()) + try assertMatEqual(gray255_32f, dst, OpenCVTestCase.EPS) + } + + func testBitwise_andMatMatMat() throws { + Core.bitwise_and(src1: gray127, src2: gray3, dst: dst) + + try assertMatEqual(gray3, dst) + } + + func testBitwise_andMatMatMatMat() throws { + Core.bitwise_and(src1: gray3, src2: gray1, dst: dst, mask: gray255) + + try assertMatEqual(gray1, dst) + } + + func testBitwise_notMatMat() throws { + Core.bitwise_not(src: gray255, dst: dst) + + try assertMatEqual(gray0, dst) + } + + func testBitwise_notMatMatMat() throws { + Core.bitwise_not(src: gray0, dst: dst, mask: gray1) + + try assertMatEqual(gray255, dst) + } + + func testBitwise_orMatMatMat() throws { + Core.bitwise_or(src1: gray1, src2: gray2, dst: dst) + + try assertMatEqual(gray3, dst) + } + + func testBitwise_orMatMatMatMat() throws { + Core.bitwise_or(src1: gray127, src2: gray3, dst: dst, mask: gray255) + + try assertMatEqual(gray127, dst) + } + + func testBitwise_xorMatMatMat() throws { + Core.bitwise_xor(src1: gray3, src2: gray2, dst: dst) + + try assertMatEqual(gray1, dst) + } + + func testBitwise_xorMatMatMatMat() throws { + Core.bitwise_or(src1: gray127, src2: gray128, dst: dst, mask: gray255) + + try assertMatEqual(gray255, dst) + } + + func testCalcCovarMatrixMatMatMatInt() throws { + let covar = Mat(rows: OpenCVTestCase.matSize, cols: OpenCVTestCase.matSize, type: CvType.CV_64FC1) + let mean = Mat(rows: 1, cols: OpenCVTestCase.matSize, type: CvType.CV_64FC1) + + Core.calcCovarMatrix(samples: gray0_32f, covar: covar, mean: mean, flags: CovarFlags.COVAR_ROWS.rawValue | CovarFlags.COVAR_NORMAL.rawValue) + + try assertMatEqual(gray0_64f, covar, OpenCVTestCase.EPS) + try assertMatEqual(gray0_64f_1d, mean, OpenCVTestCase.EPS) + } + + func testCalcCovarMatrixMatMatMatIntInt() throws { + let covar = Mat(rows: OpenCVTestCase.matSize, cols: OpenCVTestCase.matSize, type: CvType.CV_32F) + let mean = Mat(rows: 1, cols: OpenCVTestCase.matSize, type: CvType.CV_32F) + + Core.calcCovarMatrix(samples: gray0_32f, covar: covar, mean: mean, flags: CovarFlags.COVAR_ROWS.rawValue | CovarFlags.COVAR_NORMAL.rawValue, ctype: CvType.CV_32F) + + try assertMatEqual(gray0_32f, covar, OpenCVTestCase.EPS) + try assertMatEqual(gray0_32f_1d, mean, OpenCVTestCase.EPS) + } + + func testCartToPolarMatMatMatMat() throws { + let x = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try x.put(row: 0, col: 0, data: [3.0, 6.0, 5, 0] as [Float]) + let y = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try y.put(row: 0, col: 0, data: [4.0, 8.0, 12.0] as [Float]) + let dst_angle = Mat() + + Core.cartToPolar(x: x, y: y, magnitude: dst, angle: dst_angle) + + let expected_magnitude = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try expected_magnitude.put(row: 0, col: 0, data: [5.0, 10.0, 13.0] as [Float]) + + let expected_angle = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try expected_angle.put(row: 0, col: 0, data: [atan2rad(4,3), atan2rad(8,6), atan2rad(12,5)]) + try assertMatEqual(expected_magnitude, dst, OpenCVTestCase.EPS) + try assertMatEqual(expected_angle, dst_angle, OpenCVTestCase.EPS) + } + + func testCartToPolarMatMatMatMatBoolean() throws { + let x = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try x.put(row: 0, col: 0, data: [3.0, 6.0, 5, 0] as [Float]) + let y = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try y.put(row: 0, col: 0, data: [4.0, 8.0, 12.0] as [Float]) + let dst_angle = Mat() + + Core.cartToPolar(x: x, y: y, magnitude: dst, angle: dst_angle, angleInDegrees: true) + + let expected_magnitude = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try expected_magnitude.put(row: 0, col: 0, data: [5.0, 10.0, 13.0] as [Float]) + let expected_angle = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try expected_angle.put(row: 0, col: 0, data:[atan2deg(4,3), atan2deg(8,6), atan2deg(12,5)]) + try assertMatEqual(expected_magnitude, dst, OpenCVTestCase.EPS) + try assertMatEqual(expected_angle, dst_angle, OpenCVTestCase.EPS * 180/Double.pi) + } + + + func testCheckRangeMat() throws { + let outOfRange = Mat(rows: 2, cols: 2, type: CvType.CV_64F) + try outOfRange.put(row: 0, col: 0, data: [Double.nan, -Double.infinity, Double.infinity, 0]) + + XCTAssert(Core.checkRange(a: grayRnd_32f)) + XCTAssert(Core.checkRange(a: Mat())) + XCTAssertFalse(Core.checkRange(a: outOfRange)) + } + + func testCheckRangeMatBooleanPointDoubleDouble() throws { + let inRange = Mat(rows: 2, cols: 3, type: CvType.CV_64F) + try inRange.put(row: 0, col: 0, data: [14, 48, 76, 33, 5, 99] as [Double]) + + XCTAssert(Core.checkRange(a: inRange, quiet: true, minVal: 5, maxVal: 100)) + + let outOfRange = Mat(rows: 2, cols: 3, type: CvType.CV_64F) + try inRange.put(row: 0, col: 0, data: [-4, 0, 6, 33, 4, 109] as [Double]) + + XCTAssertFalse(Core.checkRange(a: outOfRange, quiet: true, minVal: 5, maxVal: 100)) + } + + func testCompare() throws { + Core.compare(src1: gray0, src2: gray0, dst: dst, cmpop: .CMP_EQ) + + try assertMatEqual(dst, gray255) + + Core.compare(src1: gray0, src2: gray1, dst: dst, cmpop: .CMP_EQ) + + try assertMatEqual(dst, gray0) + + try grayRnd.put(row: 0, col: 0, data: [0, 0] as [Int8]) + + Core.compare(src1: gray0, src2: grayRnd, dst: dst, cmpop: .CMP_GE) + + let expected = Int32(grayRnd.total()) - Core.countNonZero(src: grayRnd) + XCTAssertEqual(expected, Core.countNonZero(src: dst)) + } + + func testCompleteSymmMat() throws { + Core.completeSymm(m: grayRnd_32f) + + try assertMatEqual(grayRnd_32f, grayRnd_32f.t(), OpenCVTestCase.EPS) + } + + func testCompleteSymmMatBoolean() throws { + let grayRnd_32f2 = grayRnd_32f.clone() + + Core.completeSymm(m: grayRnd_32f, lowerToUpper: true) + + try assertMatEqual(grayRnd_32f, grayRnd_32f.t(), OpenCVTestCase.EPS) + Core.completeSymm(m: grayRnd_32f2, lowerToUpper: false) + try assertMatNotEqual(grayRnd_32f2, grayRnd_32f, OpenCVTestCase.EPS) + } + + func testConvertScaleAbsMatMat() throws { + Core.convertScaleAbs(src: gray0, dst: dst) + + try assertMatEqual(gray0, dst, OpenCVTestCase.EPS) + + Core.convertScaleAbs(src: gray_16u_256, dst: dst) + + try assertMatEqual(gray255, dst, OpenCVTestCase.EPS) + } + + func testConvertScaleAbsMatMatDoubleDouble() throws { + Core.convertScaleAbs(src: gray_16u_256, dst: dst, alpha: 2, beta: -513) + + try assertMatEqual(gray1, dst) + } + + func testCountNonZero() throws { + XCTAssertEqual(0, Core.countNonZero(src: gray0)) + let gray0copy = gray0.clone() + + try gray0copy.put(row: 0, col: 0, data: [-1] as [Int8]) + try gray0copy.put(row: gray0copy.rows() - 1, col: gray0copy.cols() - 1, data: [-1] as [Int8]) + + XCTAssertEqual(2, Core.countNonZero(src: gray0copy)) + } + + func testCubeRoot() { + let res:Float = Core.cubeRoot(val: -27.0) + + XCTAssertEqual(-3.0, res) + } + + func testDctMatMat() throws { + let m = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try m.put(row: 0, col: 0, data: [135.22211, 50.811096, 102.27016, 207.6682] as [Float]) + let dst1 = Mat() + let dst2 = Mat() + + Core.dct(src: gray0_32f_1d, dst: dst1) + Core.dct(src: m, dst: dst2) + + truth = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try truth!.put(row: 0, col: 0, data: [247.98576, -61.252407, 94.904533, 14.013477] as [Float]) + try assertMatEqual(gray0_32f_1d, dst1, OpenCVTestCase.EPS) + try assertMatEqual(truth!, dst2, OpenCVTestCase.EPS) + } + + func testDctMatMatInt() throws { + let m = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try m.put(row: 0, col: 0, data: [247.98576, -61.252407, 94.904533, 14.013477] as [Float]) + let dst1 = Mat() + let dst2 = Mat() + + Core.dct(src: gray0_32f_1d, dst: dst1, flags:DftFlags.DCT_INVERSE.rawValue) + Core.dct(src: m, dst: dst2, flags:DftFlags.DCT_INVERSE.rawValue) + + truth = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try truth!.put(row: 0, col: 0, data: [135.22211, 50.811096, 102.27016, 207.6682] as [Float]) + try assertMatEqual(gray0_32f_1d, dst1, OpenCVTestCase.EPS) + try assertMatEqual(truth!, dst2, OpenCVTestCase.EPS) + } + + func testDeterminant() throws { + let mat = Mat(rows: 2, cols: 2, type: CvType.CV_32F) + try mat.put(row: 0, col: 0, data: [4.0] as [Float]) + try mat.put(row: 0, col: 1, data: [2.0] as [Float]) + try mat.put(row: 1, col: 0, data: [4.0] as [Float]) + try mat.put(row: 1, col: 1, data: [4.0] as [Float]) + + let det = Core.determinant(mtx: mat) + + XCTAssertEqual(8.0, det) + } + + func testDftMatMat() throws { + Core.dft(src: gray0_32f_1d, dst: dst) + + try assertMatEqual(gray0_32f_1d, dst, OpenCVTestCase.EPS) + } + + func testDftMatMatIntInt() throws { + let src1 = Mat(rows: 2, cols: 4, type: CvType.CV_32F) + try src1.put(row: 0, col: 0, data: [1, 2, 3, 4] as [Float]) + try src1.put(row: 1, col: 0, data: [1, 1, 1, 1] as [Float]) + let src2 = Mat(rows: 2, cols: 4, type: CvType.CV_32F) + try src2.put(row: 0, col: 0, data: [1, 2, 3, 4] as [Float]) + try src2.put(row: 1, col: 0, data: [0, 0, 0, 0] as [Float]) + let dst1 = Mat() + let dst2 = Mat() + + Core.dft(src: src1, dst: dst1, flags: DftFlags.DFT_REAL_OUTPUT.rawValue, nonzeroRows: 1) + Core.dft(src: src2, dst: dst2, flags: DftFlags.DFT_REAL_OUTPUT.rawValue, nonzeroRows: 0) + + try assertMatEqual(dst2, dst1, OpenCVTestCase.EPS) + } + + func testDivideDoubleMatMat() throws { + Core.divide(scale: 4.0, src: gray2, dst: dst) + + try assertMatEqual(gray2, dst) + + Core.divide(scale: 4.0, src: gray0, dst: dst) + + try assertMatEqual(gray0, dst) + } + + func testDivideDoubleMatMatInt() throws { + Core.divide(scale: 9.0, src: gray3, dst: dst, dtype: CvType.CV_32F) + + try assertMatEqual(gray3_32f, dst, OpenCVTestCase.EPS) + } + + func testDivideMatMatMat() throws { + Core.divide(src1: gray9, src2: gray3, dst: dst) + + try assertMatEqual(gray3, dst) + } + + func testDivideMatMatMatDouble() throws { + Core.divide(src1: gray1, src2: gray2, dst: dst, scale: 6.0) + + try assertMatEqual(gray3, dst) + } + + func testDivideMatMatMatDoubleInt() throws { + Core.divide(src1: gray1, src2: gray2, dst: dst, scale: 6.0, dtype: CvType.CV_32F) + + try assertMatEqual(gray3_32f, dst, OpenCVTestCase.EPS) + } + + func testEigen() throws { + let src = Mat(rows: 3, cols: 3, type: CvType.CV_32FC1) + try src.put(row: 0, col: 0, data: [2, 0, 0] as [Float]) + try src.put(row: 1, col: 0, data: [0, 6, 0] as [Float]) + try src.put(row: 2, col: 0, data: [0, 0, 4] as [Float]) + let eigenVals = Mat() + let eigenVecs = Mat() + + Core.eigen(src: src, eigenvalues: eigenVals, eigenvectors: eigenVecs) + + let expectedEigenVals = Mat(rows: 3, cols: 1, type: CvType.CV_32FC1) + try expectedEigenVals.put(row: 0, col: 0, data: [6, 4, 2] as [Float]) + try assertMatEqual(eigenVals, expectedEigenVals, OpenCVTestCase.EPS) + + // check by definition + let EPS = 1e-3 + for i:Int32 in 0..<3 { + let vec = eigenVecs.row(i).t() + let lhs = Mat(rows: 3, cols: 1, type: CvType.CV_32FC1) + Core.gemm(src1: src, src2: vec, alpha: 1.0, src3: Mat(), beta: 1.0, dst: lhs) + let rhs = Mat(rows: 3, cols: 1, type: CvType.CV_32FC1) + Core.gemm(src1: vec, src2: eigenVals.row(i), alpha: 1.0, src3: Mat(), beta: 1.0, dst: rhs) + try assertMatEqual(lhs, rhs, EPS) + } + } + + func testExp() throws { + Core.exp(src: gray0_32f, dst: dst) + + try assertMatEqual(gray1_32f, dst, OpenCVTestCase.EPS) + } + + func testExtractChannel() throws { + Core.extractChannel(src: rgba128, dst: dst, coi: 0) + + try assertMatEqual(gray128, dst) + } + + func testFastAtan2() { + let EPS: Float = 0.3 + + let res = Core.fastAtan2(y: 50, x: 50) + + XCTAssertEqual(Float(45.0), res, accuracy:EPS) + + let res2 = Core.fastAtan2(y: 80, x: 20) + + XCTAssertEqual(atan2(80, 20) * 180 / Float.pi, res2, accuracy:EPS) + } + + func testFillConvexPolyMatListOfPointScalar() { + let polyline = [Point(x: 1, y: 1), Point(x: 5, y: 0), Point(x: 6, y: 8), Point(x: 0, y: 9)] + dst = gray0.clone() + + Imgproc.fillConvexPoly(img: dst, points: polyline, color: Scalar(150)) + + XCTAssert(0 < Core.countNonZero(src: dst)) + XCTAssert(dst.total() > Core.countNonZero(src: dst)) + } + + func testFillConvexPolyMatListOfPointScalarIntInt() { + let polyline1 = [Point(x: 2, y: 1), Point(x: 5, y: 1), Point(x: 5, y: 7), Point(x: 2, y: 7)] + let polyline2 = [Point(x: 4, y: 2), Point(x: 10, y: 2), Point(x: 10, y: 14), Point(x: 4, y: 14)] + + // current implementation of fixed-point version of fillConvexPoly + // requires image to be at least 2-pixel wider in each direction than + // contour + Imgproc.fillConvexPoly(img: gray0, points: polyline1, color: colorWhite, lineType: .LINE_8, shift: 0) + + XCTAssert(0 < Core.countNonZero(src: gray0)) + XCTAssert(gray0.total() > Core.countNonZero(src: gray0)) + + Imgproc.fillConvexPoly(img: gray0, points: polyline2, color: colorBlack, lineType: .LINE_8, shift: 1) + + XCTAssertEqual(0, Core.countNonZero(src: gray0)) + } + + func testFillPolyMatListOfListOfPointScalar() throws { + let matSize = 10; + let gray0 = Mat.zeros(Int32(matSize), cols: Int32(matSize), type: CvType.CV_8U) + let polyline = [Point(x: 1, y: 4), Point(x: 1, y: 8), Point(x: 4, y: 1), Point(x: 7, y: 8), Point(x: 7, y: 4)] + let polylines = [polyline] + + Imgproc.fillPoly(img: gray0, pts: polylines, color: Scalar(1)) + + let truth:[Int8] = + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, + 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + + let truthMat = Mat(size: gray0.size(), type: CvType.CV_8U) + try truthMat.put(row:0, col:0, data:truth) + + try assertMatEqual(truthMat, gray0) + } + + func testFillPolyMatListOfListOfPointScalarIntIntPoint() { + let polyline1 = [Point(x: 1, y: 4), Point(x: 1, y: 8), Point(x: 4, y: 1), Point(x: 7, y: 8), Point(x: 7, y: 4)] + let polyline2 = [Point(x: 0, y: 3), Point(x: 0, y: 7), Point(x: 3, y: 0), Point(x: 6, y: 7), Point(x: 6, y: 3)] + + let polylines1 = [polyline1] + let polylines2 = [polyline2] + + Imgproc.fillPoly(img: gray0, pts: polylines1, color: Scalar(1), lineType: .LINE_8, shift: 0, offset: Point(x: 0, y: 0)) + + XCTAssert(0 < Core.countNonZero(src: gray0)) + + Imgproc.fillPoly(img: gray0, pts: polylines2, color: Scalar(0), lineType: .LINE_8, shift: 0, offset: Point(x: 1, y: 1)) + + XCTAssertEqual(0, Core.countNonZero(src: gray0)) + } + + func testFlip() throws { + let src = Mat(rows: 2, cols: 2, type: CvType.CV_32F) + try src.put(row: 0, col: 0, data: [1.0] as [Float]) + try src.put(row: 0, col: 1, data: [2.0] as [Float]) + try src.put(row: 1, col: 0, data: [3.0] as [Float]) + try src.put(row: 1, col: 1, data: [4.0] as [Float]) + let dst1 = Mat() + let dst2 = Mat() + + Core.flip(src: src, dst: dst1, flipCode: 0) + Core.flip(src: src, dst: dst2, flipCode: 1) + + let dst_f1 = Mat(rows: 2, cols: 2, type: CvType.CV_32F) + try dst_f1.put(row: 0, col: 0, data: [3.0] as [Float]) + try dst_f1.put(row: 0, col: 1, data: [4.0] as [Float]) + try dst_f1.put(row: 1, col: 0, data: [1.0] as [Float]) + try dst_f1.put(row: 1, col: 1, data: [2.0] as [Float]) + let dst_f2 = Mat(rows: 2, cols: 2, type: CvType.CV_32F) + try dst_f2.put(row: 0, col: 0, data: [2.0] as [Float]) + try dst_f2.put(row: 0, col: 1, data: [1.0] as [Float]) + try dst_f2.put(row: 1, col: 0, data: [4.0] as [Float]) + try dst_f2.put(row: 1, col: 1, data: [3.0] as [Float]) + try assertMatEqual(dst_f1, dst1, OpenCVTestCase.EPS) + try assertMatEqual(dst_f2, dst2, OpenCVTestCase.EPS) + } + + func testGemmMatMatDoubleMatDoubleMat() throws { + let m1 = Mat(rows: 2, cols: 2, type: CvType.CV_32FC1) + try m1.put(row: 0, col: 0, data: [1.0, 0.0] as [Float]) + try m1.put(row: 1, col: 0, data: [1.0, 0.0] as [Float]) + let m2 = Mat(rows: 2, cols: 2, type: CvType.CV_32FC1) + try m2.put(row: 0, col: 0, data: [1.0, 0.0] as [Float]) + try m2.put(row: 1, col: 0, data: [1.0, 0.0] as [Float]) + let dmatrix = Mat(rows: 2, cols: 2, type: CvType.CV_32FC1); + try dmatrix.put(row: 0, col: 0, data: [0.001, 0.001] as [Float]) + try dmatrix.put(row: 1, col: 0, data: [0.001, 0.001] as [Float]) + + Core.gemm(src1: m1, src2: m2, alpha: 1.0, src3: dmatrix, beta: 1.0, dst: dst) + + let expected = Mat(rows: 2, cols: 2, type: CvType.CV_32FC1) + try expected.put(row: 0, col: 0, data: [1.001, 0.001] as [Float]) + try expected.put(row: 1, col: 0, data: [1.001, 0.001] as [Float]) + try assertMatEqual(expected, dst, OpenCVTestCase.EPS) + } + + func testGemmMatMatDoubleMatDoubleMatInt() throws { + let m1 = Mat(rows: 2, cols: 2, type: CvType.CV_32FC1) + try m1.put(row: 0, col: 0, data: [1.0, 0.0]) + try m1.put(row: 1, col: 0, data: [1.0, 0.0]) + let m2 = Mat(rows: 2, cols: 2, type: CvType.CV_32FC1) + try m2.put(row: 0, col: 0, data: [1.0, 0.0]) + try m2.put(row: 1, col: 0, data: [1.0, 0.0]) + let dmatrix = Mat(rows: 2, cols: 2, type: CvType.CV_32FC1) + try dmatrix.put(row: 0, col: 0, data: [0.001, 0.001]) + try dmatrix.put(row: 1, col: 0, data: [0.001, 0.001]) + + Core.gemm(src1: m1, src2: m2, alpha: 1.0, src3: dmatrix, beta: 1.0, dst: dst, flags: GemmFlags.GEMM_1_T.rawValue) + + let expected = Mat(rows: 2, cols: 2, type: CvType.CV_32FC1) + try expected.put(row: 0, col: 0, data: [2.001, 0.001]) + try expected.put(row: 1, col: 0, data: [0.001, 0.001]) + try assertMatEqual(expected, dst, OpenCVTestCase.EPS) + } + + func testGetCPUTickCount() { + let cpuCountStart = Core.getCPUTickCount() + Core.sum(src: gray255) + let actualTickCount = Core.getCPUTickCount() + + let expectedTickCount = actualTickCount - cpuCountStart; + XCTAssert(expectedTickCount > 0) + } + + func testGetNumberOfCPUs() { + let cpus = Core.getNumberOfCPUs() + + XCTAssert(ProcessInfo().processorCount <= cpus) + } + + func testGetOptimalDFTSize() { + XCTAssertEqual(1, Core.getOptimalDFTSize(vecsize: 0)) + XCTAssertEqual(135, Core.getOptimalDFTSize(vecsize: 133)) + XCTAssertEqual(15, Core.getOptimalDFTSize(vecsize: 13)) + } + + func testGetTickCount() { + + let startCount = Core.getTickCount() + Core.divide(src1: gray2, src2: gray1, dst: dst) + let endCount = Core.getTickCount() + + let count = endCount - startCount; + XCTAssert(count > 0) + } + + func testGetTickFrequency() { + let freq1 = Core.getTickFrequency() + Core.divide(src1: gray2, src2: gray1, dst: dst) + let freq2 = Core.getTickFrequency() + + XCTAssert(0 < freq1) + XCTAssertEqual(freq1, freq2) + } + + func testHconcat() throws { + let mats = [Mat.eye(rows: 3, cols: 3, type: CvType.CV_8U), Mat.zeros(3, cols: 2, type: CvType.CV_8U)] + + Core.hconcat(src: mats, dst: dst) + + try assertMatEqual(Mat.eye(rows: 3, cols: 5, type: CvType.CV_8U), dst) + } + + func testIdctMatMat() throws { + let mat = Mat(rows: 1, cols: 8, type: CvType.CV_32F) + try mat.put(row: 0, col: 0, data: [1.0, 2.0, 1.0, 0.0, 1.0, 2.0, 3.0, 1.0]) + + Core.idct(src: mat, dst: dst) + + truth = Mat(rows: 1, cols: 8, type: CvType.CV_32F) + + try truth!.put(row: 0, col: 0, data: [3.3769724, -1.6215782, 2.3608727, 0.20730907, -0.86502546, 0.028082132, -0.7673766, 0.10917115]) + try assertMatEqual(truth!, dst, OpenCVTestCase.EPS) + } + + func testIdctMatMatInt() throws { + let mat = Mat(rows: 2, cols: 8, type: CvType.CV_32F) + try mat.put(row: 0, col: 0, data: [1.0, 2.0, 1.0, 0.0, 1.0, 2.0, 3.0, 1.0]) + try mat.put(row: 1, col: 0, data: [1.0, 2.0, 1.0, 0.0, 1.0, 2.0, 3.0, 1.0]) + + Core.idct(src: mat, dst: dst, flags: DftFlags.DCT_ROWS.rawValue) + + truth = Mat(rows: 2, cols: 8, type: CvType.CV_32F) + + try truth!.put(row: 0, col: 0, data: [3.3769724, -1.6215782, 2.3608727, 0.20730907, -0.86502546, 0.028082132, -0.7673766, 0.10917115]) + try truth!.put(row: 1, col: 0, data: [3.3769724, -1.6215782, 2.3608727, 0.20730907, -0.86502546, 0.028082132, -0.7673766, 0.10917115]) + try assertMatEqual(truth!, dst, OpenCVTestCase.EPS) + } + + func testIdftMatMat() throws { + let mat = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try mat.put(row: 0, col: 0, data: [1.0, 2.0, 3.0, 4.0]) + + Core.idft(src: mat, dst: dst) + + truth = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try truth!.put(row: 0, col: 0, data: [9, -9, 1, 3] as [Float]) + try assertMatEqual(truth!, dst, OpenCVTestCase.EPS) + } + + func testIdftMatMatIntInt() throws { + let mat = Mat(rows: 2, cols: 4, type: CvType.CV_32F) + try mat.put(row: 0, col: 0, data: [1.0, 2.0, 3.0, 4.0] as [Float]) + try mat.put(row: 1, col: 0, data: [1.0, 2.0, 3.0, 4.0] as [Float]) + let dst = Mat() + + Core.idft(src: mat, dst: dst, flags: DftFlags.DFT_REAL_OUTPUT.rawValue, nonzeroRows: 1) + + truth = Mat(rows: 2, cols: 4, type: CvType.CV_32F) + try truth!.put(row: 0, col: 0, data: [18, -18, 2, 6] as [Float]) + try truth!.put(row: 1, col: 0, data: [0, 0, 0, 0] as [Float]) + try assertMatEqual(truth!, dst, OpenCVTestCase.EPS) + } + + func testInRange() throws { + let gray0copy = gray0.clone() + try gray0copy.put(row: 1, col: 1, data: [100, -105, -55] as [Int8]) + + Core.inRange(src: gray0copy, lowerb: Scalar(120), upperb: Scalar(160), dst: dst) + + var vals = [Int8](repeating: 0, count: 3) + try dst.get(row: 1, col: 1, data: &vals) + + XCTAssertEqual(0, vals[0]) + XCTAssertEqual(-1, vals[1]) + XCTAssertEqual(0, vals[2]) + XCTAssertEqual(1, Core.countNonZero(src: dst)) + } + + func testInsertChannel() throws { + dst = rgba128.clone() + Core.insertChannel(src: gray0, dst: dst, coi: 0) + Core.insertChannel(src: gray0, dst: dst, coi: 1) + Core.insertChannel(src: gray0, dst: dst, coi: 2) + Core.insertChannel(src: gray0, dst: dst, coi: 3) + + try assertMatEqual(rgba0, dst) + } + + func testInvertMatMat() throws { + let src = Mat(rows: 2, cols: 2, type: CvType.CV_32F) + try src.put(row: 0, col: 0, data: [1.0] as [Float]) + try src.put(row: 0, col: 1, data: [2.0] as [Float]) + try src.put(row: 1, col: 0, data: [1.5] as [Float]) + try src.put(row: 1, col: 1, data: [4.0] as [Float]) + + Core.invert(src: src, dst: dst) + + truth = Mat(rows: 2, cols: 2, type: CvType.CV_32F) + + try truth!.put(row: 0, col: 0, data: [4.0] as [Float]) + try truth!.put(row: 0, col: 1, data: [-2.0] as [Float]) + try truth!.put(row: 1, col: 0, data: [-1.5] as [Float]) + try truth!.put(row: 1, col: 1, data: [1.0] as [Float]) + try assertMatEqual(truth!, dst, OpenCVTestCase.EPS) + } + + func testInvertMatMatInt() throws { + let src = Mat.eye(rows: 3, cols: 3, type: CvType.CV_32FC1) + try src.put(row: 0, col: 2, data: [1] as [Float]) + + let cond = Core.invert(src: src, dst: dst, flags: DecompTypes.DECOMP_SVD.rawValue) + + truth = Mat.eye(rows: 3, cols: 3, type: CvType.CV_32FC1) + try truth!.put(row: 0, col: 2, data: [-1] as [Float]) + try assertMatEqual(truth!, dst, OpenCVTestCase.EPS) + XCTAssertEqual(0.3819660544395447, cond, accuracy:OpenCVTestCase.EPS) + } + + func testKmeansMatIntMatTermCriteriaIntInt() throws { + let data = Mat(rows: 4, cols: 5, type: CvType.CV_32FC1) + try data.put(row: 0, col: 0, data: [1, 2, 3, 4, 5] as [Float]) + try data.put(row: 1, col: 0, data: [2, 3, 4, 5, 6] as [Float]) + try data.put(row: 2, col: 0, data: [5, 4, 3, 2, 1] as [Float]) + try data.put(row: 3, col: 0, data: [6, 5, 4, 3, 2] as [Float]) + let criteria = TermCriteria(type: TermCriteria.eps, maxCount: 0, epsilon: OpenCVTestCase.EPS) + let labels = Mat() + + Core.kmeans(data: data, K: 2, bestLabels: labels, criteria: criteria, attempts: 1, flags: KmeansFlags.KMEANS_PP_CENTERS.rawValue) + + var first_center = [Int32](repeating: 0, count: 1) + try labels.get(row: 0, col: 0, data: &first_center) + let c1 = first_center[0] + let expected_labels = Mat(rows: 4, cols: 1, type: CvType.CV_32S) + try expected_labels.put(row: 0, col: 0, data: [c1, c1, 1 - c1, 1 - c1]) + try assertMatEqual(expected_labels, labels) + } + + func testKmeansMatIntMatTermCriteriaIntIntMat() throws { + let data = Mat(rows: 4, cols: 5, type: CvType.CV_32FC1) + try data.put(row: 0, col: 0, data: [1, 2, 3, 4, 5] as [Float]) + try data.put(row: 1, col: 0, data: [2, 3, 4, 5, 6] as [Float]) + try data.put(row: 2, col: 0, data: [5, 4, 3, 2, 1] as [Float]) + try data.put(row: 3, col: 0, data: [6, 5, 4, 3, 2] as [Float]) + let criteria = TermCriteria(type:TermCriteria.eps, maxCount: 0, epsilon: OpenCVTestCase.EPS) + let labels = Mat() + let centers = Mat() + + Core.kmeans(data: data, K: 2, bestLabels: labels, criteria: criteria, attempts: 6, flags: KmeansFlags.KMEANS_RANDOM_CENTERS.rawValue, centers: centers) + + var first_center = [Int32](repeating: 0, count: 1) + try labels.get(row: 0, col: 0, data: &first_center) + let c1 = first_center[0] + let expected_labels = Mat(rows: 4, cols: 1, type: CvType.CV_32S) + try expected_labels.put(row: 0, col: 0, data: [c1, c1, 1 - c1, 1 - c1]) + let expected_centers = Mat(rows: 2, cols: 5, type: CvType.CV_32FC1) + try expected_centers.put(row: c1, col: 0, data: [1.5, 2.5, 3.5, 4.5, 5.5] as [Float]) + try expected_centers.put(row: 1 - c1, col: 0, data: [5.5, 4.5, 3.5, 2.5, 1.5] as [Float]) + try assertMatEqual(expected_labels, labels) + try assertMatEqual(expected_centers, centers, OpenCVTestCase.EPS) + } + + func testLineMatPointPointScalar() { + let nPoints = min(gray0.cols(), gray0.rows()) + let point1 = Point(x: 0, y: 0) + let point2 = Point(x: nPoints, y: nPoints) + let color = Scalar(255) + + Imgproc.line(img: gray0, pt1: point1, pt2: point2, color: color) + + XCTAssert(nPoints == Core.countNonZero(src: gray0)) + } + + func testLineMatPointPointScalarInt() { + let nPoints = min(gray0.cols(), gray0.rows()) + let point1 = Point(x: 0, y: 0) + let point2 = Point(x: nPoints, y: nPoints) + + Imgproc.line(img: gray0, pt1: point1, pt2: point2, color: colorWhite, thickness: 1) + + XCTAssert(nPoints == Core.countNonZero(src: gray0)) + } + + func testLineMatPointPointScalarIntIntInt() { + let nPoints = min(gray0.cols(), gray0.rows()) + let point1 = Point(x: 3, y: 4) + let point2 = Point(x: nPoints, y: nPoints) + let point1_4 = Point(x: 3 * 4, y: 4 * 4) + let point2_4 = Point(x: nPoints * 4, y: nPoints * 4) + + Imgproc.line(img: gray0, pt1: point2, pt2: point1, color: colorWhite, thickness: 2, lineType: .LINE_8, shift: 0) + + XCTAssertFalse(0 == Core.countNonZero(src: gray0)) + + Imgproc.line(img: gray0, pt1: point2_4, pt2: point1_4, color: colorBlack, thickness: 2, lineType: .LINE_8, shift: 2) + + XCTAssertEqual(0, Core.countNonZero(src: gray0)) + } + + func testLog() throws { + let mat = Mat(rows: 1, cols: 4, type: CvType.CV_32FC1) + try mat.put(row: 0, col: 0, data: [1.0, 10.0, 100.0, 1000.0]) + + Core.log(src: mat, dst: dst) + + let expected = Mat(rows: 1, cols: 4, type: CvType.CV_32FC1) + try expected.put(row: 0, col: 0, data: [0, 2.3025851, 4.6051702, 6.9077554]) + try assertMatEqual(expected, dst, OpenCVTestCase.EPS) + } + + func testLUTMatMatMat() throws { + let lut = Mat(rows: 1, cols: 256, type: CvType.CV_8UC1) + lut.setTo(scalar: Scalar(0)) + + Core.LUT(src: grayRnd, lut: lut, dst: dst) + + try assertMatEqual(gray0, dst) + + lut.setTo(scalar: Scalar(255)) + + Core.LUT(src: grayRnd, lut: lut, dst: dst) + + try assertMatEqual(gray255, dst) + } + + func testMagnitude() throws { + let x = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + let y = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try x.put(row: 0, col: 0, data: [3.0, 5.0, 9.0, 6.0]) + try y.put(row: 0, col: 0, data: [4.0, 12.0, 40.0, 8.0]) + + Core.magnitude(x: x, y: y, magnitude: dst) + + let out = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try out.put(row: 0, col: 0, data: [5.0, 13.0, 41.0, 10.0]) + try assertMatEqual(out, dst, OpenCVTestCase.EPS) + + Core.magnitude(x: gray0_32f, y: gray255_32f, magnitude: dst) + + try assertMatEqual(gray255_32f, dst, OpenCVTestCase.EPS) + } + + func testMahalanobis() { + Core.setRNGSeed(seed: 45) + let src = Mat(rows: OpenCVTestCase.matSize, cols: OpenCVTestCase.matSize, type: CvType.CV_32F) + Core.randu(dst: src, low: -128, high: 128) + + var covar = Mat(rows: OpenCVTestCase.matSize, cols: OpenCVTestCase.matSize, type: CvType.CV_32F) + let mean = Mat(rows: 1, cols: OpenCVTestCase.matSize, type: CvType.CV_32F) + Core.calcCovarMatrix(samples: src, covar: covar, mean: mean, flags: CovarFlags.COVAR_ROWS.rawValue | CovarFlags.COVAR_NORMAL.rawValue, ctype: CvType.CV_32F) + covar = covar.inv() + + let line1 = src.row(0) + let line2 = src.row(1) + + var d = Core.Mahalanobis(v1: line1, v2: line1, icovar: covar) + + XCTAssertEqual(0.0, d) + + d = Core.Mahalanobis(v1: line1, v2: line2, icovar: covar) + + XCTAssert(d > 0.0) + } + + func testMax() throws { + Core.max(src1: gray0, src2: gray255, dst: dst) + + try assertMatEqual(gray255, dst) + + let x = Mat(rows: 1, cols: 1, type: CvType.CV_32F) + let y = Mat(rows: 1, cols: 1, type: CvType.CV_32F) + try x.put(row: 0, col: 0, data: [23.0]) + try y.put(row: 0, col: 0, data: [4.0]) + + Core.max(src1: x, src2: y, dst: dst) + + let truth = Mat(rows: 1, cols: 1, type: CvType.CV_32F) + try truth.put(row: 0, col: 0, data: [23.0]) + try assertMatEqual(truth, dst, OpenCVTestCase.EPS) + } + + func testMeanMat() { + let mean = Core.mean(src: makeMask(gray128)) + + assertScalarEqual(Scalar(64), mean, OpenCVTestCase.EPS) + } + + func testMeanMatMat() { + let mask1 = makeMask(gray1.clone()) + let mask2 = makeMask(gray0, vals: [1]) + + let mean1 = Core.mean(src: grayRnd, mask: mask1) + let mean2 = Core.mean(src: grayRnd, mask: mask2) + let mean = Core.mean(src: grayRnd, mask: gray1) + + assertScalarEqual(mean, Scalar(0.5 * (mean1.val[0].doubleValue + mean2.val[0].doubleValue)), OpenCVTestCase.EPS) + } + + func testMeanStdDevMatMatMat() { + let mean = DoubleVector() + let stddev = DoubleVector() + Core.meanStdDev(src: rgbLena, mean: mean, stddev: stddev) + + let expectedMean = [105.3989906311035, 99.56269836425781, 179.7303047180176] + let expectedDev = [33.74205485167219, 52.8734582803278, 49.01569488056406] + + assertArrayEquals(expectedMean as [NSNumber], mean.array as [NSNumber], OpenCVTestCase.EPS) + assertArrayEquals(expectedDev as [NSNumber], stddev.array as [NSNumber], OpenCVTestCase.EPS) + } + + func testMeanStdDevMatMatMatMat() { + var submat = grayRnd.submat(rowStart: 0, rowEnd: grayRnd.rows() / 2, colStart: 0, colEnd: grayRnd.cols() / 2) + submat.setTo(scalar: Scalar(33)) + let mask = gray0.clone() + submat = mask.submat(rowStart: 0, rowEnd: mask.rows() / 2, colStart: 0, colEnd: mask.cols() / 2) + submat.setTo(scalar: Scalar(1)) + let mean = DoubleVector() + let stddev = DoubleVector() + + Core.meanStdDev(src: grayRnd, mean: mean, stddev: stddev, mask: mask) + + let expectedMean = [33] + let expectedDev = [0] + + assertArrayEquals(expectedMean as [NSNumber], mean.array as [NSNumber], OpenCVTestCase.EPS) + assertArrayEquals(expectedDev as [NSNumber], stddev.array as [NSNumber], OpenCVTestCase.EPS) + } + + func testMerge() throws { + let src1 = Mat(rows: 2, cols: 2, type: CvType.CV_32FC1, scalar: Scalar(1)) + let src2 = Mat(rows: 2, cols: 2, type: CvType.CV_32FC1, scalar: Scalar(2)) + let src3 = Mat(rows: 2, cols: 2, type: CvType.CV_32FC1, scalar: Scalar(3)) + let srcArray = [src1, src2, src3] + + Core.merge(mv: srcArray, dst: dst) + + truth = Mat(rows: 2, cols: 2, type: CvType.CV_32FC3, scalar: Scalar(1, 2, 3)) + try assertMatEqual(truth!, dst, OpenCVTestCase.EPS) + } + + func testMin() throws { + Core.min(src1: gray0, src2: gray255, dst: dst) + + try assertMatEqual(gray0, dst) + } + + func testMinMaxLocMat() throws { + let minVal:Double = 1 + let maxVal:Double = 10 + let minLoc = Point(x: gray3.cols() / 4, y: gray3.rows() / 2) + let maxLoc = Point(x: gray3.cols() / 2, y: gray3.rows() / 4) + let gray3copy = gray3.clone() + try gray3copy.put(row: minLoc.y, col: minLoc.x, data: [minVal]) + try gray3copy.put(row: maxLoc.y, col: maxLoc.x, data: [maxVal]) + + let mmres = Core.minMaxLoc(gray3copy) + + XCTAssertEqual(minVal, mmres.minVal) + XCTAssertEqual(maxVal, mmres.maxVal) + assertPointEquals(minLoc, mmres.minLoc) + assertPointEquals(maxLoc, mmres.maxLoc) + } + + func testMinMaxLocMatMat() throws { + let src = Mat(rows: 4, cols: 4, type: CvType.CV_8U) + try src.put(row: 0, col: 0, data: [2, 4, 27, 3] as [Int8]) + try src.put(row: 1, col: 0, data: [0, 8, 7, -126] as [Int8]) + try src.put(row: 2, col: 0, data: [13, 4, 13, 4] as [Int8]) + try src.put(row: 3, col: 0, data: [6, 4, 2, 13] as [Int8]) + let mask = Mat(rows: 4, cols: 4, type: CvType.CV_8U, scalar: Scalar(0)) + mask.submat(rowStart: 1, rowEnd: 3, colStart: 1, colEnd: 4).setTo(scalar: Scalar(1)) + + let res = Core.minMaxLoc(src, mask: mask) + + XCTAssertEqual(4.0, res.minVal) + XCTAssertEqual(130.0, res.maxVal) + assertPointEquals(Point(x: 1, y: 2), res.minLoc) + assertPointEquals(Point(x: 3, y: 1), res.maxLoc) + } + + func testMixChannels() throws { + let rgba0Copy = rgba0.clone() + rgba0Copy.setTo(scalar: Scalar(10, 20, 30, 40)) + let src = [rgba0Copy] + let dst = [gray3, gray2, gray1, gray0, getMat(CvType.CV_8UC3, vals: [0, 0, 0])] + let fromTo = IntVector([ + 3, 0, + 3, 1, + 2, 2, + 0, 3, + 2, 4, + 1, 5, + 0, 6]) + + Core.mixChannels(src: src, dst: dst, fromTo: fromTo) + + try assertMatEqual(getMat(CvType.CV_8U, vals: [40]), dst[0]) + try assertMatEqual(getMat(CvType.CV_8U, vals: [40]), dst[1]) + try assertMatEqual(getMat(CvType.CV_8U, vals: [30]), dst[2]) + try assertMatEqual(getMat(CvType.CV_8U, vals: [10]), dst[3]) + try assertMatEqual(getMat(CvType.CV_8UC3, vals: [30, 20, 10]), dst[4]) + } + + func testMulSpectrumsMatMatMatInt() throws { + let src1 = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try src1.put(row: 0, col: 0, data: [1.0, 2.0, 3.0, 4.0]) + + let src2 = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try src2.put(row: 0, col: 0, data: [1.0, 2.0, 3.0, 4.0]) + + Core.mulSpectrums(a: src1, b: src2, c: dst, flags: DftFlags.DFT_ROWS.rawValue) + + let expected = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try expected.put(row: 0, col: 0, data: [1, -5, 12, 16] as [Float]) + + try assertMatEqual(expected, dst, OpenCVTestCase.EPS) + } + + func testMulSpectrumsMatMatMatIntBoolean() throws { + let src1 = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try src1.put(row: 0, col: 0, data: [1.0, 2.0, 3.0, 4.0]) + let src2 = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try src2.put(row: 0, col: 0, data: [1.0, 2.0, 3.0, 4.0]) + + Core.mulSpectrums(a: src1, b: src2, c: dst, flags: DftFlags.DFT_ROWS.rawValue, conjB: true) + + let expected = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try expected.put(row: 0, col: 0, data: [1, 13, 0, 16] as [Float]) + try assertMatEqual(expected, dst, OpenCVTestCase.EPS) + } + + func testMultiplyMatMatMat() throws { + Core.multiply(src1: gray0, src2: gray255, dst: dst) + + try assertMatEqual(gray0, dst) + } + + func testMultiplyMatMatMatDouble() throws { + Core.multiply(src1: gray1, src2: gray1, dst: dst, scale: 2.0) + + try assertMatEqual(gray2, dst) + + } + + func testMultiplyMatMatMatDoubleInt() throws { + Core.multiply(src1: gray1, src2: gray2, dst: dst, scale: 1.5, dtype: CvType.CV_32F) + + try assertMatEqual(gray3_32f, dst, OpenCVTestCase.EPS) + } + + func testMulTransposedMatMatBoolean() throws { + Core.mulTransposed(src: grayE_32f, dst: dst, aTa: true) + + try assertMatEqual(grayE_32f, dst, OpenCVTestCase.EPS) + } + + func testMulTransposedMatMatBooleanMatDouble() throws { + Core.mulTransposed(src: grayE_32f, dst: dst, aTa: true, delta: gray0_32f, scale: 2) + + truth = gray0_32f; + truth!.diag().setTo(scalar: Scalar(2)) + try assertMatEqual(truth!, dst, OpenCVTestCase.EPS) + } + + func testMulTransposedMatMatBooleanMatDoubleInt() throws { + let a = getMat(CvType.CV_32F, vals: [1]) + + Core.mulTransposed(src: a, dst: dst, aTa: true, delta: gray0_32f, scale: 3, dtype: CvType.CV_64F) + + try assertMatEqual(getMat(CvType.CV_64F, vals: [3 * a.rows()] as [NSNumber]), dst, OpenCVTestCase.EPS) + } + + func testNormalizeMatMat() throws { + let m = gray0.clone() + m.diag().setTo(scalar: Scalar(2)) + + Core.normalize(src: m, dst: dst) + + try assertMatEqual(gray0, dst) + } + + func testNormalizeMatMatDoubleDoubleInt() throws { + let src = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try src.put(row: 0, col: 0, data: [1.0, 2.0, 3.0, 4.0]) + + Core.normalize(src: src, dst: dst, alpha: 1.0, beta: 2.0, norm_type: .NORM_INF) + + let expected = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try expected.put(row: 0, col: 0, data: [0.25, 0.5, 0.75, 1]) + try assertMatEqual(expected, dst, OpenCVTestCase.EPS) + } + + func testNormalizeMatMatDoubleDoubleIntInt() throws { + let src = Mat(rows: 1, cols: 5, type: CvType.CV_32F) + try src.put(row: 0, col: 0, data: [0, 1, 2, 3, 4] as [Float]) + + Core.normalize(src: src, dst: dst, alpha: 1, beta: 2, norm_type: .NORM_MINMAX, dtype: CvType.CV_64F) + + let expected = Mat(rows: 1, cols: 5, type: CvType.CV_64F) + try expected.put(row: 0, col: 0, data: [1, 1.25, 1.5, 1.75, 2]) + try assertMatEqual(expected, dst, OpenCVTestCase.EPS) + } + + func testNormalizeMatMatDoubleDoubleIntIntMat() throws { + let src = Mat(rows: 1, cols: 5, type: CvType.CV_32F) + try src.put(row: 0, col: 0, data: [0, 1, 2, 3, 4] as [Float]) + let mask = Mat(rows: 1, cols: 5, type: CvType.CV_8U) + try mask.put(row: 0, col: 0, data: [1, 0, 0, 0, 1] as [Int8]) + dst = src.clone() + + Core.normalize(src: src, dst: dst, alpha: 1, beta: 2, norm_type: .NORM_MINMAX, dtype: CvType.CV_32F, mask: mask) + + let expected = Mat(rows: 1, cols: 5, type: CvType.CV_32F) + try expected.put(row: 0, col: 0, data: [1, 1, 2, 3, 2] as [Float]) + try assertMatEqual(expected, dst, OpenCVTestCase.EPS) + } + + func testNormMat() throws { + let n = Core.norm(src1: gray1) + + XCTAssertEqual(10, n) + } + + func testNormMatInt() throws { + let n = Core.norm(src1: gray127, normType: .NORM_INF) + + XCTAssertEqual(127, n) + } + + func testNormMatIntMat() throws { + let n = Core.norm(src1: gray3, normType: .NORM_L1, mask: gray0) + + XCTAssertEqual(0.0, n) + } + + func testNormMatMat() throws { + let n = Core.norm(src1: gray0, src2: gray1) + + XCTAssertEqual(10.0, n) + } + + func testNormMatMatInt() throws { + let n = Core.norm(src1: gray127, src2: gray1, normType: .NORM_INF) + + XCTAssertEqual(126.0, n) + } + + func testNormMatMatIntMat() throws { + let n = Core.norm(src1: gray3, src2: gray0, normType: .NORM_L1, mask: makeMask(gray0.clone(), vals: [1])) + + XCTAssertEqual(150.0, n) + } + + func testPCABackProject() throws { + let mean = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try mean.put(row: 0, col: 0, data: [2, 4, 4, 8] as [Float]) + let vectors = Mat(rows: 1, cols: 4, type: CvType.CV_32F, scalar: Scalar(0)) + try vectors.put(row: 0, col: 0, data: [0.2, 0.4, 0.4, 0.8]) + let data = Mat(rows: 3, cols: 1, type: CvType.CV_32F) + try data.put(row: 0, col: 0, data: [-5, 0, -10] as [Float]) + let result = Mat() + + Core.PCABackProject(data: data, mean: mean, eigenvectors: vectors, result: result) + + let truth = Mat(rows: 3, cols: 4, type: CvType.CV_32F) + try truth.put(row: 0, col: 0, data: [1, 2, 2, 4] as [Float]) + try truth.put(row: 1, col: 0, data: [2, 4, 4, 8] as [Float]) + try truth.put(row: 2, col: 0, data: [0, 0, 0, 0] as [Float]) + try assertMatEqual(truth, result, OpenCVTestCase.EPS) + } + + func testPCAComputeMatMatMat() throws { + let data = Mat(rows: 3, cols: 4, type: CvType.CV_32F) + try data.put(row: 0, col: 0, data: [1, 2, 2, 4] as [Float]) + try data.put(row: 1, col: 0, data: [2, 4, 4, 8] as [Float]) + try data.put(row: 2, col: 0, data: [3, 6, 6, 12] as [Float]) + let mean = Mat() + let vectors = Mat() + + Core.PCACompute(data: data, mean: mean, eigenvectors: vectors) + let mean_truth = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try mean_truth.put(row: 0, col: 0, data: [2, 4, 4, 8] as [Float]) + let vectors_truth = Mat(rows: 3, cols: 4, type: CvType.CV_32F, scalar: Scalar(0)) + try vectors_truth.put(row: 0, col: 0, data: [0.2, 0.4, 0.4, 0.8] as [Float]) + try assertMatEqual(mean_truth, mean, OpenCVTestCase.EPS) + + // eigenvectors are normalized (length = 1), + // but direction is unknown (v and -v are both eigen vectors) + // so this direct check doesn't work: + // try assertMatEqual(vectors_truth, vectors, OpenCVTestCase.EPS) + for i in 0..<1 { + let vec0 = vectors_truth.row(Int32(i)) + let vec1 = vectors.row(Int32(i)) + let vec1_ = Mat() + Core.subtract(src1: Mat(rows: 1, cols: 4, type: CvType.CV_32F, scalar: Scalar(0)), src2: vec1, dst: vec1_) + let scale1 = Core.norm(src1: vec0, src2: vec1) + let scale2 = Core.norm(src1: vec0, src2: vec1_) + XCTAssert(min(scale1, scale2) < OpenCVTestCase.EPS) + } + } + + func testPCAComputeMatMatMatInt() throws { + let data = Mat(rows: 3, cols: 4, type: CvType.CV_32F) + try data.put(row: 0, col: 0, data: [1, 2, 2, 4] as [Float]) + try data.put(row: 1, col: 0, data: [2, 4, 4, 8] as [Float]) + try data.put(row: 2, col: 0, data: [3, 6, 6, 12] as [Float]) + let mean = Mat() + let vectors = Mat() + + Core.PCACompute(data:data, mean:mean, eigenvectors:vectors, maxComponents:1) + + let mean_truth = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try mean_truth.put(row: 0, col: 0, data: [2, 4, 4, 8] as [Float]) + let vectors_truth = Mat(rows: 1, cols: 4, type: CvType.CV_32F, scalar: Scalar(0)) + try vectors_truth.put(row: 0, col: 0, data: [0.2, 0.4, 0.4, 0.8] as [Float]) + try assertMatEqual(mean_truth, mean, OpenCVTestCase.EPS) + // eigenvectors are normalized (length = 1), + // but direction is unknown (v and -v are both eigen vectors) + // so this direct check doesn't work: + // try assertMatEqual(vectors_truth, vectors, OpenCVTestCase.EPS) + for i in 0..<1 { + let vec0 = vectors_truth.row(Int32(i)) + let vec1 = vectors.row(Int32(i)) + let vec1_ = Mat() + Core.subtract(src1: Mat(rows: 1, cols: 4, type: CvType.CV_32F, scalar: Scalar(0)), src2: vec1, dst: vec1_) + let scale1 = Core.norm(src1: vec0, src2: vec1) + let scale2 = Core.norm(src1: vec0, src2: vec1_) + XCTAssert(min(scale1, scale2) < OpenCVTestCase.EPS) + } + } + + func testPCAProject() throws { + let mean = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try mean.put(row: 0, col: 0, data: [2, 4, 4, 8] as [Float]) + let vectors = Mat(rows: 1, cols: 4, type: CvType.CV_32F, scalar: Scalar(0)) + try vectors.put(row: 0, col: 0, data: [0.2, 0.4, 0.4, 0.8] as [Float]) + let data = Mat(rows: 3, cols: 4, type: CvType.CV_32F) + try data.put(row: 0, col: 0, data: [1, 2, 2, 4] as [Float]) + try data.put(row: 1, col: 0, data: [2, 4, 4, 8] as [Float]) + try data.put(row: 2, col: 0, data: [0, 0, 0, 0] as [Float]) + let result = Mat() + + Core.PCAProject(data: data, mean: mean, eigenvectors: vectors, result: result) + + let truth = Mat(rows: 3, cols: 1, type: CvType.CV_32F) + try truth.put(row: 0, col: 0, data: [-5, 0, -10] as [Float]) + try assertMatEqual(truth, result, OpenCVTestCase.EPS) + } + + func testPerspectiveTransform() throws { + let src = Mat(rows: OpenCVTestCase.matSize, cols: OpenCVTestCase.matSize, type: CvType.CV_32FC2) + Core.randu(dst: src, low: 0, high: 256) + let transformMatrix = Mat.eye(rows: 3, cols: 3, type: CvType.CV_32F) + + Core.perspectiveTransform(src: src, dst: dst, m: transformMatrix) + try assertMatEqual(src, dst, OpenCVTestCase.EPS) + } + + func testPerspectiveTransform3D() throws { + let src = Mat(rows: OpenCVTestCase.matSize, cols: OpenCVTestCase.matSize, type: CvType.CV_32FC3) + Core.randu(dst: src, low: 0, high: 256) + let transformMatrix = Mat.eye(rows: 4, cols: 4, type: CvType.CV_32F) + + Core.perspectiveTransform(src: src, dst: dst, m: transformMatrix) + + try assertMatEqual(src, dst, OpenCVTestCase.EPS) + } + + func testPhaseMatMatMat() throws { + let x = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try x.put(row: 0, col: 0, data: [10.0, 10.0, 20.0, 5.0] as [Float]) + let y = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try y.put(row: 0, col: 0, data: [20.0, 15.0, 20.0, 20.0] as [Float]) + let gold = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try gold.put(row: 0, col: 0, data: [atan2rad(20, 10), atan2rad(15, 10), atan2rad(20, 20), atan2rad(20, 5)]) + + Core.phase(x: x, y: y, angle: dst) + + try assertMatEqual(gold, dst, OpenCVTestCase.EPS) + } + + func testPhaseMatMatMatBoolean() throws { + let x = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try x.put(row: 0, col: 0, data: [10.0, 10.0, 20.0, 5.0] as [Float]) + let y = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try y.put(row: 0, col: 0, data: [20.0, 15.0, 20.0, 20.0] as [Float]) + let gold = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try gold.put(row: 0, col: 0, data: [atan2deg(20, 10), atan2deg(15, 10), atan2deg(20, 20), atan2deg(20, 5)]) + + Core.phase(x: x, y: y, angle: dst, angleInDegrees: true) + + try assertMatEqual(gold, dst, OpenCVTestCase.EPS * 180 / Double.pi) + } + + func testPolarToCartMatMatMatMat() throws { + let magnitude = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try magnitude.put(row: 0, col: 0, data: [5.0, 10.0, 13.0]) + let angle = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try angle.put(row: 0, col: 0, data: [0.92729962, 0.92729962, 1.1759995]) + let xCoordinate = Mat() + let yCoordinate = Mat() + + Core.polarToCart(magnitude: magnitude, angle: angle, x: xCoordinate, y: yCoordinate) + + let x = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try x.put(row: 0, col: 0, data: [3.0, 6.0, 5, 0]) + let y = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try y.put(row: 0, col: 0, data: [4.0, 8.0, 12.0]) + try assertMatEqual(x, xCoordinate, OpenCVTestCase.EPS) + try assertMatEqual(y, yCoordinate, OpenCVTestCase.EPS) + } + + func testPolarToCartMatMatMatMatBoolean() throws { + let magnitude = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try magnitude.put(row: 0, col: 0, data: [5.0, 10.0, 13.0]) + let angle = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try angle.put(row: 0, col: 0, data: [0.92729962, 0.92729962, 1.1759995]) + let xCoordinate = Mat() + let yCoordinate = Mat() + + Core.polarToCart(magnitude: magnitude, angle: angle, x: xCoordinate, y: yCoordinate, angleInDegrees: true) + + let x = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try x.put(row: 0, col: 0, data: [4.9993458, 9.9986916, 12.997262]) + let y = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try y.put(row: 0, col: 0, data: [0.080918625, 0.16183725, 0.26680708]) + try assertMatEqual(x, xCoordinate, OpenCVTestCase.EPS) + try assertMatEqual(y, yCoordinate, OpenCVTestCase.EPS) + } + + func testPow() throws { + Core.pow(src: gray2, power: 7, dst: dst) + + try assertMatEqual(gray128, dst) + } + + func testRandn() { + Core.randn(dst: gray0, mean: 100, stddev: 23) + + XCTAssertEqual(100, Core.mean(src: gray0).val[0] as! Double, accuracy:23 / 2) + } + + func testRandShuffleMat() throws { + let original = Mat(rows: 1, cols: 10, type: CvType.CV_32F) + try original.put(row: 0, col: 0, data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] as [Float]) + let shuffled = original.clone() + + Core.randShuffle(dst: shuffled) + + try assertMatNotEqual(original, shuffled, OpenCVTestCase.EPS) + let dst1 = Mat() + let dst2 = Mat() + Core.sort(src: original, dst: dst1, flags: SortFlags.SORT_ASCENDING.rawValue) + Core.sort(src: shuffled, dst: dst2, flags: SortFlags.SORT_ASCENDING.rawValue) + try assertMatEqual(dst1, dst2, OpenCVTestCase.EPS) + } + + func testRandShuffleMatDouble() throws { + let original = Mat(rows: 1, cols: 10, type: CvType.CV_32F) + try original.put(row: 0, col: 0, data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] as [Float]) + let shuffled = original.clone() + + Core.randShuffle(dst: shuffled, iterFactor: 10) + + try assertMatNotEqual(original, shuffled, OpenCVTestCase.EPS) + let dst1 = Mat() + let dst2 = Mat() + Core.sort(src: original, dst: dst1, flags: SortFlags.SORT_ASCENDING.rawValue) + Core.sort(src: shuffled, dst: dst2, flags: SortFlags.SORT_ASCENDING.rawValue) + try assertMatEqual(dst1, dst2, OpenCVTestCase.EPS) + } + + func testRandu() { + Core.randu(dst: gray0, low: 3, high: 23) + XCTAssert(Core.checkRange(a: gray0, quiet: true, minVal: 3, maxVal: 23)) + } + + func testRectangleMatPointPointScalar() { + let bottomRight = Point(x: gray0.cols() / 2, y: gray0.rows() / 2) + let topLeft = Point(x: 0, y: 0) + let color = Scalar(128) + + Imgproc.rectangle(img: gray0, pt1: bottomRight, pt2: topLeft, color: color) + + XCTAssert(0 != Core.countNonZero(src: gray0)) + } + + func testRectangleMatPointPointScalarInt() { + let bottomRight = Point(x: gray0.cols(), y: gray0.rows()) + let topLeft = Point(x: 0, y: 0) + let color = Scalar(128) + + Imgproc.rectangle(img: gray0, pt1: bottomRight, pt2: topLeft, color: color, thickness: 2) + Imgproc.rectangle(img: gray0, pt1: bottomRight, pt2: topLeft, color: colorBlack) + + XCTAssert(0 != Core.countNonZero(src: gray0)) + } + + func testRectangleMatPointPointScalarIntInt() { + let bottomRight = Point(x: gray0.cols() / 2, y: gray0.rows() / 2) + let topLeft = Point(x: 0, y: 0) + let color = Scalar(128) + + Imgproc.rectangle(img: gray0, pt1: bottomRight, pt2: topLeft, color: color, thickness: 2, lineType: .LINE_AA, shift: 0) + Imgproc.rectangle(img: gray0, pt1: bottomRight, pt2: topLeft, color: colorBlack, thickness: 2, lineType: .LINE_4, shift: 0) + + XCTAssert(0 != Core.countNonZero(src: gray0)) + } + + func testRectangleMatPointPointScalarIntIntInt() { + let bottomRight1 = Point(x: gray0.cols(), y: gray0.rows()) + let bottomRight2 = Point(x: gray0.cols() / 2, y: gray0.rows() / 2) + let topLeft = Point(x: 0, y: 0) + let color = Scalar(128) + + Imgproc.rectangle(img: gray0, pt1: bottomRight1, pt2: topLeft, color: color, thickness: 2, lineType: .LINE_8, shift: 1) + + XCTAssert(0 != Core.countNonZero(src: gray0)) + + Imgproc.rectangle(img: gray0, pt1: bottomRight2, pt2: topLeft, color: colorBlack, thickness: 2, lineType: .LINE_8, shift: 0) + + XCTAssertEqual(0, Core.countNonZero(src: gray0)) + } + + func testReduceMatMatIntInt() throws { + let src = Mat(rows: 2, cols: 2, type: CvType.CV_32F) + + try src.put(row: 0, col: 0, data: [1, 0] as [Float]) + try src.put(row: 1, col: 0, data: [3, 0] as [Float]) + + Core.reduce(src: src, dst: dst, dim: 0, rtype: Int32(Core.REDUCE_AVG)) + + let out = Mat(rows: 1, cols: 2, type: CvType.CV_32F) + try out.put(row: 0, col: 0, data: [2, 0] as [Float]) + try assertMatEqual(out, dst, OpenCVTestCase.EPS) + } + + func testReduceMatMatIntIntInt() throws { + let src = Mat(rows: 2, cols: 2, type: CvType.CV_32F) + try src.put(row: 0, col: 0, data: [1, 0] as [Float]) + try src.put(row: 1, col: 0, data: [2, 3] as [Float]) + + Core.reduce(src: src, dst: dst, dim: 1, rtype: Int32(Core.REDUCE_SUM), dtype: CvType.CV_64F) + + let out = Mat(rows: 2, cols: 1, type: CvType.CV_64F) + try out.put(row: 0, col: 0, data: [1, 5] as [Double]) + try assertMatEqual(out, dst, OpenCVTestCase.EPS) + } + + func testRepeat() throws { + let src = Mat(rows: 1, cols: 2, type: CvType.CV_32F, scalar: Scalar(0)) + + Core.repeat(src: src, ny: OpenCVTestCase.matSize, nx: OpenCVTestCase.matSize / 2, dst: dst) + + try assertMatEqual(gray0_32f, dst, OpenCVTestCase.EPS) + } + + func testScaleAdd() throws { + Core.scaleAdd(src1: gray3, alpha: 2.0, src2: gray3, dst: dst) + + try assertMatEqual(gray9, dst) + } + + func testSetIdentityMat() throws { + Core.setIdentity(mtx: gray0_32f) + + try assertMatEqual(grayE_32f, gray0_32f, OpenCVTestCase.EPS) + } + + func testSetIdentityMatScalar() throws { + let m = gray0_32f; + + Core.setIdentity(mtx: m, s: Scalar(5)) + + truth = Mat(size: m.size(), type: m.type(), scalar: Scalar(0)) + truth!.diag().setTo(scalar: Scalar(5)) + try assertMatEqual(truth!, m, OpenCVTestCase.EPS) + } + + func testSolveCubic() throws { + let coeffs = Mat(rows: 1, cols: 4, type: CvType.CV_32F) + try coeffs.put(row: 0, col: 0, data: [1, 6, 11, 6] as [Float]) + + XCTAssertEqual(3, Core.solveCubic(coeffs: coeffs, roots: dst)) + + let roots = Mat(rows: 3, cols: 1, type: CvType.CV_32F) + try roots.put(row: 0, col: 0, data: [-3, -1, -2] as [Float]) + try assertMatEqual(roots, dst, OpenCVTestCase.EPS) + } + + func testSolveMatMatMat() throws { + let a = Mat(rows: 3, cols: 3, type: CvType.CV_32F) + try a.put(row: 0, col: 0, data: [1, 1, 1] as [Float]) + try a.put(row: 1, col: 0, data: [1, -2, 2] as [Float]) + try a.put(row: 2, col: 0, data: [1, 2, 1] as [Float]) + let b = Mat(rows: 3, cols: 1, type: CvType.CV_32F) + try b.put(row: 0, col: 0, data: [0, 4, 2] as [Float]) + + XCTAssert(Core.solve(src1: a, src2: b, dst: dst)) + + let res = Mat(rows: 3, cols: 1, type: CvType.CV_32F) + try res.put(row: 0, col: 0, data: [-12, 2, 10] as [Float]) + try assertMatEqual(res, dst, OpenCVTestCase.EPS) + } + + func testSolveMatMatMatInt() throws { + let a = Mat(rows: 3, cols: 3, type: CvType.CV_32F) + try a.put(row: 0, col: 0, data: [1, 1, 1] as [Float]) + try a.put(row: 1, col: 0, data: [1, -2, 2] as [Float]) + try a.put(row: 2, col: 0, data: [1, 2, 1] as [Float]) + let b = Mat(rows: 3, cols: 1, type: CvType.CV_32F) + try b.put(row: 0, col: 0, data: [0, 4, 2] as [Float]) + + XCTAssert(Core.solve(src1: a, src2: b, dst: dst, flags: DecompTypes.DECOMP_QR.rawValue | DecompTypes.DECOMP_NORMAL.rawValue)) + + let res = Mat(rows: 3, cols: 1, type: CvType.CV_32F) + try res.put(row: 0, col: 0, data: [-12, 2, 10] as [Float]) + try assertMatEqual(res, dst, OpenCVTestCase.EPS) + } + + func testSolvePolyMatMat() throws { + let coeffs = Mat(rows: 4, cols: 1, type: CvType.CV_32F) + try coeffs.put(row: 0, col: 0, data: [-6, 11, -6, 1] as [Float]) + let roots = Mat() + + XCTAssertGreaterThanOrEqual(1e-6, abs(Core.solvePoly(coeffs: coeffs, roots: roots))) + + truth = Mat(rows: 3, cols: 1, type: CvType.CV_32FC2) + try truth!.put(row: 0, col: 0, data: [1, 0, 2, 0, 3, 0] as [Float]) + try assertMatEqual(truth!, roots, OpenCVTestCase.EPS) + } + + func testSolvePolyMatMatInt() throws { + let coeffs = Mat(rows: 4, cols: 1, type: CvType.CV_32F) + try coeffs.put(row: 0, col: 0, data: [-6, 11, -6, 1] as [Float]) + let roots = Mat() + + XCTAssertEqual(10.198039027185569, Core.solvePoly(coeffs: coeffs, roots: roots, maxIters: 1)) + + truth = Mat(rows: 3, cols: 1, type: CvType.CV_32FC2) + try truth!.put(row: 0, col: 0, data: [1, 0, -1, 2, -2, 12] as [Float]) + try assertMatEqual(truth!, roots, OpenCVTestCase.EPS) + } + + func testSort() { + var submat = gray0.submat(rowStart: 0, rowEnd: gray0.rows() / 2, colStart: 0, colEnd: gray0.cols() / 2) + submat.setTo(scalar: Scalar(1.0)) + + Core.sort(src: gray0, dst: dst, flags: SortFlags.SORT_EVERY_ROW.rawValue) + + submat = dst.submat(rowStart: 0, rowEnd: dst.rows() / 2, colStart: dst.cols() / 2, colEnd: dst.cols()) + XCTAssert(submat.total() == Core.countNonZero(src: submat)) + + Core.sort(src: gray0, dst: dst, flags: SortFlags.SORT_EVERY_COLUMN.rawValue) + + submat = dst.submat(rowStart: dst.rows() / 2, rowEnd: dst.rows(), colStart: 0, colEnd: dst.cols() / 2) + + XCTAssert(submat.total() == Core.countNonZero(src: submat)) + } + + func testSortIdx() throws { + let a = Mat.eye(rows: 3, cols: 3, type: CvType.CV_8UC1) + let b = Mat() + + Core.sortIdx(src: a, dst: b, flags: SortFlags.SORT_EVERY_ROW.rawValue | SortFlags.SORT_ASCENDING.rawValue) + + truth = Mat(rows: 3, cols: 3, type: CvType.CV_32SC1) + try truth!.put(row: 0, col: 0, data: [1, 2, 0] as [Int32]) + try truth!.put(row: 1, col: 0, data: [0, 2, 1] as [Int32]) + try truth!.put(row: 2, col: 0, data: [0, 1, 2] as [Int32]) + try assertMatEqual(truth!, b) + } + + func testSplit() throws { + let m = getMat(CvType.CV_8UC3, vals: [1, 2, 3]) + let cois = NSMutableArray() + + Core.split(m: m, mv: cois) + + try assertMatEqual(gray1, cois[0] as! Mat) + try assertMatEqual(gray2, cois[1] as! Mat) + try assertMatEqual(gray3, cois[2] as! Mat) + } + + func testSqrt() throws { + Core.sqrt(src: gray9_32f, dst: dst) + + try assertMatEqual(gray3_32f, dst, OpenCVTestCase.EPS) + + let rgba144 = Mat(rows: OpenCVTestCase.matSize, cols: OpenCVTestCase.matSize, type: CvType.CV_32FC4, scalar: Scalar.all(144)) + let rgba12 = Mat(rows: OpenCVTestCase.matSize, cols: OpenCVTestCase.matSize, type: CvType.CV_32FC4, scalar: Scalar.all(12)) + + Core.sqrt(src: rgba144, dst: dst) + + try assertMatEqual(rgba12, dst, OpenCVTestCase.EPS) + } + + func testSubtractMatMatMat() throws { + Core.subtract(src1: gray128, src2: gray1, dst: dst) + + try assertMatEqual(gray127, dst) + } + + func testSubtractMatMatMatMat() throws { + let mask = makeMask(gray1.clone()) + dst = gray128.clone() + + Core.subtract(src1: gray128, src2: gray1, dst: dst, mask: mask) + + try assertMatEqual(makeMask(gray127, vals: [128]), dst) + } + + func testSubtractMatMatMatMatInt() throws { + Core.subtract(src1: gray3, src2: gray2, dst: dst, mask: gray1, dtype: CvType.CV_32F) + + try assertMatEqual(gray1_32f, dst, OpenCVTestCase.EPS) + } + + func testSumElems() throws { + let src = Mat(rows: 4, cols: 4, type: CvType.CV_8U, scalar: Scalar(10)) + + let res1 = Core.sum(src: src) + + assertScalarEqual(Scalar(160), res1, OpenCVTestCase.EPS) + } + + func testSVBackSubst() throws { + let w = Mat(rows: 2, cols: 2, type: CvType.CV_32FC1, scalar: Scalar(2)) + let u = Mat(rows: 2, cols: 2, type: CvType.CV_32FC1, scalar: Scalar(4)) + let vt = Mat(rows: 2, cols: 2, type: CvType.CV_32FC1, scalar: Scalar(2)) + let rhs = Mat(rows: 2, cols: 2, type: CvType.CV_32FC1, scalar: Scalar(1)) + + Core.SVBackSubst(w: w, u: u, vt: vt, rhs: rhs, dst: dst) + + let truth = Mat(rows: 2, cols: 2, type: CvType.CV_32FC1, scalar: Scalar(16)) + try assertMatEqual(truth, dst, OpenCVTestCase.EPS) + } + + func testSVDecompMatMatMatMat() throws { + let src = Mat(rows: 1, cols: 4, type: CvType.CV_32FC1) + try src.put(row: 0, col: 0, data: [1, 4, 8, 6] as [Float]) + let w = Mat() + let u = Mat() + let vt = Mat() + + Core.SVDecomp(src: src, w: w, u: u, vt: vt) + + let truthW = Mat(rows: 1, cols: 1, type: CvType.CV_32FC1, scalar: Scalar(10.816654)) + let truthU = Mat(rows: 1, cols: 1, type: CvType.CV_32FC1, scalar: Scalar(1)) + let truthVT = Mat(rows: 1, cols: 4, type: CvType.CV_32FC1) + try truthVT.put(row: 0, col: 0, data: [0.09245003, 0.36980012, 0.73960024, 0.5547002]) + try assertMatEqual(truthW, w, OpenCVTestCase.EPS) + try assertMatEqual(truthU, u, OpenCVTestCase.EPS) + try assertMatEqual(truthVT, vt, OpenCVTestCase.EPS) + } + + func testSVDecompMatMatMatMatInt() throws { + let src = Mat(rows: 1, cols: 4, type: CvType.CV_32FC1) + try src.put(row: 0, col: 0, data: [1, 4, 8, 6] as [Float]) + let w = Mat() + let u = Mat() + let vt = Mat() + + Core.SVDecomp(src: src, w: w, u: u, vt: vt, flags: Int32(Core.SVD_NO_UV)) + + let truthW = Mat(rows: 1, cols: 1, type: CvType.CV_32FC1, scalar: Scalar(10.816654)) + try assertMatEqual(truthW, w, OpenCVTestCase.EPS) + XCTAssert(u.empty()) + XCTAssert(vt.empty()) + } + + func testTrace() { + let s = Core.trace(mtx: gray1) + + XCTAssertEqual(Scalar(Double(OpenCVTestCase.matSize)), s) + } + + func testTransform() throws { + let src = Mat(rows: 2, cols: 2, type: CvType.CV_32F, scalar: Scalar(55)) + let m = Mat.eye(rows: 2, cols: 2, type: CvType.CV_32FC1) + + Core.transform(src: src, dst: dst, m: m) + + truth = Mat(rows: 2, cols: 2, type: CvType.CV_32FC2, scalar: Scalar(55, 1)) + try assertMatEqual(truth!, dst, OpenCVTestCase.EPS) + } + + func testTranspose() { + gray0.submat(rowStart: 0, rowEnd: gray0.rows() / 2, colStart: 0, colEnd: gray0.cols()).setTo(scalar: Scalar(1)) + let destination = getMat(CvType.CV_8U, vals: [0]) + + Core.transpose(src: gray0, dst: destination) + + let subdst = destination.submat(rowStart: 0, rowEnd: destination.rows(), colStart: 0, colEnd: destination.cols() / 2) + XCTAssert(subdst.total() == Core.countNonZero(src: subdst)) + } + + func testVconcat() throws { + let mats = [Mat.eye(rows: 3, cols: 3, type: CvType.CV_8U), Mat.zeros(2, cols: 3, type: CvType.CV_8U)] + + Core.vconcat(src: mats, dst: dst) + + try assertMatEqual(Mat.eye(rows: 5, cols: 3, type: CvType.CV_8U), dst) + + } + + func testCopyMakeBorderMatMatIntIntIntIntInt() throws { + let src = Mat(rows: 2, cols: 2, type: CvType.CV_32F, scalar: Scalar(1)) + let border: Int32 = 2 + + Core.copyMakeBorder(src: src, dst: dst, top: border, bottom: border, left: border, right: border, borderType: .BORDER_REPLICATE) + + truth = Mat(rows: 6, cols: 6, type: CvType.CV_32F, scalar: Scalar(1)) + try assertMatEqual(truth!, dst, OpenCVTestCase.EPS) + } + + func testCopyMakeBorderMatMatIntIntIntIntIntScalar() throws { + let src = Mat(rows: 2, cols: 2, type: CvType.CV_32F, scalar: Scalar(1)) + + let value = Scalar(0) + let border: Int32 = 2 + + Core.copyMakeBorder(src: src, dst: dst, top: border, bottom: border, left: border, right: border, borderType: .BORDER_REPLICATE, value: value) + // TODO_: write better test (use Core.BORDER_CONSTANT) + + truth = Mat(rows: 6, cols: 6, type: CvType.CV_32F, scalar: Scalar(1)) + try assertMatEqual(truth!, dst, OpenCVTestCase.EPS) + } + + func testBorderInterpolate() { + let val1 = Core.borderInterpolate(p: 100, len: 150, borderType: .BORDER_REFLECT_101) + XCTAssertEqual(100, val1) + + let val2 = Core.borderInterpolate(p: -5, len: 10, borderType: .BORDER_WRAP) + XCTAssertEqual(5, val2) + } + + func atan2deg(_ y:Double, _ x:Double) -> Double { + var res = atan2(y, x) + if (res < 0) { + res = Double.pi * 2 + res + } + return res * 180 / Double.pi + } + + func atan2rad(_ y:Double, _ x:Double) -> Double { + var res = atan2(y, x) + if (res < 0) { + res = Double.pi * 2 + res + } + return res + } +} diff --git a/modules/core/misc/objc/test/CvTypeTest.swift b/modules/core/misc/objc/test/CvTypeTest.swift new file mode 100644 index 0000000000..28f462f81c --- /dev/null +++ b/modules/core/misc/objc/test/CvTypeTest.swift @@ -0,0 +1,75 @@ +// +// CvTypeTest.swift +// +// Created by Giles Payne on 2020/01/31. +// + +import XCTest +import OpenCV + +class CvTypeTest: OpenCVTestCase { + + func testMakeType() { + XCTAssertEqual(CvType.CV_8UC4, CvType.make(CvType.CV_8U, channels: 4)) + } + + func testCV_8UC() { + XCTAssertEqual(CvType.CV_8UC4, CvType.CV_8UC(4)) + } + + func testCV_8SC() { + XCTAssertEqual(CvType.CV_8SC4, CvType.CV_8SC(4)) + } + + func testCV_16UC() { + XCTAssertEqual(CvType.CV_16UC4, CvType.CV_16UC(4)) + } + + func testCV_16SC() { + XCTAssertEqual(CvType.CV_16SC4, CvType.CV_16SC(4)) + } + + func testCV_32SC() { + XCTAssertEqual(CvType.CV_32SC4, CvType.CV_32SC(4)) + } + + func testCV_32FC() { + XCTAssertEqual(CvType.CV_32FC4, CvType.CV_32FC(4)) + } + + func testCV_64FC() { + XCTAssertEqual(CvType.CV_64FC4, CvType.CV_64FC(4)) + } + + func testCV_16FC() { + XCTAssertEqual(CvType.CV_16FC1, CvType.CV_16FC(1)) + XCTAssertEqual(CvType.CV_16FC2, CvType.CV_16FC(2)) + XCTAssertEqual(CvType.CV_16FC3, CvType.CV_16FC(3)) + XCTAssertEqual(CvType.CV_16FC4, CvType.CV_16FC(4)) + } + + func testChannels() { + XCTAssertEqual(1, CvType.channels(CvType.CV_64F)) + } + + func testDepth() { + XCTAssertEqual(CvType.CV_64F, CvType.depth(CvType.CV_64FC3)) + } + + func testIsInteger() { + XCTAssertFalse(CvType.isInteger(CvType.CV_32FC3)); + XCTAssert(CvType.isInteger(CvType.CV_16S)); + } + + func testELEM_SIZE() { + XCTAssertEqual(3 * 8, CvType.elemSize(CvType.CV_64FC3)); + XCTAssertEqual(3 * 2, CvType.elemSize(CvType.CV_16FC3)); + } + + func testTypeToString() { + XCTAssertEqual("CV_32FC1", CvType.type(toString: CvType.CV_32F)); + XCTAssertEqual("CV_32FC3", CvType.type(toString: CvType.CV_32FC3)); + XCTAssertEqual("CV_32FC(128)", CvType.type(toString: CvType.CV_32FC(128))); + } + +} diff --git a/modules/core/misc/objc/test/DMatchTest.swift b/modules/core/misc/objc/test/DMatchTest.swift new file mode 100644 index 0000000000..82419271d8 --- /dev/null +++ b/modules/core/misc/objc/test/DMatchTest.swift @@ -0,0 +1,44 @@ +// +// DMatchTest.swift +// +// Created by Giles Payne on 2020/01/31. +// + +import XCTest +import OpenCV + +class DMatchTest: OpenCVTestCase { + + func testDMatchIntIntFloat() { + let dm1 = DMatch(queryIdx: 1, trainIdx: 4, distance: 4.0) + + XCTAssertEqual(1, dm1.queryIdx) + XCTAssertEqual(4, dm1.trainIdx) + XCTAssertEqual(4.0, dm1.distance) + } + + func testDMatchIntIntIntFloat() { + let dm2 = DMatch(queryIdx: 2, trainIdx: 6, imgIdx: -1, distance: 8.0) + + XCTAssertEqual(2, dm2.queryIdx) + XCTAssertEqual(6, dm2.trainIdx) + XCTAssertEqual(-1, dm2.imgIdx) + XCTAssertEqual(8.0, dm2.distance) + } + + func testLessThan() { + let dm1 = DMatch(queryIdx: 1, trainIdx: 4, distance: 4.0) + let dm2 = DMatch(queryIdx: 2, trainIdx: 6, imgIdx: -1, distance: 8.0) + XCTAssert(dm1.lessThan(dm2)) + } + + func testToString() { + let dm2 = DMatch(queryIdx: 2, trainIdx: 6, imgIdx: -1, distance: 8.0) + + let actual = "\(dm2)" + + let expected = "DMatch { queryIdx: 2, trainIdx: 6, imgIdx: -1, distance: 8.000000}" + XCTAssertEqual(expected, actual) + } + +} diff --git a/modules/core/misc/objc/test/KeyPointTest.swift b/modules/core/misc/objc/test/KeyPointTest.swift new file mode 100644 index 0000000000..30490f5057 --- /dev/null +++ b/modules/core/misc/objc/test/KeyPointTest.swift @@ -0,0 +1,59 @@ +// +// KeyPointTest.swift +// +// Created by Giles Payne on 2020/01/31. +// + +import XCTest +import OpenCV + +class KeyPointTest: OpenCVTestCase { + + let angle:Float = 30 + let classId:Int32 = 1 + let octave:Int32 = 1 + let response:Float = 2.0 + let size:Float = 3.0 + let x:Float = 1.0 + let y:Float = 2.0 + + func testKeyPoint() { + let keyPoint = KeyPoint() + assertPoint2fEquals(Point2f(x: 0, y: 0), keyPoint.pt, OpenCVTestCase.FEPS) + } + + func testKeyPointFloatFloatFloat() { + let keyPoint = KeyPoint(x: x, y: y, size: size) + assertPoint2fEquals(Point2f(x: 1, y: 2), keyPoint.pt, OpenCVTestCase.FEPS) + } + + func testKeyPointFloatFloatFloatFloat() { + let keyPoint = KeyPoint(x: x, y: y, size: size, angle: 10.0) + XCTAssertEqual(10.0, keyPoint.angle); + } + + func testKeyPointFloatFloatFloatFloatFloat() { + let keyPoint = KeyPoint(x: x, y: y, size: size, angle: 1.0, response: 1.0) + XCTAssertEqual(1.0, keyPoint.response) + } + + func testKeyPointFloatFloatFloatFloatFloatInt() { + let keyPoint = KeyPoint(x: x, y: y, size: size, angle: 1.0, response: 1.0, octave: 1) + XCTAssertEqual(1, keyPoint.octave) + } + + func testKeyPointFloatFloatFloatFloatFloatIntInt() { + let keyPoint = KeyPoint(x: x, y: y, size: size, angle: 1.0, response: 1.0, octave: 1, classId: 1) + XCTAssertEqual(1, keyPoint.classId) + } + + func testToString() { + let keyPoint = KeyPoint(x: x, y: y, size: size, angle: angle, response: response, octave: octave, classId: classId) + + let actual = "\(keyPoint)" + + let expected = "KeyPoint { pt: Point2f {1.000000,2.000000}, size: 3.000000, angle: 30.000000, response: 2.000000, octave: 1, classId: 1}" + XCTAssertEqual(expected, actual) + } + +} diff --git a/modules/core/misc/objc/test/MatTest.swift b/modules/core/misc/objc/test/MatTest.swift new file mode 100644 index 0000000000..af26eb0bdb --- /dev/null +++ b/modules/core/misc/objc/test/MatTest.swift @@ -0,0 +1,1146 @@ +// +// StitchAppTests.swift +// +// Created by Giles Payne on 2020/01/19. +// + +import XCTest +import OpenCV + +class MatTests: OpenCVTestCase { + + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } + + func testAdjustROI() throws { + let roi = gray0.submat(rowStart: 3, rowEnd: 5, colStart: 7, colEnd: 10) + let originalroi = roi.clone() + let adjusted = roi.adjustRoi(top: 2, bottom: 2, left: 2, right: 2) + try assertMatEqual(adjusted, roi) + assertSizeEquals(Size(width: 5, height: 6), adjusted.size()) + XCTAssertEqual(originalroi.type(), adjusted.type()) + XCTAssertTrue(adjusted.isSubmatrix()) + XCTAssertFalse(adjusted.isContinuous()) + + let offset = Point() + let size = Size() + adjusted.locateROI(wholeSize: size, offset: offset) + assertPointEquals(Point(x: 5, y: 1), offset); + assertSizeEquals(gray0.size(), size); + } + + func testAssignToMat() throws { + gray0.assign(to: dst) + try assertMatEqual(gray0, dst) + gray255.assign(to: dst) + try assertMatEqual(gray255, dst) + } + + func testAssignToMatInt() throws { + gray255.assign(to: dst, type: CvType.CV_32F) + try assertMatEqual(gray255_32f, dst, OpenCVTestCase.EPS) + } + + func testChannels() { + XCTAssertEqual(1, gray0.channels()) + XCTAssertEqual(3, rgbLena.channels()) + XCTAssertEqual(4, rgba0.channels()) + } + + func testCheckVectorInt() { + // ! returns N if the matrix is 1-channel (N x ptdim) or ptdim-channel + // (1 x N) or (N x 1); negative number otherwise + XCTAssertEqual(2, Mat(rows: 2, cols: 10, type: CvType.CV_8U).checkVector(elemChannels: 10)) + XCTAssertEqual(2, Mat(rows: 1, cols: 2, type: CvType.CV_8UC(10)).checkVector(elemChannels: 10)) + XCTAssertEqual(2, Mat(rows: 2, cols: 1, type: CvType.CV_8UC(10)).checkVector(elemChannels: 10)) + XCTAssertEqual(10, Mat(rows: 1, cols: 10, type: CvType.CV_8UC2).checkVector(elemChannels: 2)) + + XCTAssert(0 > Mat().checkVector(elemChannels: 0)) + XCTAssert(0 > Mat(rows: 10, cols: 1, type: CvType.CV_8U).checkVector(elemChannels: 10)) + XCTAssert(0 > Mat(rows: 10, cols: 20, type: CvType.CV_8U).checkVector(elemChannels: 10)) + } + + func testCheckVectorIntInt() { + XCTAssertEqual(2, Mat(rows: 2, cols: 10, type: CvType.CV_8U).checkVector(elemChannels: 10, depth: CvType.CV_8U)) + XCTAssertEqual(2, Mat(rows: 1, cols: 2, type: CvType.CV_8UC(10)).checkVector(elemChannels: 10, depth: CvType.CV_8U)) + XCTAssertEqual(2, Mat(rows: 2, cols: 1, type: CvType.CV_8UC(10)).checkVector(elemChannels: 10, depth: CvType.CV_8U)) + XCTAssertEqual(10, Mat(rows: 1, cols: 10, type: CvType.CV_8UC2).checkVector(elemChannels: 2, depth: CvType.CV_8U)) + + XCTAssert(0 > Mat(rows: 2, cols: 10, type: CvType.CV_8U).checkVector(elemChannels: 10, depth: CvType.CV_8S)); + XCTAssert(0 > Mat(rows: 1, cols: 2, type: CvType.CV_8UC(10)).checkVector(elemChannels: 10, depth: CvType.CV_8S)); + XCTAssert(0 > Mat(rows: 2, cols: 1, type: CvType.CV_8UC(10)).checkVector(elemChannels: 10, depth: CvType.CV_8S)); + XCTAssert(0 > Mat(rows: 1, cols: 10, type: CvType.CV_8UC2).checkVector(elemChannels: 10, depth: CvType.CV_8S)); + } + + func testCheckVectorIntIntBoolean() { + let mm = Mat(rows: 5, cols: 1, type: CvType.CV_8UC(10)) + let roi = Mat(rows: 5, cols: 3, type: CvType.CV_8UC(10)).submat(rowStart: 1, rowEnd: 3, colStart: 2, colEnd: 3); + + XCTAssertEqual(5, mm.checkVector(elemChannels: 10, depth: CvType.CV_8U, requireContinuous: true)); + XCTAssertEqual(5, mm.checkVector(elemChannels: 10, depth: CvType.CV_8U, requireContinuous: false)); + XCTAssertEqual(2, roi.checkVector(elemChannels: 10, depth: CvType.CV_8U, requireContinuous: false)); + XCTAssert(0 > roi.checkVector(elemChannels: 10, depth: CvType.CV_8U, requireContinuous: true)); + } + + func testClone() throws { + dst = gray0.clone() + try assertMatEqual(gray0, dst) + XCTAssertFalse(dst.isSameMat(gray0)) + } + + func testCol() { + let col = gray0.col(0) + XCTAssertEqual(1, col.cols()) + XCTAssertEqual(gray0.rows(), col.rows()) + } + + func testColRangeIntInt() { + let cols = gray0.colRange(start: 0, end: gray0.cols() / 2) + XCTAssertEqual(gray0.cols() / 2, cols.cols()) + XCTAssertEqual(gray0.rows(), cols.rows()) + } + + func testColRangeRange() throws { + let range = Range(start: 0, end: 5) + dst = gray0.colRange(range) + + truth = Mat(rows: 10, cols: 5, type: CvType.CV_8UC1, scalar: Scalar(0.0)) + try assertMatEqual(truth!, dst) + } + + func testCols() { + XCTAssertEqual(OpenCVTestCase.matSize, gray0.cols()) + } + + func testConvertToMatInt() throws { + gray255.convert(to: dst, rtype: CvType.CV_32F) + + truth = Mat(rows: OpenCVTestCase.matSize, cols: OpenCVTestCase.matSize, type: CvType.CV_32F, scalar: Scalar(255)); + try assertMatEqual(truth!, dst, OpenCVTestCase.EPS) + } + + func testConvertToMatIntDouble() throws { + gray2.convert(to: dst, rtype: CvType.CV_16U, alpha: 2.0) + + truth = Mat(rows: OpenCVTestCase.matSize, cols: OpenCVTestCase.matSize, type: CvType.CV_16U, scalar: Scalar(4)) + try assertMatEqual(truth!, dst) + } + + func testConvertToMatIntDoubleDouble() throws { + gray0_32f.convert(to: dst, rtype: CvType.CV_8U, alpha: 2.0, beta: 4.0) + + truth = Mat(rows: OpenCVTestCase.matSize, cols: OpenCVTestCase.matSize, type: CvType.CV_8U, scalar: Scalar(4)) + try assertMatEqual(truth!, dst) + } + + func testCopyToMat() throws { + rgbLena.copy(to:dst) + try assertMatEqual(rgbLena, dst) + } + + func testCopyToMatMat() throws { + let src = Mat(rows: 4, cols: 4, type: CvType.CV_8U, scalar: Scalar(5)) + let mask = makeMask(src.clone()) + + src.copy(to: dst, mask: mask) + + truth = Mat(rows: 4, cols: 4, type: CvType.CV_8U) + try XCTAssertEqual(truth!.put(row: 0, col: 0, data: [0, 0, 5, 5] as [Int8]), 4) + try XCTAssertEqual(truth!.put(row: 1, col: 0, data: [0, 0, 5, 5] as [Int8]), 4) + try XCTAssertEqual(truth!.put(row: 2, col: 0, data: [0, 0, 5, 5] as [Int8]), 4) + try XCTAssertEqual(truth!.put(row: 3, col: 0, data: [0, 0, 5, 5] as [Int8]), 4) + try assertMatEqual(truth!, dst) + } + + func testCreateIntIntInt() { + gray255.create(rows: 4, cols: 5, type: CvType.CV_32F) + + XCTAssertEqual(4, gray255.rows()) + XCTAssertEqual(5, gray255.cols()) + XCTAssertEqual(CvType.CV_32F, gray255.type()) + } + + func testCreateSizeInt() { + let size = Size(width: 5, height: 5) + dst.create(size: size, type: CvType.CV_16U) + + XCTAssertEqual(5, dst.rows()) + XCTAssertEqual(5, dst.cols()) + XCTAssertEqual(CvType.CV_16U, dst.type()) + } + + func testCreateIntArrayInt() { + dst.create(sizes:[5, 6, 7], type:CvType.CV_16U) + + XCTAssertEqual(5, dst.size(0)) + XCTAssertEqual(6, dst.size(1)) + XCTAssertEqual(7, dst.size(2)) + XCTAssertEqual(CvType.CV_16U, dst.type()) + } + + func testCross() throws { + let answer = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + try XCTAssertEqual(answer.put(row: 0, col: 0, data: [7.0, 1.0, -5.0] as [Float]), 12) + + let cross = v1.cross(v2) + try assertMatEqual(answer, cross, OpenCVTestCase.EPS) + } + + func testDepth() { + XCTAssertEqual(CvType.CV_8U, gray0.depth()) + XCTAssertEqual(CvType.CV_32F, gray0_32f.depth()) + } + + func testDiag() throws { + dst = gray0.diag() + truth = Mat(rows: 10, cols: 1, type: CvType.CV_8UC1, scalar: Scalar(0)) + try assertMatEqual(truth!, dst) + } + + func testDiagInt() throws { + dst = gray255.diag(2) + truth = Mat(rows: 8, cols: 1, type: CvType.CV_8UC1, scalar: Scalar(255)) + try assertMatEqual(truth!, dst) + } + + func testDiagMat() throws { + let diagVector = Mat(rows: OpenCVTestCase.matSize, cols: 1, type: CvType.CV_32F, scalar: Scalar(1)) + dst = Mat.diag(diagVector) + try assertMatEqual(grayE_32f, dst, OpenCVTestCase.EPS); + } + + func testDot() { + let s = v1.dot(v2) + XCTAssertEqual(11.0, s) + } + + func testDump() { + XCTAssertEqual("[1, 3, 2]", v1.dump()) + } + + func testElemSize() { + XCTAssertEqual(MemoryLayout.size * Int(gray0.channels()), gray0.elemSize()) + XCTAssertEqual(MemoryLayout.size * Int(gray0_32f.channels()), gray0_32f.elemSize()) + XCTAssertEqual(MemoryLayout.size * Int(rgbLena.channels()), rgbLena.elemSize()) + } + + func testElemSize1() { + XCTAssertEqual(MemoryLayout.size, gray255.elemSize1()) + XCTAssertEqual(MemoryLayout.size, gray0_64f.elemSize1()) + XCTAssertEqual(MemoryLayout.size, rgbLena.elemSize1()) + } + + func testEmpty() { + XCTAssert(dst.empty()) + XCTAssertFalse(gray0.empty()) + } + + func testEyeIntIntInt() throws { + let eye = Mat.eye(rows: 3, cols: 3, type: CvType.CV_32FC1) + try assertMatEqual(eye, eye.inv(), OpenCVTestCase.EPS) + } + + func testEyeSizeInt() { + let size = Size(width: 5, height: 5) + let eye = Mat.eye(size: size, type: CvType.CV_32S) + XCTAssertEqual(5, Core.countNonZero(src: eye)) + } + + func getTestMat(size:Int32, type:Int32) throws -> Mat { + let ret = Mat(rows: size, cols: size, type: type) + let ch = CvType.channels(type) + var buff:[Double] = [] + for i: Int32 in (0.. 4D + let src = Mat(rows: 6, cols: 5, type: CvType.CV_8UC3, scalar: Scalar(0)) + XCTAssertEqual(2, src.dims()) + XCTAssertEqual(src.rows(), src.size(0)) + XCTAssertEqual(src.cols(), src.size(1)) + + let newShape = [1, src.channels() * src.cols(), 1, src.rows()] + dst = src.reshape(channels: 1, newshape: newShape as [NSNumber]) + XCTAssertEqual(newShape.count, Int(dst.dims())) + for i in 0.. 2D + let src2 = Mat(sizes: [4, 6, 7], type: CvType.CV_8UC3, scalar: Scalar(0)) + XCTAssertEqual(3, src2.dims()) + XCTAssertEqual(4, src2.size(0)) + XCTAssertEqual(6, src2.size(1)) + XCTAssertEqual(7, src2.size(2)) + + let newShape2 = [src2.channels() * src2.size(2), src2.size(0) * src2.size(1)] + dst = src2.reshape(channels: 1, newshape: newShape2 as [NSNumber]) + XCTAssertEqual(newShape2.count, Int(dst.dims())) + for i in 0..= ceil(center.x + halfDiagonal)) && (r.br().y >= ceil(center.y + halfDiagonal))) + + XCTAssert((r.br().x - ceil(center.x + halfDiagonal)) <= 1 && (r.br().y - ceil(center.y + halfDiagonal)) <= 1) + } + + func testClone() { + let rrect = RotatedRect(center: center, size: size, angle: angle) + let clone = rrect.clone(); + + XCTAssertNotNil(clone) + XCTAssert(rrect.center == clone.center) + XCTAssert(rrect.size == clone.size) + XCTAssert(rrect.angle == clone.angle) + } + + func testEqualsObject() { + let center2 = Point2f(x: Float(OpenCVTestCase.matSize / 3), y: Float(OpenCVTestCase.matSize) / 1.5) + let size2 = Size2f(width: Float(OpenCVTestCase.matSize / 2), height: Float(OpenCVTestCase.matSize / 4)) + let angle2:Double = 0 + + let rrect1 = RotatedRect(center: center, size: size, angle: angle) + let rrect2 = RotatedRect(center: center2, size: size2, angle: angle2) + let rrect3 = rrect1 + let clone1 = rrect1.clone() + let clone2 = rrect2.clone() + + XCTAssert(rrect1 == rrect3) + XCTAssertFalse(rrect1 == rrect2) + + XCTAssert(rrect2 == clone2) + clone2.angle = 10 + XCTAssertFalse(rrect2 == clone2) + + XCTAssert(rrect1 == clone1) + + clone1.center.x += 1 + XCTAssertFalse(rrect1 == clone1) + + clone1.center.x -= 1 + XCTAssert(rrect1 == clone1) + + clone1.size.width += 1 + XCTAssertFalse(rrect1 == clone1) + + XCTAssertFalse(rrect1 == size) + } + + func testHashCode() { + let rr = RotatedRect(center: center, size: size, angle: angle) + XCTAssertEqual(rr.hash(), rr.hash()) + } + + func testPoints() { + let rrect = RotatedRect(center: center, size: size, angle: angle); + + let p = rrect.points() + + let is_p0_irrational = (100 * p[0].x != round(100 * p[0].x)) && (100 * p[0].y != round(100 * p[0].y)) + let is_p1_irrational = (100 * p[1].x != round(100 * p[1].x)) && (100 * p[1].y != round(100 * p[1].y)); + let is_p2_irrational = (100 * p[2].x != round(100 * p[2].x)) && (100 * p[2].y != round(100 * p[2].y)); + let is_p3_irrational = (100 * p[3].x != round(100 * p[3].x)) && (100 * p[3].y != round(100 * p[3].y)); + + XCTAssert(is_p0_irrational && is_p1_irrational && is_p2_irrational && is_p3_irrational) + + XCTAssert(abs((p[0].x + p[2].x) / 2 - center.x) + abs((p[0].y + p[2].y) / 2 - center.y) < OpenCVTestCase.FEPS, "Symmetric points 0 and 2") + + XCTAssert(abs((p[1].x + p[3].x) / 2 - center.x) + abs((p[1].y + p[3].y) / 2 - center.y) < OpenCVTestCase.FEPS, "Symmetric points 1 and 3") + + XCTAssert(abs((p[1].x - p[0].x) * (p[2].x - p[1].x) + + (p[1].y - p[0].y) * (p[2].y - p[1].y)) < OpenCVTestCase.FEPS, "Orthogonal vectors 01 and 12") + + XCTAssert(abs((p[2].x - p[1].x) * (p[3].x - p[2].x) + + (p[2].y - p[1].y) * (p[3].y - p[2].y)) < OpenCVTestCase.FEPS, "Orthogonal vectors 12 and 23"); + + XCTAssert(abs((p[3].x - p[2].x) * (p[0].x - p[3].x) + + (p[3].y - p[2].y) * (p[0].y - p[3].y)) < OpenCVTestCase.FEPS, "Orthogonal vectors 23 and 30") + + XCTAssert(abs((p[0].x - p[3].x) * (p[1].x - p[0].x) + + (p[0].y - p[3].y) * (p[1].y - p[0].y)) < OpenCVTestCase.FEPS, "Orthogonal vectors 30 and 01") + + XCTAssert(abs((p[1].x - p[0].x) * (p[1].x - p[0].x) + + (p[1].y - p[0].y) * (p[1].y - p[0].y) - size.height * size.height) < OpenCVTestCase.FEPS, "Length of the vector 01") + + XCTAssert(abs((p[1].x - p[2].x) * (p[1].x - p[2].x) + + (p[1].y - p[2].y) * (p[1].y - p[2].y) - size.width * size.width) < OpenCVTestCase.FEPS, "Length of the vector 21") + + XCTAssert(abs((p[2].x - p[1].x) / size.width - Float(cos(angle * Double.pi / 180))) < OpenCVTestCase.FEPS, "Angle of the vector 21 with the axes"); + } + + func testRotatedRect() { + let rr = RotatedRect() + + XCTAssertNotNil(rr) + XCTAssertNotNil(rr.center) + XCTAssertNotNil(rr.size) + XCTAssertEqual(0.0, rr.angle) + } + + func testRotatedRectDoubleArray() { + let vals = [1.5, 2.6, 3.7, 4.2, 5.1] + let rr = RotatedRect(vals: vals as [NSNumber]) + + XCTAssertNotNil(rr) + XCTAssertEqual(1.5, rr.center.x) + XCTAssertEqual(2.6, rr.center.y) + XCTAssertEqual(3.7, rr.size.width) + XCTAssertEqual(4.2, rr.size.height) + XCTAssertEqual(5.1, rr.angle) + } + + func testRotatedRectPointSizeDouble() { + let rr = RotatedRect(center: center, size: size, angle: 40); + + XCTAssertNotNil(rr) + XCTAssertNotNil(rr.center) + XCTAssertNotNil(rr.size) + XCTAssertEqual(40.0, rr.angle); + } + + func testSet() { + let vals1: [Double] = [] + let r1 = RotatedRect(center: center, size: size, angle: 40); + + r1.set(vals: vals1 as [NSNumber]) + + XCTAssertEqual(0, r1.angle) + assertPoint2fEquals(Point2f(x: 0, y: 0), r1.center, OpenCVTestCase.FEPS) + assertSize2fEquals(Size2f(width: 0, height: 0), r1.size, OpenCVTestCase.FEPS) + + let vals2 = [1, 2, 3, 4, 5] + let r2 = RotatedRect(center: center, size: size, angle: 40) + + r2.set(vals: vals2 as [NSNumber]) + + XCTAssertEqual(5, r2.angle) + assertPoint2fEquals(Point2f(x: 1, y: 2), r2.center, OpenCVTestCase.FEPS) + assertSize2fEquals(Size2f(width: 3, height: 4), r2.size, OpenCVTestCase.FEPS) + } + + func testToString() { + let actual = "\(RotatedRect(center: Point2f(x:1, y:2), size: Size2f(width:10, height:12), angle:4.5))" + let expected = "RotatedRect {Point2f {1.000000,2.000000},Size2f {10.000000,12.000000},4.500000}" + XCTAssertEqual(expected, actual); + } + +} diff --git a/modules/core/misc/objc/test/ScalarTest.swift b/modules/core/misc/objc/test/ScalarTest.swift new file mode 100644 index 0000000000..72495dbc76 --- /dev/null +++ b/modules/core/misc/objc/test/ScalarTest.swift @@ -0,0 +1,98 @@ +// +// ScalarTest.swift +// +// Created by Giles Payne on 2020/01/31. +// + +import XCTest +import OpenCV + +class ScalarTest: OpenCVTestCase { + + let s1 = Scalar(1.0) + let s2 = Scalar.all(1.0) + + func testAll() { + let dstScalar = Scalar.all(2.0) + let truth = Scalar(2.0, 2.0, 2.0, 2.0) + XCTAssertEqual(truth, dstScalar) + } + + func testClone() { + let dstScalar = s2.clone() + XCTAssertEqual(s2, dstScalar) + } + + func testConj() { + let dstScalar = s2.conj() + let truth = Scalar(1, -1, -1, -1) + XCTAssertEqual(truth, dstScalar) + } + + func testEqualsObject() { + let dstScalar = s2.clone() + XCTAssert(s2 == dstScalar) + + XCTAssertFalse(s2 == s1) + } + + func testHashCode() { + XCTAssertEqual(s2.hash(), s2.hash()) + } + + func testIsReal() { + XCTAssert(s1.isReal()) + + XCTAssertFalse(s2.isReal()) + } + + func testMulScalar() { + let dstScalar = s2.mul(s1) + XCTAssertEqual(s1, dstScalar) + } + + func testMulScalarDouble() { + let multiplier = 2.0 + let dstScalar = s2.mul(s1, scale: multiplier) + let truth = Scalar(2) + XCTAssertEqual(truth, dstScalar) + } + + func testScalarDouble() { + let truth = Scalar(1) + XCTAssertEqual(truth, s1) + } + + func testScalarDoubleArray() { + let vals: [Double] = [2.0, 4.0, 5.0, 3.0] + let dstScalar = Scalar(vals:vals as [NSNumber]) + + let truth = Scalar(2.0, 4.0, 5.0, 3.0) + XCTAssertEqual(truth, dstScalar) + } + + func testScalarDoubleDouble() { + let dstScalar = Scalar(2, 5) + let truth = Scalar(2.0, 5.0, 0.0, 0.0) + XCTAssertEqual(truth, dstScalar) + } + + func testScalarDoubleDoubleDouble() { + let dstScalar = Scalar(2.0, 5.0, 5.0) + let truth = Scalar(2.0, 5.0, 5.0, 0.0) + XCTAssertEqual(truth, dstScalar); + } + + func testScalarDoubleDoubleDoubleDouble() { + let dstScalar = Scalar(2.0, 5.0, 5.0, 9.0) + let truth = Scalar(2.0, 5.0, 5.0, 9.0) + XCTAssertEqual(truth, dstScalar) + } + + func testToString() { + let actual = "\(s2)" + let expected = "Scalar [1.000000, 1.000000, 1.000000, 1.000000]" + XCTAssertEqual(expected, actual) + } + +} diff --git a/modules/core/misc/objc/test/SizeTest.swift b/modules/core/misc/objc/test/SizeTest.swift new file mode 100644 index 0000000000..cd8bcd1448 --- /dev/null +++ b/modules/core/misc/objc/test/SizeTest.swift @@ -0,0 +1,86 @@ +// +// SizeTest.swift +// +// Created by Giles Payne on 2020/01/31. +// + +import XCTest +import OpenCV + +class SizeTest: OpenCVTestCase { + + let sz1 = Size2d(width: 10.0, height: 10.0) + let sz2 = Size2d(width: -1, height: -1) + + func testArea() { + let area = sz1.area() + XCTAssertEqual(100.0, area); + } + + func testClone() { + let dstSize = sz1.clone() + XCTAssertEqual(sz1, dstSize) + } + + func testEqualsObject() { + XCTAssertFalse(sz1 == sz2); + + let sz2 = sz1.clone(); + XCTAssertTrue(sz1 == sz2); + } + + func testHashCode() { + XCTAssertEqual(sz1.hash(), sz1.hash()); + } + + func testSet() { + let vals1:[Double] = [] + sz2.set(vals: vals1 as [NSNumber]) + XCTAssertEqual(0, sz2.width); + XCTAssertEqual(0, sz2.height); + + let vals2:[Double] = [9, 12] + sz1.set(vals: vals2 as [NSNumber]); + XCTAssertEqual(9, sz1.width); + XCTAssertEqual(12, sz1.height); + } + + func testSize() { + let dstSize = Size2d() + + XCTAssertNotNil(dstSize) + XCTAssertEqual(0, dstSize.width) + XCTAssertEqual(0, dstSize.height) + } + + func testSizeDoubleArray() { + let vals:[Double] = [10, 20] + let sz2 = Size2d(vals: vals as [NSNumber]) + + XCTAssertEqual(10, sz2.width) + XCTAssertEqual(20, sz2.height) + } + + func testSizeDoubleDouble() { + XCTAssertNotNil(sz1) + + XCTAssertEqual(10.0, sz1.width) + XCTAssertEqual(10.0, sz1.height) + } + + func testSizePoint() { + let p = Point2d(x: 2, y: 4) + let sz1 = Size2d(point: p) + + XCTAssertNotNil(sz1) + XCTAssertEqual(2.0, sz1.width) + XCTAssertEqual(4.0, sz1.height) + } + + func testToString() { + let actual = "\(sz1)" + let expected = "Size2d {10.000000,10.000000}" + XCTAssertEqual(expected, actual); + } + +} diff --git a/modules/core/misc/objc/test/TermCriteriaTest.swift b/modules/core/misc/objc/test/TermCriteriaTest.swift new file mode 100644 index 0000000000..25a2e9933b --- /dev/null +++ b/modules/core/misc/objc/test/TermCriteriaTest.swift @@ -0,0 +1,82 @@ +// +// TermCriteriaTest.swift +// +// Created by Giles Payne on 2020/01/31. +// + +import XCTest +import OpenCV + +class TermCriteriaTest: OpenCVTestCase { + + let tc2 = TermCriteria(type: 2, maxCount: 4, epsilon: EPS) + + func testClone() { + let tc1 = tc2.clone() + XCTAssertEqual(tc2, tc1) + } + + func testEqualsObject() { + var tc1 = TermCriteria() + XCTAssertFalse(tc2 == tc1) + + tc1 = tc2.clone() + XCTAssert(tc2 == tc1) + } + + func testHashCode() { + XCTAssertEqual(tc2.hash(), tc2.hash()) + } + + func testSet() { + let tc1 = TermCriteria() + let vals1:[Double] = [] + + tc1.set(vals: vals1 as [NSNumber]) + + XCTAssertEqual(0, tc1.type) + XCTAssertEqual(0, tc1.maxCount) + XCTAssertEqual(0.0, tc1.epsilon) + + let vals2 = [9, 8, 0.002] + tc2.set(vals: vals2 as [NSNumber]) + + XCTAssertEqual(9, tc2.type) + XCTAssertEqual(8, tc2.maxCount) + XCTAssertEqual(0.002, tc2.epsilon) + } + + func testTermCriteria() { + let tc1 = TermCriteria() + + XCTAssertNotNil(tc1) + XCTAssertEqual(0, tc1.type) + XCTAssertEqual(0, tc1.maxCount) + XCTAssertEqual(0.0, tc1.epsilon) + } + + func testTermCriteriaDoubleArray() { + let vals = [ 3, 2, 0.007] + let tc1 = TermCriteria(vals: vals as [NSNumber]) + + XCTAssertEqual(3, tc1.type) + XCTAssertEqual(2, tc1.maxCount) + XCTAssertEqual(0.007, tc1.epsilon) + } + + func testTermCriteriaIntIntDouble() { + let tc1 = TermCriteria(type: 2, maxCount: 4, epsilon: OpenCVTestCase.EPS) + + XCTAssertNotNil(tc1) + XCTAssertEqual(2, tc1.type) + XCTAssertEqual(4, tc1.maxCount) + XCTAssertEqual(OpenCVTestCase.EPS, tc1.epsilon) + } + + func testToString() { + let actual = "\(tc2)" + let expected = "TermCriteria { type: 2, maxCount: 4, epsilon: 0.001000}" + XCTAssertEqual(expected, actual) + } + +} diff --git a/modules/core/misc/objc/test/resources/chessboard.jpg b/modules/core/misc/objc/test/resources/chessboard.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6ade32006e4af484d433fbde43948fa550ed7768 GIT binary patch literal 27908 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%0Dq~kn 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 E*_#Bwd;kCd literal 0 HcmV?d00001 diff --git a/modules/core/misc/objc/test/resources/lena.png b/modules/core/misc/objc/test/resources/lena.png new file mode 100644 index 0000000000000000000000000000000000000000..3e8668734a753364bbf5f5f5390d46e11c75f907 GIT binary patch literal 494405 zcmX_{dpy(s8}~mGqNvu9h`uu_9hk`>#|l|0!iX9b*1;*qVNOX}CA3nOW2I;qr8&(| z%EF?^aXyn4liB7Fb9R6F-H-eJi^l_x*=O(Tx?b1w^?KiRICDaA-OhCY04QQ?tsDVB z3i?(Gko)(Axbt`k0PF@ZR!5y9VEP}ZSCGmuO4jJQ_K#i8X1doBGB-mwk1MKW%uKk2j; z#q44|q&F$~9{gfuTf9Akja- zK2?pYe$+xuI&X-Zo0z#j2JV*kpiW z$+gMoI5jm_q~Yrmjotk$VX^a3olc%l=^e`Rr}?z79KOa=j1Z4%B!!5#Nu<2m3coH@(XF?B@uvv;Y9QhgKcG#w{P#k6+~O6!XKc%@Y5PHH zkm}lg&M3+|P5-gzdx)#H{y6Kx0G^6rJ9JL<9X3&Sk? z+zn1fGNYeKJ9rV35vFQwq65zYrVF#(Nb|F&zJ^pS&a6DhSLCz{q_bZCR(r%7}Yh`omZz?bykJBtGDA8Jy*7@m^3R0 zBEgj7WD~U0Yf;_o!rwog^zOF(9|<10ZS5tsYJ`nD?~x7>ui-$wB};`Nsq-zUChnq9 zN-y*QOV6%@NL#af&e%&hnyyzg0G zYms}Ogw5%oPjzk#9Kl9@)hOGKP*Br(+6MFK+f;A3ra6lws2wA`WX+|}W_JxhoYNo2fLH~^7{<)|y454FF}|wYGiN zT-T(XbhHA_^@+@zMn%*Xwn)v(!3PhADr-Hz_P@#+vtQLcwb_Gel8i=6VW|5^O-$6{ zRPCZ^PJ00=!Q)k1u)6<+>Y?zGu1&W|^K-$a)T8Kb;e_$pZy^)m47XmTm+}PG#Evty z?}wExaY5z(Us;VT9)~w^Eg$|KCZc@5VjurYSkntmUGSvWcy!6!f(W{*L`J%zB zxcSB$rMTcNerMLZ!O|t z7iWc>jky+ZV-Ohqy!UQ^SF)~ouKleet*ZPCI{Lg?pzDn3Ml>UbKsNXD6m$naOCX9H zOBmO>6RptG@OC~LVJo$JUo$A7vlW2mnTU@|b4#Ogf?P;DF|mrp6OeRcrN8_`>8_FS3^Tme@dii51GPR-5pb5|$Dt2h5wSpXRn zCegc5$21*27}6a#!c3D+Mue^D}1+>Z4;!X?DBh z#lO77>alZMU()!CK^yRyZoN0k(!u%J@PHC3ff&Qcw#F=14VtTM1B;UptCOAleA}kKOafu!#=JoOn_{}lbCwcI=zQ4H867Ro z{;2fw6ZJ^S@y$nIO3v7^og;AGWGp|KomGiOXa2Hl1x*%*Xa9uYKPFR`qwBImeAHNz z=S&cLH;;<)-t>dFY1$!c_ofDf>X46xS0c;PesA|u1(_0=_$2C-m(*xB{HT6&+fFa% zH&p4GjWyWol^$(S%afSY{41)clVde=9XPDUM>Y^jfNDmY@m%fDKDKOe_~v*O_rwdj z&l!Yo1C4Ijt+uq~tdNuUNX(O){{Tgvn^38|y&u6+!C9;`(;-bC4;1F z8(m{D&W!0*^6Gjb?S%b)hZ5$S{QXy}`!#$TGs3~~%EQOnXgDmCxH9j#JTKI-82&KH zdjLIF?KRDEAy2(eepJrUX+L4n!+W~87?@kX`iEn&I-_2{T>JhU_LA1PeQrQ1e_1F* zMk%b#+wto&yXih>dOqL1YsBdCk2c&c7QgG>AAE(HJ+OVCC+g*Gio(BAqF(KmzNmv@ zEyt$Hp*8MqM27gIA6e;@YVzjirFV*F^Mca)m1&EWEvvKjD`F%pwDKlV?3I-N=b_F? zvaebD^tgz={WxHwN}jD*Be&(}^*w!2;f*)eyeRwXz(l-Y@UtvaQF@)=XW524#{EzN zoc`!FtQ8y5w!gf*YWVJA&)T2UbAPAR>qd^(FHL%=dDfc^mcFX63LOe6f$B#e@7cZ> zslqmx)7?X%$Jc==mtD5SGYv0#DUe090{~U_=NZi7QEpx`nW~Kj&Ezl|$-i|)%oDw> zSx#NhL!1_t6wmAViTy`)*WNI(cNR09cEMSZ)Os{*J5l_iNvW@pT3K1n_^#NH#~yFF zgldZ5>L|E$8KsI2k-WvKUZF08LGzflQyFLJxzI!k@5-8PdAEan46hhc_*KCY>sGd& z)Tg)`Cbulrz}{U~wGl&E50n&XH^FxMK0xiwaKoRls(%b*$c!%UT35r2sqt)YgIZ5_ z)Yz-1jF)vPa3t)nor{(8dK=Z_n_s~M2Q=0@9=-{CSwjuZII(Hp-FQj~B=XX#7rEzu zt#7y$Ugl zIoIzBEVQ8~OX)7HL}&cRrlZ+*W+Tzv__^4Cx(4cdM)J{0>d6`I`ME=W(Tm}(PcjwF z0$D*>TyT2AKG*y^swrOOtCCvCm+zqNE`HihzZya%6X*$(8J~~M9f5y$gBo;-?*0D5 zO|amS!+}?@hyMmAr~Rk~nNyg}OjbtEp}QOEfYE?;I+yDszJcoxzBvmDdAjA)6w>vy z3;RJ(2{4t*yKZ;P*EPlKHJN6yY#0}{^7;JWfQ7mm+Aj}hW0PS52()$+d}xC2?K5-3 z^DCT1@yg_KLH)||qI%_a&D+ZFQ$va`uuyE>*K0-tVs9W2NsI1wShB5+^pR5BbOa|M zbvLDlEPcW!Hoi`s9qDXA)+Ktu~3jV1l6D^Y$ppt5lDxwNPhe zkQH`%UFf;x9ajYgU^ScBYN;oBA-PyN`Gg{RNx~WjZr}^=Oj@ik;-+WAPg-w{4-|XG zF8tZEvb?&O3lzSi4je$IdW90c)tLBJFNWbl&RF^$G#Y25{g@vQAOLL}$hYrEk15Ix z1y@pqBJ4J}{sYuI4tOc*?Ij%qh_e#rU+WN1LfTIFT)9Nwqb4SN`5IWi65M_Tnl*3X zV#&-(M4?yfkJ;YE&H|1Sy@zL%lR0f7epdoPE_<4-cSUYM{Sv8ab9b?a&=}1#ez}T(7 zyhRjCBtCMcV|Jdy;hO>*{&l`ct6CDS-p)Enr%NpwEY+UQ z;}2DG9;6swxw~D|8Do`6ZAsuqRv2K6kdxN#zHS<&!ymKC%P$sdr=<0|d7V;{aCueO zbaS>cSmN%L%oIhRWt_ktlWkxFwo;bw7@CwuFmu5H6rwO98tK2g6X`y9(FW+J&i=LU z^*Tq!&txu4>cpC^{!+|M8cb?$RI+R!$%U-Fc(Uc5gNx`6EoOCQa)q%dj;mSyySlu> z5$oU{8{H`iKJ`@J=F8!vEz6gG+9|QwAi5k4OVdY_eT5k>t=8{xGnxF=jV-V|`kGx^ zedV=`^;*lCROnDc9d+&^o_xeN`5^_Ea(}b(cm58mrb!9-I4Yv=h^LbE`Nh;ETN*tp z$ZVR#t9G?|95(WduP+{48nItpGPH<|GWmKDkDZy;iCbE%UnQ-J&ndT-fou{dD~P#oE*Ej+pDu zT)QUzG*T;+lMCxkm1SUM8o(keJv5lTwRwmO(suys_guph#hbq#KY%L70GYU07_AhSGJ zDc&o&@mG|2urNq1ZST4J>Min)+4ycZ=u}nKr(vy)fS9WtY+I?lHGQb^lPOZ zD3r%gP9GKN+S4zB%zk;*GC-UOKMP;c{fvKBXA?jW2ZVn`bw|mTq~p6mGLWH+ z0TLe-bCIyL$3w3enlOL7+fJ|T-pTB6Nm31mZA&HRJETX!bkD>smRC+22Cnd$D=(oC z2}%lg7zse3LEhC-M=pJ8VWDa@dU>)Qw>o8Lu_{uJj7*Rs7GCtJyFCg8vDwm`eod9`Uf5W-KHw2^`!Cu2Q1Y* z#Y?Q1M+o<-%_jON@b6o`yKWa;fF!h^x{G(Ye#KP1r)=`eVgCC|l*Oz|NtK*v6-!@be4(nil0+?llV0#ry38@Kjqtqezkov`s_dD7B7xBoVmUEXp+LK8vk5Q3th+uN1EG4kL zR>TxzuX&boZ%ZTu-eiQ?Kv2scxUu<_Th(4eTLFb}31yq~3{fK5O8IR16~F)L_f=iZ?_ zTu4dV3J27BQb-~Lv^=;AjyyXm8gjuZ9g>$NQ(CW4B9otwQIE7&G7W7p(AMIYK}jYL z6o;P-E(@km7>^RKiG}(x4eM3(V)*(+*E=xbdpwJ~ysL=buPNAciqC6CCS_$CKnA2A zt!1Oph(d$%nOeJ@jS{YnZSY67UgrUJvHRg&4L8)w!pw@+EuG2k*3(7k^@3>+NHHbG zSMdG!%ewE$p#>tUUODM10)14)%UcVhq37gCfEsSXhCJ$kSrcsE^c&H?DgZ(DEO)^& zI2pFBnN-#NA``j&iWUB&UswmO17%~eL#Bi9`eRMz1U12}vBfe1TS7mTCcO`RZSvfC z;2JY$s1{5?3FQpezTpUE`B_^UG45YLpJ9~kV9 zpZaU<8{k|1R`0QjwEEAJnY^X18&~GbB2R5>vQ((@A$eP-eUOV^b`ULvmy|9-ph~hj zHw?+4#vTh(k*CfHrO{Zn68Z9vi~uQjwNQt2&SC}_eUIh$ zWFK^M?Nau@nzKR1LtmCEXx4Pj7u%)O2tB3$6@fp#@X42nmf5l$;jqyF%!n?UP`cx8 zzSY`N-#hc=vynL{Gc@Z)17PYZKh5_P*Dp>jEw_v7O=*E*duSD3t_RW&(E)<$hx3%gq66s?iyY5*mi6l+)LP}z5c2f4Bd3`o0 z$f{%~XR!RA!9GNpaUjMppYV_8Qx0Q8A}E}sgh}sSo(gOH0pw_-iR*R z$dbb-X}vj%3=!%UQI*$pVZ&5e?siS0*Bm>8xy@WK;E8W4s<$QPVRG3Hr4aw)FEtGS z$i*TME5CH#1+pogdP64G(5h zMvPKk<_l_?j1ZT_2k&|lGAa2~nszGmhk9tq^E07ghpJ$Oj~VA9@Z|A;H7uo)$NFYD z#Lr6L*Ri3ul8T8dq98Uq2NmVN4oT4bShRJ-*T-d#oW=p*M?d@_G4mTp3aYo@7)93( z*2_%(5t?qW;O~|hDX`CdmJ!k+-(Y{AgqKP~BOHYyytB*xhcTaeQ};BtC7dOzUlA2a zj^Hy=>D@-D?5&f(-cy4oOxwe7_@tB1W1_ka9R?1=(gN^`&)l$>7e^hjFgd6n5R=a) zxQ(Bz913wxN0LXSEvZfcCxqEiE`R6Bd3WL#`8@k}qrlF_rr#MiI9%Xk|4(c5;j#m+ z=IT*B)AwUn{w!CnPPB?4656UB`#BiB(Ih3Z=z`Z}*}Y}k8WoB?u*2%_TER;9j@oSI zqb|G_ft&{GJ-YT~;@E5(8(kZI`9@SR{iEr~|CLNo2McMVKcTiy4-gp)*mW1ovBVRJ zXOWXb^R=v2Rp9S`Dt>P|ueD#`M=n69KFaxzU~#&#etCv-+akKJ{!nbp#UPsW>Vn1U z+E^f2=LSHAs0w#*uyNv78zhyE9{wJx9vVvM>Ly4Z7%3iO60E!f&|7AeUY-PMdLk&Y zyGPRWS^MsOpq}(6<owl6AGWi@pVhy6CWL??2L2N(V;@kX!Q*grYwltufa?_S9dmglGWua;+L!-f7j$0JbH3}sj$ z#3iJD{^VxHGhEY=>z^rOFyKLXpjr2%UOdtrU|+?j3L4v)E{i;ntapSr5!#!(PMY*x_!GHLkx&BvsX@{GBdam+U z8{%X#@(eB-~KFJl<a4I z47+y|qSp-Mbhu<(zeAyr=6KGT>bftCY0=m6ieK647t0h6%nfsx9l5E@9fmtER{&Ke zU0z9H7-GtQ^!$u92RFX8cI)lSidJjMfr z@_SU0a!t!Ao2F0<;|b%=TfMJ-8AaFfh-FN-qFG&bKONoGiLj)1}dacv_=c*`(->&!>?ppr3Si@^faL0;fo)0XZ-H&S1H>;So;km zvx_aV_>y5P-o+ABGV@$Z@lF?btjmHjU(zth%5OqKd6 z0#8r5-_OtI8J!#r_~J23BUM@Gkns>YqR|z&AZ0&d${io%$vKckB84O=vr#sx1?|Q@ z!ID|?cBe$7#e;n1aTBRRsz}S-@!Kp{dVom?C#W`U1a?Njb~iVOo=7(Um@^@g@vIR1 zYh(xV;o~1NWW3g!sOjSbjd7Wa1iPgTXM)AS5|Mtnb9g1gDfom?yNzDXv{>ycu#cPk zGh0x=tj>PKORQ$?hNUb2z!*L|{Jp<7j5(Ma7ahCImw2vBt}Nz?JYyGo_c!7O0+b9C z^w(n06DGA)3<~xz0M{8RXMhga&PKN0qKqZp@!%h$0?2))VEqBLsQ+rI19vF69?gAM z|DEsOM+t`I?15K1Ae&_?;E0rP;b*bH=$d%Oqgdg#k$h(cgD@$m6r>K0@pWeG>zAiS z=b}7cxb7F+lUw<^I_;=6F;=4GwhEt<&eQuXcl!NCub}^(Pw` zFhl!)QsOPIApd9tRB}Re&>9rV2J1u>cCr;=qdGFCisBOp*gV&C{+1Um5y@`i-@NW( zqS&a(^lYCtMem5`)C!rCm#{UA$k23?z3?-eEeV1(7((FieMYA8z7{>Sk^w6W$Kv>x zY`gThhI7q%X8k5Of~tLa?3KKwW6Y?CXM~);^KTkz!5lZxP6FrC^!c#(66#Jh@N^~n z!pVY%p*ONvTn;<8z2L;pTS$#bN128%V^0`z?*XNw9f=}?7B+QAM5eab0GrNX(;!Y_ z`{D@NZ0?<_wRAkaTj{vY-o@Q-*8g+!eWbsEA}$E9{UD{tW|Mk}sVKm9EC4|MCkTKn zi;LpZhU7~t9W6UBqPC4DkGu^>viiDiQxVH8LQ(2sCp-ESkZ6%>PlitlIh80IEavfB zm6n6oHl&h5`Xh7TSs@D_s8d5BXL3ET;S--NGLAZixoms$G|xv1Mi6I{{eIqFU1#}5 z^Q&>u$u6?(+Mhq|W`ZS5L!jf|syreh<^7SdB)D$#$P#UM>mf}JfKFy7+a05q4hltD zY0gJUxP$vGyjC#LEBDpg)d!dSZ>T9$qA&^a4GlZ2Z;hRyPAx~qFmvk9i|dI?E9%@; z&fxNru+_{+lcF30D7BGvpCBFo_4bwngr|=wUO7XtI6~+N>E=Vsf15l}Oai`Xqt|SL zV|T=p(xiLsXs6#nd-P~FcKGPOlkp5jWk=}#C}p2is>-9FuDzaaZ0swBF8 zinF)^K4s~6PDuu77E8h3+i|~@fWlD1ig0zt!VYl_Pu6pS&OfJwfnmOPBvW8}ze8B@ z=nf!u)P>N~#xrWrE2oPX z8`Crbp{{LY1!YcfPrrz?S}`N%EcL1_#CDw4c8(3%cr!vUJ0~_kaMlo2LhAMn)@_>THMpRA*c9=DZ_}Ky<}kNQ^8&%gp5?)MHPPx z)1VphnN$2|W%MV+0M0O) zX`AvRNHt_^HrV^5R2M7gcN*-(&18a{DZRB~!2WU3JsW&fruPhH^FmU-Few-DVi30J zqX9tP_vnJ}L+L8XldW)~7$pmEHbdZR7rbacLT<)zE(vBS?$7joo`d)#t9(dlEMU*h z;ah{NEV0GPO6OeYr`DfrrQ^zqkN%T?QvP7f!)o!uq9AN#A!2cIY2LGLNiewjccy@0 z-~HtA`txuNp2`!;mg5ii{d?pxrSv8vz1xQvU&@%0ie2KoszGsd4@kC6K{Z6(BZ(?@ z!3(vNo$=jtz=5WH6a4C6l~%0n`slHA!yybL869k}pC8#)3hB!;XDyiKD@)VMo^cCP z)BG^wSfbfB^_CPl&ge+z;3_!SuXQUwnfmT@aPnJ;tO;V43m!y;TwrL({t>-i4>>}q zi`(F3=n%0J`7OdG6W&+MPF#A-dMtghWE(u{m|2*ZBdm~EzpI436$Y2aE(__BS-cLe zpq|^q5kxVfbT&=T2<8hlZRG7SNe*-y-A~4Q0J5#tWjplqs4x zAxl{;1mA-s>|wsD7n|aHa5EhCRCvl-Arlv1g!Zx`a5No@0c&rq1**19)kzY?i&1n> z8C?F;DG3Y=tiYU^{7%@=1M;EadwmS^hqYC9za%}Ismz?<={%GJL_FBGB`QI@*PQm<{jQEYx z_{gwDyq7%r0m8qpYFPpmnAaWQFJWoY7c=v@W@@P-+4GK8w(m~AD`X?DWqMnrYgTi@ zOCgUAuu$8G0s1rZkpKe@|2Ml)O^`C75#UEV{+GFdA|L-g0`b9pUf~kDYaNHk;Sw1H zD_g`}0;G78^vYG>K_y*^_=Z}VZ*q{3Z1s-X#%f`+`jP%py;w>l?rFY=rrnDS)bRB1 z(2G_*{sFRn88lu-)3cE69||n3_U?Xj4G4DK)=LcBN$CU0vys-)kLI-95mDVuwi>HBgn@sD-Xd6`;Vg#Gv8};q;GFaq16zy@M*GL3PXYDGVw$gzS!0rL zdiP?ZXWZ(LL_I|uu<-a4mZj%V!rQyqzC zI1V*6d{&a>l@CapY`cdyN%;3_Z$WIj@o<3aCIwg-4r`#108eEdzqbcK5}xLBviOmX zA_Ap(*2w`$^rY8o?CJ#q7s zt*N=~Jw@=!%Ee||&KoGFF!wigRkC_bWz_=maVk=I=Y0FN>o)my;w2R&0LHLr-6o9d z39A`pc+>WfHIyx5#sx2PE+YYydg=+QWYn=`X8|03$P{}xXB~5W7hlkPJ@CSw>jL51 z_7icD=X?5Y*VdP?JSdF_pih+j2CiU?W=!gjoW(YEJG~Zy_r0wk7b9E}#-0B`qZ-RL zQ^!Q?0pgVrD0j7ijP??jT(s(y+3iHx(q-WKG(V#;ylb6Vm{RziPlo_hBNki^9;h1_ zgmeNq{1{Gf z?LwspYHIeQ*Ng}B!ru-6w*8_2G60b&WoUb=nxd0xjek1{HCvV<1#Qvs`3RZ8xwzf=CMn1OWR8{Ta6Hly6kaDsRZ6tn=uMp{|3{X*nVC9+ zIEG|nz1hkiR;fjdo5{%J6qp;gFh3b#yW1Gy-yT{1)nW-8gjysPK>h=;SRZHr3u=*l zPZILKiu^OBo2j&^jLs4F{fOklO@%eUf9T8S%$njaU|WFQZ>R}Uo34|6Jz*acah#*w z6+N%d^25xww@4}zSb~`9>1lzNYjkyPYPpNs=D&(K+PWFLGq-Q`Gjf|usf5>v!emk1 zbpeiGhv@TNeA7Ss<$cNTFkI}3&6aqwX;ZPbJlXOQ+d2NKja79f$$06sPa^IXwMMRY z8$4u$%ZTzBi4^jTXy9aGA!N0DxtZpF5`I>yAM0zi3D#f$)I*9j z4a=!S!Q^VYjhjep9nU&db#v!*h1gpngL~qdOz}k9SEiv9a)zdYh0aJmQ*x?LYu~jJ zr;e-;@xfR55KsB`56?q^UQ(IaR(pO}dxp%K=yxC(?F!-E;-V%cW0rdn?F|F?i~v8~{2eS<^~KQc(ElrO=lW(~pIx{##@J4k`V2>ynlOHlb); zJY+zx>2f4%x$Pxie5kNVZ2RIY_C2gP%m;F92Cx1jxsa3+YV|Q;wuoz)`Cq9OrxUjU z-6GPch*UNJlBs^L8R$2MI0$t{%OY!1bjSbEX%VtM)u+Ln-lH=--^J+ zrKu<;w|?o*^v2Z*PU`B!{B)}8-^Aj)i~ad)r32pcvyiU-55(Fl}ts*`-)F@7*tKx%Ue27+_04Du@<)R3FsrF z71h%rB(f|-JLKIik~WM_dmR>BdK3W5?cZLG$jN3A`*F-}qmy@uG|1ToGf zo{+}8XDRU`@px=lWjx8UA)K&gcfyb5=wwsB6Q5}N(ambStFJOtT=7R1FdDEbWw^#% zgz0rp3dZ>yc;h)$YJf--=0(r&VqICBp3dz0=*TUpJuInIoj^hD!Z-*5Gl@2VRpx3e zrHA*Jm0zJX&Dvcm9m4U*hsD%?s+T5O&}v{0h$2;d#ey2W5U8CwIpWui2Xv4v1-qz2 zH4ofAZMj1BBc^<37Q7&K>Q>}u1?3bZ(z~PQ9k9yf44<%cAt$4vsO3L$=If)b4)HY{ z0}Q5uao$lCe(u+4K1H|fF#3IS(C%l zr6FxGW&x}_f_{ouBiW!AY_VieKNh;H2k1x^aUG^j^HoOW>yM?P#%bCw|4NIQ{&oq+ zHpeq7M(f^riR3ky-IC$JBGW=*#~ z)ln|L7$0}({AM_WJ1K4=lx%ZPk1N2+2%Vv^qfOOXewqMCe@}+S<+f`Ks@}9PC^zID zm$*jB3+N>hj*{Q}TX&(Fm;j*$)i(Xxa7^S|>Hj&xl$P;QCAV&Ig`XR@;rpyd7w8S7 z={Ic}GinlQ^5Lv+rU)_~{V@?qCXF~vU~Y<@Kx!?YtLc*t0$&5~Acoe21Z??NWQ-%uv+GzLtOTVJ7xJr8A&ZHYMFTy&~| zy5Qc%B`|Jbg|RyKpNEwCvFYuRPTa?`4Sr;wlkzr!m8DQ~H1}-xY%7hcU7gtIxiX_3 zON{0kzhL}PgL}U@?f$bsZ0^z(c@VQpI&J#N51!zubG(|@KtMTkeE?Z4(TY^PBV?Z6 z%w@7U0)a37q&u{Xbn&LACzh?x}N-&IBW=w$fuP(rJil= zrOUi_{>J*c<%J}j9o+3us7tQfedF7$StyC(!#)@6(jXJq2Jn#VWa6m8#P z3Dx9t2&BXermX>bx$x8M4EDSd(6;wTl$)5i5RO7pr zBGK$jo<*&yj!xyfQh{R|ix)(k-}|TaX%EUq0{O(Ux_ndhjf?=V3TT(<4pM~%ew*|r zc!j35752fYi>*7oyxa8r(cTbl&U&<$>|0`7kWAWpfaBduqmt4(!m&e`6gl$B>#Lu( zL}sg4vX6F*a81w*m8+OSF7b{o|3Z^fW5^lk?_~W51S?gqEdIXcS<=AplRQfX;p~}p zB5KV82-53)-ZTFvHixAY)~(d|jT&s*f}eiFfh^0AHmwtCt?4L9%@w?>j~1g;&~gzQ zQ_CC>OL#_rbC>r_AnYAox zb_APYkbs5eaQr%^N~?@dI;M`v++~bR42_F1Nm=tvz+ryfn7gvhtCq>d^#}xl$)Vfc z>FN?EWiM5GO3Sybg}*`UePy=3-dJm0vW|LSPni2QICKz_NwM0dLXcS@b#zF_3Zn%P zTv+~AxD*3Y-NfUXcNJ=usiJ_6HMacFwqg;3_x8Rw5J|+C5yS5+)QJf3!KgM!aivNY!P6L==C!uR&&!HbAQ>GZ#!Wa?LpfB! ziBBA!)Wo080%&@(jWOYd@$64dmHt4*m86_+E3Zeg=mx;9KHvLIG>9qf$*kmnK3@Y& z>rC5911*-9=7zH3<~jo{Ru@_$=I5f!x3V~4u}gEHyWPbNkv$%JF!DM&I!mnj#lVW2 zTJm{=eQIbR)DUpBd#4hwNQV4TV3rF%8Z4A`D9eY2bvQ#p}3gr<=Mip`Ln*b<#~ZjVl$H{9(kmX|Lft^)cR20 zc1xkJ^{1fq9DZa|r;%Ffoy)e@brDOnZtLLWArZYT9~ftTfcOM}u+np?;Nh?j*i@(O z=r6jD#Wj^@H`lC>|AMl4$}rhnf$0||<&(-lu;L8?R{!#C_ri^V8cw(qSRx0&hYD8i zEAF<$Y=`x#d1ajQeK;NfP%xY0i!}QJLIzT{?xw<9{#YHiAKjW3ARKrl>21X)sf%hK|6A_fhJxLTK|qcZ3VS+TfqP2Y))fwv{T}Gp*xy1)A!H@p%}jtA%euc5K61 zA1C|v^mny(dB`R z8jiLol$~Cq;z6TUmWv~nYGdd5gRx7zQj6tjel8T8GcbSf7#ux)WSjl*Ap#Vo?TJag zl$rF_$97CK4_%5u`il>lJwQfgo1pnwJz@U>zGOc;SVI8tzUX@5-0ycFlcfYLJrC&> z3EwkLyf}TNDVozmt14ZYnx97UE_EhmIV*DLRl&5Ly22E&Z^6C0mEu_*RN`QV=614d@>~b(mlkuXUFYV2Yu9q`6ip_S)=4?Cyt{7tPfAq^e z^^bzZX2&5dhXfVhgB4!hI}V<#4qub@P3-sR=;n(ScLBMzR~dxw!+RnnI|t+D{;HeD zENoOy6{pq()eabKxM7Pa-1~BO+Hjq8Pj%$4h3MG%Az@G4@{IlJ-+9lKLG|A%mjKww zbsuIGtP_PC;cQeLH=vSPnQSW!E&gUU7CvA!`#l}x2F=Dlr? z^>oTTJT=rzM{hZhc{D8ZWQP~N2NSiIc7h2?bOM(fp?<$KHP_=Q3cO#>#3_c|U}x&N zgo(|XCbVxe*V9NqR9kcXsyJ?a{5vGDVz}AguAi7{%kfg+ywdvPACK#0pbPRt{5cOG z3_rFH|IgC5tTJaQ@sHTIHiD&e<7dMT?Y|Es7KhhF)emPuN>1!rh zoYwLDW~wfzIixq@v<|oKOPemtOqm|ucD9f=?7~47U2m&qVHJ4_zvF7jr7C z@?Vm;yMuvB_)QuQkjbUG;M*vmqMW6ntrU(PfW}x&O-5{j{R}5QbSA>meiO*YpkwKG zJk1I+#nz01Udf^-pBxB}q#bnQMHQzZZl+8$lNuMXtqQ5gK2ZQr!BX;r-smI!76ZWC ztzw1-^;if}32KmT#nk6zxW^B)^hQp7uj)42*9$1X4wzgnrrSQ-eXZw_jrqQ&<=8uO z`csA4`^S!0Z^>r)m&icSp!O>-Hm+p)NJ=$re*!cj_B6#)ve5$dZHbqah-`$&h9@ z2#NZN?@%9rI$sUqax$zzq_v}oJ}cxEOq+tyV=>PU9<>y^`$EZbuSWhdS_a)*cRG~#m$xoyAz={YWoXWH_D5gYF}w#ucPq;v1_HxSEZ%>gW*CE-2kmljSWS;e zlxH&$3!);T;}-v_Tf{60>?ilJT1ya0Cz6pbAM7d6bk6;6w)#d_6i4DI&W)R!;#sUL z@K@)DTkWA3%0Pg6fR~7Q>B@+Lt-A*qx-pniacQl|HdJs)I)xb60UU6_UbXGVkr7f2 zydZ9XA#|CdFD33c2fO(NtfGOt4I?Xq-(Ywe^)s~R4ehEacWjz3Xs;Jp#CWX!#-4SHwo}3yzn9q^lEJuo(G_AB{U<`+89yanT^yXkG}U{Vpt~UqMzr$i*#<}I zl#=DLect&1O))e;6TL35c8mz$~J8;-x;-Yuq=kFzVC`ObcF~W zdk?ed)l$E})+0G|@aemNFS^L1^?IZ-O?h_D;sim;|~AAA{p( zd;7blZ@UcThP)16T5Pk6b>@KL9q4#-omqz=v$6nQ?xgMRtRsLYRJ=$E=%*pt& zs<%Gu+co)%YtIVCik7NqmCLi!Pjgp>r|nm#g{-)F;Y=&%4vzuR<=7X?Q&Z$%i+Ok7 z2b^zC3X;y8hY-JgfPyRuvdfOxaz>slP z7niH~Bp*t-tqjF2xsri@(4`D!>+XwTv-4;Igfd(u&Dnx)-82c^w9yNMG2~ zroR5<8$u@RlT#brMJRw)E2h7z5S2velbuT>fRM0jyz`Q=UiMp(zxxM*4K!E!;5RqL zBa5gEjO?xaiPMBf`tl9>kl#|F0R|1aHcb|Wv7d@&Y2v!o2(G%Iwx`noxF1goA!yi$ zn0&UVj+#4?1^i;ZU~k8-K>Gh>=QmW2p2}me-PPWXV)g zYErV5rNWSPvQOz{l66uNS<0&#S+a~6gW|PhCkA7vWH&R)G8oJ6_C1c@A03XPKdA9M z_kCU0d7hu^Ziwf>ueJ-Iv448y`-wG;cPsjq(lfrttsgEM5?{l5`0>8P0*Z`i{k-pW zq_O~hH}^x93k>q3fLm3DZ2pF%GHlc5L&sacl)#6Weoi=AgUd5MdWGhk^2&H;ZjBMW zz4d!5ZcTr}{9Z?gDzqH*A-a8$_O^gIPCzm9jh~C&J+38L;-R}3R-&{1%_nk15 z^g8fQa@Cvg%K(26$2MUyQfE(dU#9MHrv2Rvfy^rq*r&4ml3SP|Pb&t)Lf1uZa+#D-G5V}y!s#!|k(;%Ii8>db~@q_az?AsyXZ=S=Bh7o2OJA0Ot;tER?2MN zkZSbCI}N~;qQr>^Xck2Vy!gboYEQpW6p@nz*1qRR%2d`gY0Wc8kEG)9iP8&BI(}ED z%C;FTh(8CJx-dqx4#8P0@BeV5fXN~!g0j4bpbwfXmLG_p!Yufd=;HiWUCetK#J@uH z+DJT#-~~8hDS^Uho~h@}#X%c)nc#BZO1X7~IvoNx2rhKgdQ9O+qGs%fYwye$=#M8n zWelvlq)JLk?zepF2kr(QFk*j4N(J(*l&#gdkwu5S-?El!G=?Ce1dw0Rr(;*#8($;U z&@b0q&VQay!d~Actync>dEd?^-TFI|2x|;8p^FQxiTa#=sBmdjj5xP<;7|yoyRCyd zm<4Y%EVrbO9}#XCqu^3SKI_uOI)IHr+AS!3Hx>~6j^qnE>MxXr*Ed^NF<<~iQsK9x zkj`82@zGuBR{QY@f@Wes>o{$pctvcibal*altqzTa`dj6W>yy;Kuga=0?VjJ!QtuZ zlsKstY+Y^bLS(RBE-79N?LJp%+tR-zt9?!7k@DATipLO(fl-utRtV`mwOZ3WSp>36maZ8}9MH1YQ*Khqu-uc4_Ya}RhXMC@Y`#v2Kxx#)+UmC+|rqaUXQjy(Dvf9{b zSqK~e{@F*+yOkX#I6*k~COS(v*9YC=pL^pqjWx#8Yk0s;;ATjEO_zc1e4Pt>tGzIK zsYPLXaU#7w;(Ez{h06g>w@!XzNUv{hjyjqDUR^EFt-1E<*v~wbzq2wnfgDE#)vrgo z$>Mkg&f?n$P&e#+QwUCn+K@hH%1k;8Xc*+*1HrzAK*?iW;J)6Hqe8B3vw`>syOLUVc}q~Q`NLB`Nk!{6PcHGgYLY{ck1r$27LuG z?lndGXH79d$_>n(F1M^rrn}^(Nv6X6>*_pIN5MzdstI*uK_NiWjv*?Jbak*o{vJsc zDa934o^3r(2Y2SP42Jr93ih_A#g$+TECu!)6$`2*HMIHT5KN*Fw1qmu*UB0TMxPZ$ zzXP5dU|Il9l@|Y#;2|A*KScRYpJpZd73g2l+siA{-8pASB)gO=(H>XbZg|uB`>{m> zV^GrvQp!)5YNX-MPm@xqXPWeHx$~9G1DHpjc>r7Pv!=i#i>xk;56@#CR%{HZguQ^} zl8}jt@}n4j9r=KvDRTk4A4#Znk%C z*+y^vPCh}K-Y0MC3P$BAbsHP=;8$K4f)o2s)~#q@TKd?P+ocp{yin{^*6xdP2Cd5( z!)s|Hpcj0l^YU;Y4(3BC0u>xpJ1f0reV_3XmB){ep671;ft}thTtL@IhhHiL=9QZ8 zJ3*Xar=Teou&#_8i~QSpd~kk?8Fiqwk|gt9(U_oFKKRcV zz)-14q4wKWp>aip!gPbFV5eB1C{Ihd)5aFcMD3e~CP@Ge0|K4ET#=>1ed8 z^%;wT@V8$ovzr~~S-biv737%%e@VG9_ozt(7XiKwMJAl>Z@4<92pa`Fnl0aTC4}dB zM+%OY#ah>aj#^=;wY6Oahe5D-;_d?VfSlx{bEZI>8ZSWz#24sG_jz1@-^4|Rh1Ere z4%4Ko-cT||!~35<3eiK9XTEeRIt@(Z&l%=FSfoAT#Rk)-*t=I+=u;iDy%$fIM%NTo z)>P#Kfx06hm0>eEMvss4+Us+ItZwxvKMJqFHt{gBB2Ap>wGDkEC8&e!x(Sn(z40xxkHMR2LB{ zmr5D6Xk7aq%j`4!)!HF+TwlWWjD=9KZPf%!8PabM47MO73B8BuNaKY~mqFmFdT`E( zp+5gW3nc&ccw|1x2g0NTBm=UUJMv$eIgXU0hdvTYoQHgru8<}388`^cd1IC5(`&6{Hi>({3;!(Zk9SJ`zuur3lr z7v8g@NYUFrrrB&tUFb$YQBL&s^#c(fQF3Er=KfChLc0T#`d|(?^gf;KdtugBEV#Y2 zvt3W*yNGdjFG#yjBa*IK;43*vxy%A6(5bv|;~*`B$oIpv>V+rnI7 z240*wuDuuBC+`)f2xIAo8FB#5R#0=}wC9%DuwMp-a6n&@QO((&D=b+ZiwEQN4ycErQEH>D zO*-2~ikE^)sLHXR6K@2bQBmQUBq20I6h+vETswptUIU46$^QmzfRbibb_s|v8HYQl zdLKwr)|vjJh@Z2ewRLra!PSP^akrc}tb=RNa*Px4JmfGP5-5KildpUWI6Ef=P#(tk zjA1J^R5&f{fgJp7XpL1a5V}0z#Zf9LsnfUC=K-YOZa2`wW&Mj0RVX z>ah2j-GP7)+6Uxv=U4_u+Nd~BV(mB^TvD{);3}*p5(t-0;I%pIWa$t~fdxGfgJb@a z5}Q;FWik>O2VRN{ta?3_JwvQzy=6K_I@$Y~yIWNEMjdU`l_aJpYab9&6wJOYTV-%M z@nzsGK80BMcS%C8RZ{tLdyz6x;$Q-O<=I~H@N;*`|Gf+{rAP|Y`p0l>e7ShnhVZBm ziD7p552~6A(S3Lo&HJv?!K5!2IPOk9vsvS%LwvAzR;Hz9>9|LxkxR3ME|JppQGH8N zE^_(;o>n5nGD6BXGYUSou!I{$sa-u09lG_WWvOb1rVzc6o^yYfbd}|5bs2faUFh?D_8C-34YG=qV%c`x$0<7abPfnEk}Rc`;m}|+Q`lBOy+&PKHm9(@c63~0v>W?z zulKY3jIdL_&DjPC2jBz1nx=bHF}$&$jHK_00plAXV?ZzB7j4jZ5NP4_BwxWDaub14 zd3qS+xYH7CgnLKjA5}i@f$W-ii{c0*ufC99lIk+W25-mx+3N2}n?LlEDz6q;pXCX9 zwYNv=Bj@Tp_AAZScWPRwvm_JcFD${=z@_upD|#sWHXrmK1f1N3bGl;o{bL_Gj7p9p z27_|~U$38>N=!5N#ylbCWG62fU7GYIq6g~uMm#BAK(OGu0H=|9EFXp=Up^5`sQ8Ufi*8FS3-uj}m!&$U#1c&`^^hZ0IEViBF7;xzz*3y~~1R7+=gU2M-zK5Z$AM-fv{m zS+euC{;o(o`o&G>p zT4vCikb+PT!tn=(NAV0%e3E-&yy;ez`45$U)@VSYw=dA0vIBYU+$yrv(he`_kThx* zav&CJms9!n{g-~iuRg+LXLEwC5baREwFHQgQPp$(RpVU-Yz}zGx|U>9D~mOM&gvG% zGYR+nAnpFutoc2V_O_;M3SH6c&`;_%)zs^I7Q zO0fo3JGxLxg?if^qMHahBsI;hFBOb~CP}BPh(6aOw7Zt_-MJy*2dRSxDv*$GNL4HA zxqJ9-MBz~!)ONJ&Yd$4BVIjmbg!efX<(A-0?J{^U=W9n6<_H=~0UPw1Yo?^BtlR3? z4Eg+@kte|S(ltzh2vC@kGq{LIwXL z`%&h9jC6|O&o_Lej3x*X`Jaj`))cGVo}#xqNn8Q48JTjkKVL^O)u4rtY8DpTLNE~I zl^*^<=z)ugBY2#NJOVvFM0K2AFR_Q^tF^&UeY{hw@?a|u%73{d&>0oSIymtl0Z_M^ zOO17Wa6<#+VY09*1sjM29@a#!-*S*S{~Kv2QP2*WQ-cm-aV4usx%;-}OgHCNnQzyt zY>k@b<0D?+70r4TEgtg>+n3B6nR2Kl3N+l#N{d1ir@wx?A4hAio4ve0qwy{}LD!2t zz!=|VmbAU|4(3>|G>#ChVv!M_#$A+o8knn_>yziwb8>R99Kko>3+SQUge>hqVXTR0 zCUl!d&pvShDc+6k$*Sm$*>27Tt(&$sM^CQ`{N?`(8llu>`4tOzTX~jJhA&tmV;ts5 zq_=O6(7!6K|M{ae{foWbT`!1|fzS{zx#c2jl)n^HtMawn62N!{Jn7)#M&e2ct_XzU zxz{2Mz`|{Rz%k+uo6412Zs=tIJNf0Ae;as3iOv+7%A0Nn&g7r@_R2#pE+v6W%;0*b z`;o?(b1aO2;*VKIh$>iyc8=1%{O=xfRvU! zrFfU}we>^UBfxkJs!DTe?eGPj@a$>26z?ec-wK-=%8xEV5(&)AFvHJzP!ig#9-#2yaDIAd zOGg?jR^Qqx!6~?Y9FR{^%)yi^&#W2T7MRWqCi6>7CgaWv>!buJw=PMPDFjG5v(TPQ!_nbRY_oef*l&DpBx&_SBJh{t>UR4!ks>wi** z`xy}8i>}vg)kG?8B}s_VEJX{p6p1YkOw?RcLXbdv7ryl;G{HafF%CqYYGkH3<2w;` z8!b7-U@AGu8IRolxG~M0`YoJvBcZfLZ?-^*Vu<*q>S~Wy+CG4P3&Q20&f&KU@?CZ| zXq%nU+u=kl>xVXd3V`|pROpKsqK~AR74EhwpeDIz!qDeaAD!>>(W=Q52@2NhMP72k z+`;i8Qp4FAN@-~U{4gPWLh&DGt+Be=mFv32dL_*C6Z266?ekhm=`;*W<1ceNp{k4! zFq3{o#tkejX-kH{fdl6XX6l1l3a}5Pd{eOI8S*_0j{$vNST1<6rQUE<+I%7DHrxdg zN$XZZLkw_7S|5xDNfx3H?>iD*1nsSEbWM!ab)Fv1?$}MI^u*i@42DaCQ7>Z9;YyHO zLN9-&Shx$Z!A3$HFA1x7;%N zmcUH``Mn>>bn{Fo*Mk?n6dNKE9lQ_x<=NQs``5^JZrrcN?G%tixB}8r_}ETFKJu>2 z9q2PIBp8^lwY^Jr^Gwd(mXDBnK4%^=eT{&?(@9UM0Ld6TJGl1&=+>3WG|bAz#wkBu zlm(bxxS3!=K?;HUV$L_>Ub#%WD4$k-0!V%-`?gu{k?Ph)>^|f$MbIYL*m)W8Au|@u z|3*iod=r}?%m5RugB@w->#A%SLt*iO2yl@BDF^Z9D{wx?3+Z9s$Uu2<67pSh9ckaa z=w6)05vej!9Viv5>A@257fVHbpk|FU->uH^NN(rOT2)p3`iQNCK!63GmJCVpC_g_b zv3M%9u6C7Pd}3E>$vCX91$wiV9P6M9tprb*T`NL^T;-Hm|tGN2lOga`*N z0J>*B_)Nit{9x5zc6#0F&n$s;Q0W+AmBhWVvp6x2Gp*a+wl$6Bl$p9zB3w!woLcU` z3@yEz&L?8=XL8BgqSbg>B#*++rnU${3I5r)0YM=iARu4Xb8Vx=@Cu9eJ`sx7E481x zV^SAbz^_bE`2j!mU^)} zSLdf`HGcZV;p6Epb&j0!F=x(oeKzCPGny}2DdBsT!I>H|lT^d-6<1KS$Lz*#iYvd5 zfF?ufJ!y4p)Ah#rSVZ&I_E$l-gddISiQZO4LqWz6fza_Gu98BZ z>8PL%#>;{@B|Z)Ed740&uK~=*)nLzphgd&)kJOuU;1q}2KoCU&Xdu`#UA{wOl-@}( zBrMpinjUW;sT_yUkIPOXDBkSLXv+|@=E*&!{ZBI`F%D&cs&MiKJ+lPLv;l7Rn)E$h zP#VPv2}TZW7pkto*vt1`owIuen(nR+2owz{L z{B+l+3(f<_%W`(6X8U&*3vG8;^i8k(d{0~9%1^xb%|(Poc(I}%mOfNfMHO}LY%JAp zPjREyTk0cr#-o1xGyPiVvKx6m3_T2Ks#6`0sRf_xRT2^v*_{g2^HKb1mS^d*S}UOUv3G>o#nX z#QV>DO>)QKvdE5`6t7{82_?U(j!_1Aox8JH|N9kejduuX11JQVvH2uWI}mq4!I|zB z?w+Q(XoN+d52V_9f2NFbXXqc6gFWu)B@gty4-7lCxOrl6h9WN{3fwC<-wf7_>7E5M znEyTLOfJ2~Wr(gyBUKtI#Ci&Z?JwYlP*%pEuktNfE&Z4qUm7)=L$iy)lwAbP<%Nha ztqBrfYXl1_-b?sM%gC6KgX7V2^^L6+_8<(_b)D5w>-AIwAQHU0hB5F5Bk+&l831QF zpCo)YBmweVS%F1=TLFj{`&81~U}sRfR!#GYYp(`0-U5(o!(=euaP4jd&%LX~vNPtu zXTlKbg4W~z_X3P%gx0$tKffcrxGYd?$@{*e*5#ZXJ5sjg)CuAw=-}Js;C}NDBW215 zRY7JFyr|$hiY`rzI^u7UJsryEe*5+V6IC}1<{F+ zv&eATSoqCu17#ji$^f%+ufWM>{cdd`dSDk7mkO{Hs3yg`z$XZbwj`Oh$}UZTf1Cz> zY1}R_>{>$d&rD8wBc-x+op*Lz@!>Z!w1QT{qRO>#(-ui#9qOo=>lAkve7N z8PKW=a!I+cAQwg=%2UhY4e8-u(TX5!`zixYQu~Dbck%lOxpz0qmHN6;gFB4*6;x!*Rywy3gR%g8}^o*j`|RDSG0tba!-{ zq35#0^_wm+nLQRLA{V3S@Xk6`>NPyJ;ZJN~7lmC}_o04sm7B8@w9^^AzO}Wq)huk! zR?!bswX#U~K|S#wfuQ!ZBlNqu#pa(s?aYvQUwWV?2KFLwjLk84Td4i?m&5!lsDk8L zl#czk^rL2HtubAp&IODqH`8;*xBqyAnlL^0$)9~Q>NxQAGraPkeqy^0~gPQBKu%z<7xt zjs8z1l@oI2vwH$OuxqZIRuDGTn#)8P=}<_%wzk|n@bW*TWG*K-dOP{y=$C(p#a}u} z7voXUTrLHshB>JizK~%{-Wd4jdcZ;I$+$i_q2IMsVbhcaSc(MJ+$-%Gy1Rv=-=PPZ zR(24DaujMG3O8j06#b`*en7MfV+ zgjz%8K}ksZ0|^LT9ZM0J@vKVP2>0dEV8|3ezWJ^J4@-a6nZc21x=fqmgq%VbwRqku zZR}=|R-d}3H7S_q*>yZ^B`kY>-mgxDSo8O-^t~VEv)qPObdsHpph1`h$B9Kdq}_}W zkjyydiSw&s5=wXN8}s(=ODpo6ova(q+jVqbD>CMO?q9+Y z_Fv578CV%q45L#_g0$yDhomFMHFb{2ydopxjG+|&J*}jYr90~L^Fc-0hl)hU>1wU) z8hH6?U4ZyOY36H|A1B?m%69SO)EHzQUj`d4Cgshjl{F3pr++N2I+4?DYa2ap5k+0; z-16Iw`B}I>N5fk#C%fenv(6);Xsuu?|Gw7c$Hggrkz>W(U+DHZJC)m;wt&Un4O~It z_1{vRR~r4p&A0iY<%hH%UBuC!Ta;1kK^_sgi2>d;Sv^Hzs`Cjj-S_55l@D$Qg*$LH zu6lWR`a2(Sab62EjpD?uFl@QW<2wP^jr&p5dTNnNw%_KQcdoOGOH^)T__o)Hx@>tj z0e{(v2#V>rZ(u=T$&x7h{GU2D_^y83?M}V%tjOUh_D+8^Ncf8Z8Fh{Y#3XQB4n3DN zLls)^O~(B>!2ZTKUw3XZdQF_+XEZ7Cyd#TJUx(0#)7Vbh+hzOHsG`Mcf5V>F`37`5-diT@%6X9TmByo znA&@P10^!zK6M=6E}2xfq$MMIM;7@%3pe%!(pL~~21M{G`ddIvzuo%NG2LqHPKqN9 zxU&F0;kp>F?NYAn6(De;&V9YX@)b>*C_O7tv-}``7-_in-5C-}1vaq)`GqiXLxkiO zbZrl3hUE&gTv&DX?({FzO2zv~e~-Z_DO7FRKPmS|bw=Gr{0$X`Rzo495-nHQXL z*yHCG@e7{i!x*QG!~X2y@DpcLEnVwuE6U6ERs6GX&`=`bttLoZPMK~8L<>pGd$)|{WYp4=7>Cbg2S(y|AZ_o*ssGa$o?46W#Eu7=p_k_?d^IFYUQWjVb zRZ;~&5*2u+=}9j{I_p zcdknSx3g&OF!=-xz=qD1b>f_2zu53wkEjTLX33jFFaN=jL2}*WMuq&v^-|JTbC`&! zPJ*hiP8$RRC45j(uxUWi&8g}xn%gU;EJxX|WF_Fk#4>d;tmN4-teXg;K>+p3!`H2> zyykg+d9lt0a0*{V(zeuYZN)85_Hx$TN zUe_x)I?ETRPFHdJZS1V-%R67_>U$ore-Pm94Vx6Cxiqg+7A!eWHA8#4)nN-~lL^8G&7jluRXJ%U3M@{D(@NGyh`GzG)AG zNM{KotuaYZcl`O?Uj;f3Rr#-c4O77Om`pdGdJ(9lC_K|X*;lz*om$7D3=o?=(u&$X zVy~j-9DgUdH$t*|^1MVZ?SY2DbQRrv5d$j{%*Qt56O6Udm!FR^st0v(biByAC``u? z_4*%QhXmhhj5CB#ZE&tIR;>wtqOP6kk(TGnD;{?MxW{fAXT>p7DD+AKxsVdJy7_76 zCwpggqurL~XWFzf@_Fb4)-@Kfr&&?+VdhiKhT`BIu$F3R8br^yf4I>bjhy=J2=1*{ zTocdi`B~{avbTWB2q3gos<3kkp@uM&D?h#`O z)|QLnb5K~Q9V$TSs#Pi_p0~Wnd}oS4v$|}al<0p(%oL|b+Iq{ zkHjM&|JPTz=9e1@QYiW>SklK%5T{Ni#8NT?QyiZWyH25b6b%k+UF1i}GMG{O!IO(P zeB-;>L2ENBA*3=7u$GRVM$&~9Pb0g(5>B3+?l`X_?|xmN;i853^Ik?VD8m<1nB>O= zd!72A(yPPrA1!y+lDJVEo2B5X8So!!`nMB0K9C5d)UuT%_j5>@4{H98zP-}Y_w!*f1J8uYKd3jPi#FzV$F=sF`|9#5YAZibw zEfYQ|24x>XOp8dv{YQO~-oX|@dgvEq8P&t=K?h{Z5aMAzQvi59;t2+zvcmi)n8>H2 zS_28OSYP);3}g&2fUcFJ4mtk)vuu#~TDwKcb)*3BFq(O-JP!sWC~0at72$wTZCBkQ zC3+Rp(JOjuEu)nLY^*LuWn(AOR3|sVz5L|Ix!Z4$SDX*#gFi)^G@IuYB z6nymd9f8@QH$M9ku-bC)#Xdu#s=R|F}}xANz%5i-FSmXMr1P zG)_zBitTJnfK3Wq(=FDyx@FJT^JO6AdJG9uRCHH~ zZ_BM~e;&jr;c(Z(mgkG=b|%_EV-jb&{d+}a<@77@nq>`bIsf4tRwWdq4o3PC6?lH9getgwC}t%Y_}EwFg8-u9 zu{R{2GZr|(6_jFwARY7fiE@%+w}E-kb0@?J?tc6nzpDZ)H*EO%9YY4Uu1XG$Vb1xA z?8y@pSc43K1y=>HDNqGdEc`BAdK?D3HULRp?sEvuo{pkg@wEYx952IZ`r?%Xcq|>j z^P#4p8bes7XrtFHwGE>?O_R4|M9>*W7Bq81fE4-R@99VU4X+@b({>^2HueWb;F^-; zUJwVe_Dbro5TSd3ftXd3?$4V=9+`nwO&Ox(ixQ;2W{qsoubx}9h5&oTCK7RNg}=_==<4z($T;3kC?h3FL&pvur0RCYO~&}jODPFu0cd#_ZH z*Cd~A1Ah4yrN{aCfa%7tg0+JFH&y{ML6QEDu^1pE=L6n+nL@56IsB?hv+fUnLzaj4 zNdby@^Qi#2X{j3)sE8NZNGx6P1Y$3*GX(kYc!uWhF^)rCnS9iD9ltj|IoM+u42!1<_E|E)B;|t>^q0{$kzDY^N1ZoQ) z+zGQ6pofK5?go?7r@PbJEstLFUa`$_so#u7z;#6O+abKV+GK+gY^U{^wpIx zV>+miBvPhFTK)Gf!t}b33!ZBU}5M zH!-fG$*_+1B9-JN5?nRq$^Gy_4aKc{6GkV~nV#X!tD4RJ*?EG?XCZI(q>PNbUWS_w zS*F>=8r&i^fbjy3&;4RVrcbhFjW~Src@qe!oh1ggiVYy0+Cy;kK|_ck*JO_CHYfxy z>RyOWw7!bLC22xgx05t|tieP04u^CyZoZ?hjZN0Ho}3FEE{w>=8Rr(pBk?{#=5!-g z==!36(Zgnc+4q0HGdee^MhUxj<$!0(sK)+=evhii;UlxvqPc&c`7B$cc|KY5w4!hY zk^o7>8prZ1Q%=Kj7kd~HZp71II54VfbLGz~;EZXX-r@!5J1$IO-rqx|?B0o$lF#%q5lfJ_dGu>KUe`{(KEjups!;#*i6fA^{ znUVoLDf}fb>gpN3Gk|Pl*_34n)qwia#f4qPdsQ4b+^sMwG3?$|&kY7RGWchVx(V;pwpVvH<5&VOkv(Ds zu#4G&HP4OYiWx%+4+GL2nJ$WSsRMKKz31(&A;7Xr$AKw?3Z~bn5s0|C^HiZP9duY zhp%hL;#us^-wuL(pl3Vu0b(lmryK&QXr>D=jOF?7~iPm6Vet z%PB=OzPu4Buor%-1^|gs$BVM-`*_waCa^0IW1*uj-xT$ZU-2kC$g*iGY8nKQRX#st zyo%BCN=U6n+$tJiNcZc4TIe6a5mB@*EJZ{b|39sC1jDb0*c6Z|m&pL#*vG+#u}lGA zV@aQP@TQk!7-)diLL{+@WH-?`lq6JKdGd7MF661XQFp!62Aq}-zwRXV&JghD{7@3F znFx8Z0c^+vTruSe(ENjKwy;fzb?)F@t^9Xt5_FN=-eIx3djH_GRWvmiqVfz;U)G{b zyk$qQS68Im3A(O+N(Lj!HPi1kNh7^|E2`UQ1>~AM$l~tHrY&GnF^s^GKNo<0{F#G zwuvJ}Gk(pz4177`dl_z)D84WTtY~^xGe7j5vvb%k*~#gLcD8L>I6x|!4OV}I0}=zp zuw4YLZY^yt#*}pv_Xs~KuW0h^dV0G`_tRgex=2L-YRAFQ;>x42JFiL2qbk3-U|kyK z8J6g}k_lX$bTXzfqzSs{fzDSx9k^z5hHrMs$Z0R?qWK)lq0N);+`kP#BZVnKxS_Py zc`WcEdIk?FIo%%J9CP=OvBtr3*W_Q5(xrmlHTc+u*CNr7MwaZ z2;PGb&v1d1_an*_J#3YS?P%`56rtjz8R}fi6rYTsuw)uaDACib>G4ngRgD zlg)!C;j9Q~x7G7Js@>acXd)%8{KO-KBgOkYG#l@8e9B+8C?%---#B=NZ~^LVV`>KA z$~<+4!*34Mu~E(%hrVjZTBI&%!GGSdN;{4Sko$eX?hRD2CZPBjC!HXatI)O7V`%ir zhc|bxqqe}`$>FJ*%^l|@SRzt?z4W0!(3N^F8R%o{Ehk;*b?Jtu<tEJ?@*Ou>@m&b_ zF|0_`q<{utlGusM;cugVKk@Uyy%j0t zS37S9b`XtZysx(8`&SC7hM1cXQ!oiW*aoa~jnx~w#SeF4p1r;Jr~irHm88^O>5tv# z#=72DC(ehOc4>3oZZ+!Ve7fnQt$KXvlbpskzJXzKodL2D zgxRNQGHr*;)TQy>Sh`i_uPCqy3J?sMTLtfr{9Tv=cj zCf^onSP3gqdoum;lj!|?!&@)+WQNM1Fr5apuOKo8+~eK}A9pnd2k=cpD8MRLh3!61 zCqh>+jYLxXuTc)zr1MlPkMQmpvT#qsTQP;C2~daAwgR%>K4y(gc@%lTUl|_YYojxD zo3COt3-UYoN^8I|Ng7}jc7;ZswKV5Um*f=Nu5bK&mfo&<@;akPvwNxIjVE6RtgE`D zQiuHLknqP0xc{%jSSRy6onie-R@b1mvn}NAk40y1$yXyh6O`WHgf?+3-ywz#Ct-#a zoW*?sHZUMEtUS96hM$Y44UqSWqGAh`9|FM=v?^k5zq}HJg^F4G4$6aN;q@I8`h@$g zgkK-EZg~!?jvzg*KEpyU>Fw=%S5!X}?tCH*I&eofdyM~Mb@|*)v#~nQ47?oFV=jF| zQp)Y*y=up+C4^G2NqG?1{L|*b5Dd0S9^K5 zYtG0Pds*mq_4(J=Cnna`XbPrVjJNAd%s6X=BY(q9Q6sSa3jK7cb=>(}QpD8njX-Mg z%2GRSXI!Cfrv>(rNX(lLlZELj+(p zQge(zo96nszh{UlRHKqdA^Du!^L3RJlMgBhRN%sHF9-tqXr4vGhCG3nA`r%^vXI($ zdpyT4WVCLK(%inZwUT{33@i7R-O?*+=jWNAx$rDF>XT!(i6a1kY68&KaIiIELVq8a zB8GR}y{ohhQQnP&TAKhd<}%cyOwGlH9)E_%|h$l zE;)JE?T{Z}KS0KUtzjJ+^c2L;PoThTq!^_GR}%uEP$NH;MSqcTIE`6(6(}qMXiAMC zaDIt|;5@rt=szW3rni3|;~E86rjT9j`<*cfmssH%HbtkV>-d9B_vE$Le*1Wh*^`18 zW+EEUc0|Jm&)p5DbepevpxuXjZ}hzYX7CJQE%|&_*OHI~>UL^Y;8l#p6=i_%A=5&w z_*96Us>gIjFbCXUsUMqCoXx9zo=GH-)hpy7(_HY#hg2Gq0hLu3x*T=q>_Z0Qf+-3t z?RA6nDTvoMuT=#`)R06ln^*xaLA@H*}!Qb1o-rIlOvHZY<01ECaE20p=Z z@F}SgfdHRYxMxOFpa)aQEGJ!T6Vw^gIoySC13V{^8UhS?@sN-GvqG3qX{Uit3*{l0 zp7??X&0GSMdZ@ljMS`_T!(zk0mF2oa(Tr{ASU8!PfqrTc=HEDZC_UNAeA43oy#PBu zGLxgZcQ^k$=Eh>nb-i#tits%Ldz5(YReBHjchhKL2GJW;rdx~iIZ}(IN z^6t`4Qbt&Vu!kodq(ZG~N@#^+kkM+pYI&D)d&!CUw>iG15!G8$+!Lk>b((#F)qKJq z0*?(b5{5WV0QgsmrH78GuaBMi-w;=#fLBNY!-yp&8^1vO0MJQ}$o~K{pb%ie90tX<_359Pi!MFhbn;S^^&U_*Uc~W&Ah+W%-J^RUoz84r znRABiX1kvAp(H+lI6YzaJ&fMo{LvWaaC`FDr{A&BU^xLLd>lu_Oyb}V;9^yDY}c$8 zL|%bRKyx1ekeTS)H;CmRGxC_6AF^R{ZiGW6l^voRjWG7P3LHtUY^K(kA$%_3KCQ^WUmX9E&-ptgX(cbU&-Abf`^g4^s3_fT4b?GT;yD6%Q*yZ+wpE zk5z9Nv3dAHaNvHku~q}?&44$Jaz>;xg|E(>-Zhc-4fFe?0CarNVdiQ6cflGM0niDH zR({O(hh}qE!s^Fuc&^O=Lx0M$`R9;Kh8#G~K@WpM2D^+l``YL1g+%LPe#$A|dQ_$K z80AOX+hm2MUwx-(RgHKUBSuykmUkO^?*0YPAbH?7X^p|-fVpv`Oqx)hkWeSJa204^87rrg>GBFK1%_x zV1{Y5L0xfsE%YMLb4sd_oR6h?H3TL!fXRDRaB2yD})yn3m!1OU2}Spzkbwei!KRipS{*vZf}}7x*vV z+`OTz^^}06_lYV_y^GTs9~R)lSAgK%Nw-3Zg%1O+GhVbLuZ%4C2VA*LGpg@ zj}-0ju0(eqUi`NK?^?)!KF!27?4QA0TG?N9ROgS!_T zu}^jI@U&kJ6E@HcP`n+y$8mI?n94-qUI31s6#yt>DS)~FBPZzL5GQgnBLl`G1!(QA z31*^nw+>Pmb-W5|eh7FWVA~n_XV35*&IqkG0ujpvZuWHEoe|NU3?xK`1#wLDh~DZD zk&LAjJr%P81D)=rS|3A*g3XyWXfCC)!#$rnS#+p(|NZVmVDr6|tff!Xv+=pit-GM) z8vX~KVeq~tCosYJn5b24cDscmx#Pn^qmh;NKhVLnlaMP*CzpE+-tFKx> zS2{4gR`z8!hnc>$c|XmN#t+kN;AQiFetfxDJ)Je16{vd(@lstlTIn$xF(g)+FJuCh z7NNkUkLF+NVF?~Z_?&Zi27=B=r$j+ryxH%>@gTYYFW9i+1Xz3|QaRW^`(4fSB$zqk z&cX0lG+0rUdo!~sX8f6qm?%*q@$B=s>6W!sHb8sX*Z0J=&6}7wo-C+5oHuj;3wrto z_xQOHQN`SI+YxmUJAbxJch>098xzy)`mnoqoTvT^0h=ScBFAYCbn?G}<2V89s{;8+U z`Ks16BLlkg`m|F5*D$bn5HMQbms^NE{XtEDj6I&|-l48dS-cPx6Q_CfJ;^$Wa0)Q% zyuMw3#asrRbySE(R8$?+D5q!A$PvTg8j$50qK2(Qa?CdRipB#;wnfkZrJ1q8g zRME#Jg1~-jj~- z%~C(NzSUZb4%o7kvSEvq9Eg5ALjqZ>?upPY^~s0Ii+Y)V>AoAJBR^BdxCnH?wn|Wv z`V!6OeBVHP=Ov=2^pF6pB!4RecX6vL1fXW@54i2}1yns`26oY5Ox8Z2Q=q{%YaW23 zL0%6XeD58xIS+QHt+cnhL?-y@I+6d6rgMR2`v3p<=!PzukRmiAL@rCL+^UfYxh;)Q zVTIgj?j+@s>&j(_ayKMIm{BRWZIsL8GV&Em%Osb%`@j30^FOC^I;WyD+xz|gyq~Y< z^Z6LRG+HM%Ag3V>asKn+gfBC9XzMYzX$#bWfufh0yp|14UIgu~rw_4u@(-y?#~T@=q)bdD!#diWX_~B7dP$>8&G1M) z=vD?@!tl}2`d0|h3Mt{}=-4S1E!jCbC&vxSC9HZTTxap+TL3SqnOxI&Xe9yF;M9MKOSihk$(?wGd<-0Ztpvf(@{UL!+S>GOY>Wa zy2L#gPDj1$Nn;7@qHj>=8iv+);-cQkB5UARD?-YDA#svWi24yM7V|rIzrrPG_%W%~ z_tccl=t=X=?TZht3Em}@*9h@TKWqT7tQQ4Mv$5%-uLHjO{%9E-VtE`&=nA8jHQlTa zK=NoYE8#DY?cos-SDw|(HSk;0exl*Bd1;OwDZS`jq&X zEaQ$%XRq*-i%#r|h)W5^AXJvfs0FP3+%=IuXFTS z&(Q7ouv@Ook?jTc8r6?Dfp^>ZxFrFVGYS|w8Y4}&fSht>K+S?v<)_CUi9#VW-*}Y1 zj@escx-y9`-!Y22FDBiFj~SW$0>bh+*6mAle|#26pq)gBf$yTzumm?8rjZN>O;R=7 zmgMKwdk``bk$h-k8d1i;@{QWwSvX+sR=lg8$4tM?=@5J5jVOpA_nT{+Qthx-PC9<2=|UIHS`H$hiI%|@i4%y(D?D1upJ~8`fTWvDSi8?@ z8KN3OaG(I^V!!So>=`ePWcWH~?%MpwhNf*rQ-jjXe%h>|7NqauzWv9KlV?6BjY+;M zag+q7fbP!Pd_>TimVY4l4vD`gNA6V`h(!W!BRNQ!YMI9I`Mo^KVtM?cX0Y>gwZnCi zGoq>l%c3gIR_b3O#@)<6W`r&zK9ubmlgY2c;jp{jSI>bO8Mw}R4q}X6QTISM4-G~F zvd0~X$GX6F-gqGbw1(4Z^&Wm))jU4}NwK-c8}Y2$(-tCofaEJ$3%}!BoRjXv>n)%U zlYJOv)D+L)i=+q7?=t9X#=P<*V2XTY=oM?U}NFZ1<0}z@NE}MER@}j0PC~|z>n&oF4_0{eM%CDD}6-05c@Wt1roW&7|+O+#d zOE*A_KO(Tv^Y5+I=TD?vM>~9NrN&!BbYBl;0l9p`Yh$!OZ+kH!$!T+`ZG-&IE7zfi zHFb{-xG23tZ0`JcC3}5yb!pQV`GSLP30((1XGf>JawmuSt+j=oW^v@+wA*IyqKXs^ zjP93phW6nqkv zipZLNr}S}NEoYOx0}Xa3aB{OK+jbtqNvEQ#utjK1-5M^)0RmQASQQaWCJR`qWPLsIOo;VCF%MkNYQXvxpD)}&J zR&ZH^Pc#{Xo%?!mx(hN-1w`CvXG@roem$QO>Xhd>2R zJy?ifo?Wd~B@v|U_^xAWp^ciiy`AB>Goiv#30VZYZ>ytb>&cLLvuCKp0DIR#+fgn% zpo)#riwHbl(xUCzs_SrfM0a=urisrxKa*pPDSmX79qfXG-kxmcZ`xQ3bx40K+!Z9j zInzbUc|dVsf#s`?px>Fmhsg5l*Ius*1U-P<9Zy_Pf(ur=!9(2G&kWD&jVC--uVoxJ z?K7XwiAxpIz?mT{XFj#2)Og;AG>DELPt>H_y2l7b29-y7cEg2~VYWAts1i02X&t2| zyO2H<2aT`)ZBxBe;#U5K=DZ6HZ!!D+ZU6hVQ2UJj96udp;@V6&aS<}mG(R-Ay{${@ zXvscFyDi~#!;9S{iI4?)ZXZ7y38;;Ngb*_b_!Og3pZj9d86!RtxVWvY}uGS*oJ+|9VQ}Ouewcunc8In_Maz~m7@VVaSFdv=+?=_*%BKqdFcTO z&>r@(aUU(c=tKvP{-?vupGS_O>6^Zd=7u%;BJ~m_%LnaHODuN1c+196#9K1CS1O`b z+zS>jrue$0f^9K6vOcj*m-5vMUt{Z4mTbI>Se*(O<+9u_Q8)12-71dPO8T=~!*(_| ze-Aw+(ycB~8hgvC@*KQ1B>4uZRjuJ5Eu)nrTe3p;&5D;7Glq)eR7^x6P2Z5RWFh%ivsR1W)BN2r8os&d0eY|Hk!i$5F%EsKBW29iPCKel zt&O6w!nYTep8~b$91pqFpk}0A>oID}hTFLl;{YESqDo_a0L34)114zw;e!H2)ro-A z#L2zn?$JXeR@RXSPnZoP0b`Nu3%B8{oGaz1Y`qrt#KeNL_MQT@9MM8*7B(-k z@XdjknWrm0IaS{R!zA=}+NvVfVn9FH-vLU*4u{NQu_T^Z&g&!)(bG+@Ki;qO7^O}v zE`W~8$;IWWxl1YgM8=0v1_>N~iQcp1)8LM)Dx6m6e5E0b_y zB%F&Ij{b?Jc;lDWpG&gMwRJ5i(j=gvLqF=dZi^ouZ6K-Z}Yb!i~al9*5 z9)@_@kotl7ZZ4n382i9Cs$!#=`FZzAcR*weE+}N=lhSA43#3~R5-ROjy6B!d2FcBS z$m70W4;4X4686yBSkD}5T$02A=E|HV#go>`oIoJMGMkyb>6cj|lsUc+k zL=DY)&_@C@7;?%LhuH&?dK>i~oLohG?{T;l@0Vcd{qn+LbBgynRBLC>tuRer9I|$) z3EZ`RUx&|Z*oiwZMcWE4t9l;mF-zTQi|Nf9r|d9CL9TLnacHh|Vr%=#aKlZ9QXGYn zP|NEzFD1V;Zs#83r1N;CBV%!Kow)&!nA^Z}PMzu()N9GG9u05233glZ^1#Ym|Maxm z!qx_N{%en8>-Ke5L9g)4uS0*RLymb7A=-jm+tC5=&aX~i6TCwoQz46gR9hUDm<}^B zKrLbrN9VtWI&f#ss=6ON*~AB$%O5jDpIp{8PkCtY;RQ|I$>l*Zds)#|jENoy*Wr#^ zOaox4z{HybeP>MZt?{w*0^M7gMAyl}wi|}fU&t12l&*N&z@1HRd97r+qu^g3QhcaepZqo%2Wc;;7c>#Gb2L;q|Y1w zM!g?|_ncBBIv#q(JBxW0R*+OrVKH#JiDhx=l01n9^hy&*;m|>!BTzpxG#t?{R*SlU zBiyfa0m2uJ@9ZM2tipSUE`fV;ufykUk}q7S=!9=={$(%50McT{&g$%@-HYD`uY&JW$J7~{cM|)v zqHF%BoBAN(t8VLnV=K*3S1%$wltdU)*SLu9jUs9!tL;sr;0s{?m3|G;tYwU>1@n9# zH{!iliS9NbfR;(*OpTJ$DF5#bjyHf~t%S|%0Lz*v6c#)m_nWfFYTdv$C@{tRcj@;5 zDUEBW&Ujq-`1Gk8nNx-xwC8E&cU&dQcBGihF$9le76TRX#aR|RpUkC%eIGIfX;6_A&9ZDGJCWoO(|CnIX zsY>ujC@nLq#}<c-0_sfUbn0@?_coU64h z92pm7nefdvsn;D2yUBXGI?Dk%l%No6vp_bLN-g?OW4J8z>MvvD@0*_Eif0+ z)_yr~jM=)u;k^14$OyQ9S4X#J%rA7!duv*SP4&C-`|PNeFUARg~=g-g!{p zHH&B`<61*|E6O?OshIsd9##E=$C=23<+ON+-vS)5+WDcGGieVYq;96{hXXayBy*U> zQ=)~eaaNI<;iXCY=xA|t+P8WSteI27j2bI?0oR-5myV3&%kJ8?MRFl-egDHA>oRuR~smV?G)!qzA@dXR#SrvQ%9RF}gOrTRBhF}g;0HiK} z`FwMOA9QNbQz5iV1gvv0aqu9OM(P%GIuM8XALI5}Avv1}CaelPs(qegV9etUF*v)Z zRxCoRSk_YA>Y4Ls-Wkms!I!_dR9QFuX=YExzMn4Pmx~^La+aGZb!yF#s>-WUX>H!z z+yQ=b7UJ9$-Z%B@zP4A2$~ZQ?J~ZsL-qES6;j7JCvx{r9+VyI-cHi;^6q+t15I%NM)+;@N2LmgIn%XJ~9s|2XQyy-DufoYPncSiv3B+9?m&Z zOgis7{)e^0ju_PBfB(l6gZ9;g{FWI)PIhBHB`s0LXmu1l*JCKGUV^7%b@Nw-J)LW8D<;1vC~OB@P$3 z6qB>DBu^umIGeq^=3i-iG$e%xv^{udy2~(^$wV6~(iz8Jsu{Y|nM361P~E$7o`tGj z?Kww~vc`%i{>#_DKm5M6YlBG(UFB>{Z*Nnn{n>c0dY^=`;Q8MVS{xYUeNFZqOEn|V zc0Zkr8L9B~sg|Owoj?8f+7{jImB&HvUgNjycUBkLX8l{0u2v{Mo#OjA1lWXYT1nIX zQrw5cri7fn+3-d?`;7Bm8~qQs-g-kXpr^v5w!$wgoX6j0x%{%yjR@72vauHoTR)yr znY2Htz~JmRv9{<2zhk(A`#(-fyQIVQwN51L)#TqJKgz_1xwiK+gI}mHw1ao)??by^w0sXv1US#VN(=_4K622Uh>q8k3&CLYfmQ=b zAJYS!R__vwQY(nS@eoO#DvmG2PGT^0ESzZv(%mg!cyA$%#c&+{%drwd^J@a;lHbbaq@Z5TxL$?V{BO_6rr@VFFR|)BabL@EnT;Zr znHLN>9YNY0*0pO=+fyBm+xLwyex9G)4~{?fX}y2_8|gOcGTj7m*+OWRU@U!G*j$cWMmv54>3895T7lc02{)6Q zDvQ)b7Zdx}8pTOSl5`Vz)cum+>YPa;aUC$@S!632Sg|7F;=UA;IAYf=VdDofk$c{c z^4^lnCJyFMYs9)~=IFjT@lH7L7~0@(8WH{22A7V6h`Hj-eu_jcbHaQ0ziS;rJdNGb zvUMlA;-J;@A*c*zoYR4bL2$5!O@O$Qr{)_PT%im!7MP84!;s2^8L+L<7$ZP^1F4UT z>M(VdkAzMZx#J^yIjc-{-}gNyQwO!xHLP!$us`d+{Y{N^+2Wj_DQzxn{{8bum5AHR z=UF|W-2WLA@)yDK>jHJ6V_q0pi_GV)x_3w-Mjih1U z|F$>qE;${`WUk(g4z2pFX?Bh7&FY-I*X+(BatoJX8x|B4u3fS*OTWB+gHm-fTVw6y z^(f?r8v!&f|yRi9o~a|60a|bQkyK~y80}91^0JK!1aT) z_(ZKfz9k$>87w%_k@`_{viTw%nGGP{E4iSJgSS*MQj_4@&16-ig8keE6VK5-?FHkl z0#1mSVp_0i+QLj{g}98Te970E{=`N@cV#pDev<=+$;gr`M)5_@a?&)t+fL(8VKt|2 zq(64X5Zh>$yoc$KlmSgw%`Z9qG)T(smo|He#~fS^q7VHNfw=QTIx`bsvmA`e_m+7$ zk9mqUrm`+7X(}?o8An(G&)hxeJ|;lN%Y0;Zhye+qp%4JEA1xRjhCZDO`WEKJo!gR{ zsL~r7&3;_s2~9BIpbKIQVsS5HlpbrVy7#aHhpQGed%geP3y{|uzP7fo7_t3#TIJeM zN27P*;EY7|mlwvNVa=iIZ+BK)!h>^Kbp(}tp5yp64V!Chkj3O<`XcCo`DJ^RR zz@2jrS-X1x3>M52j1=#7z#3Pv*1KEF4YD zfD5e{3o(r(H$))l){{nA`h7#W#KK&W`W6OF zBE11S8}v3UGp^oGtAwgfK8Z?=w#44{{cxb$tuj$fV7K%;abBPW^&qcZ zEhkk=h05|rQlfrm0#)r~8Bk$sjfEnov_|>@E`x}rvw&>RB~{3ePEwRLhOPwrfv!N9GvYJTuX9e^M22~EQCgs??&VImH)j*=b@VTCC5f@6Ny25n#drhOuwvS z+XfWhEb>*f5HN2Kb!ehGWoLFHW+JR({t348S) zmC@9ChMIHV^|DkV!Z-gUOSNupc0wzcNzdsn*!EY+d1oTlyth|JSJ(5Y17A#xqFZ5Q zHk{&$d+h`_<2z+s4mFEt$Rp;Y_rgr0n3!mc2DST=C*GIB%WVAgX!mD%^pP@mT0JRT zrzBDoU@2HL|5SliNXY`h*oxEN*vA(i|kk3hn{q z$3hp6X%VIqzdO%9CZWLx0m&jMzT5lq9?m;{z{dn(bzQQ6D8(u-cC+p|eoiruT(hu%BKX<=3GQXc@CKu#?LO$W_|<%%Cj`Plg_;P|w+DNqNtCEJ1; z5}zhQ9{7(a;?IflJl=QR69HLTOqgyC?z7F0;fQDc2?rsL$iyme4mkiM1$faw%Ok*!d6PK3&2ig-sj7^{sr9yGC^y+LccNvBLnrqKO!Tl81zRI_LPO~$ z4!f6c=I5D}gO9%B3cID^Hk&5n@xZH`V&^MKt?$Jy<0uh+N2drIEnfX*Tq=GdTjtEQ z=k@pb?|M6QEchOCR2l8)SX9a5_BsX*PrN>X7?LF^@_Si)pYmDMDJJtoRvl_kgDJG`o6p2HI*mcVGLKLvb z6w7R%2_LxHqo#$?L=>#mC_U!s2!1E5Xf|HZ`28)ghF?TD;UO5=G6x1Y5j%Qf3954()*3c3nw)M965dviwvb_f^XZ)6QgeC^A`X>96n2*M8S>s{F)bM(b|IOcpR7z) z`w1TM@=0LOQ%C|&0K%mBVvw|H={9Kw>KLu!6_Y+8he~o;pboEswDf=erw7}Du8{*udOb<)!tksOGWfH-%MgZ z-Rn|44X>N@SaLlr*nhbF>}%JbNYceTfsf$0M%(z{CohacI1*h*3E6?e(Q8 z{Cwr(p{GfU5`)(SwREfp?e#ZwRqWWObStXQ3{m~K(l7Sav$oid;Iibn-4qdill7L} z;24FCbDg{#5D^i9)H~;X;8c`np?IeyWAZKdfQYKyg^kgY=0>u5kv!n$T^9>9d*G7n zrh!c#tB&}JzCPRjfs_?RObrbRimNxO5hWrKJm3B!7}-?MRO}WYqJMHYo_al$D|R25 z+HY4rOVDrb#UCxNlM_WZUyCBLunV>() z+X^^zp||A;#9GpO!gC_d@L?hOYPUov2DA5i8qozO;tA%B8OvstICMVv_h7tBA^f;a zFwQ3@ME7bp#qbblAn4(B@V(Mo#>&(k9K9|S&*iiR;nx>#hb`{?O2W^(Kk3Tq3OJ9Pb|dR3MK!`G=9t=n5MEWr#3=3&rVxEQd#xj2;UnAF=l;(QmQL_zw|NWOTX z9UAi??-#prMJt2oRH)kDOb{>4@oROsR$`S@t#oyqc04Uo7L4Bc@m^QKW=`%tUx};rH_Jme@i0hELVWjSl zbdyVfbd3Urfu3q*s(QOO{uv#~wAU;wOa;tuYlG-QhV7-%`s#rK9&^#<#C|{+0CVGW zj&+QCL5M4j7ZNz(6b^1L0zwF$2g?&eb2=NfB=p3j1s)}N-y<8_Uid@QOOg?zY4CeA zbbY;fX?tbk-G&d5787$pCNHl?X}P7x$#XPd#s@!CISejB?VEo`7yG0bXiY~b|3*(3s*YxFj)wH$lw+$Z#O9T_1PR-VdU zqeg)XLZes8x^M6X=k>(GP_h*0d*#sa+R7!OP1=4x$|h`6+uwF2s-(YQC3smq1snI63wWm16cGy6?auU!GsEyPt4K^xE`# zbG=ql8@K_DlZohV5C#B0@f;vbI^&=o32H3(EQg4F+_12{wDdz69%M&yyUtsbW%=>u zXNsJ2K2RX%=v&PG#Uag-!5iE#k#^>YOYED0-;`_g@enuO*zuWCcJ;gZOat5#z-s>3 zP5D3pi#w-tBd zL$=>&8X|$7U9Q*m>dH0~|F@J8iX0S5c+64wc2Hj79{hBS+sw4Y5PM;Eb0}h!?YOhh zzvwhL2}o{1!RNRC0uNTU<3P+>#+1R{y!^wH>?RO0R+tlkXlDCmk-*duJ}KrjmUkUJ zI(;1KG%*yg$Gj{o$vAsJ!hq|7H9|#;k8SOI^!8!e})#sM!Zw0mp{=bTc&;FK6{wdZT-oNa!d=s;a% zWu=AT{u}$n5C|I2b&Od&-+Y3%xgv(AR53m0x?d7o_uYwt_RcNBrJFrdYr&zbfA(yv z%JmG4$YV8?eJ&wR0>dy}U!7r}7`wRmYK?$q$ke%e?;-MRYNy@=xi zMQTsL#R;T>-<5jZ!l7TYn>?~HK}qC(SMiP%WiDQ3v02L9(MZFVNh{nlTT6&~uFQ+= z2=bay9Smzuc&f6!v#|Rv8Hl>UDLbuoj16GO!~FhFIQ7_TXQdA(c-Qs>O5RU-9LN(L zdi%_u6DkiMr=-3o9IT& zy1{M@#F0yg@+IUc1dlLpWQ#AJu#r<)7kekYmwg)x%B2t|VUZ0NAQ(C=_V7jK;Ytmu z+LrGJ9Uikx-4o$|_h0Wico@_31qZ53urNyuC;=rZ+<}J~V&cRqmJ&l3Gg@^*1F8f& z&M*C}e36uze=iaOi&9TH_KVOXrVi`X)$;<@zwEpn@_hJqOy0Zs=|%h$Yoc}Y!`g;> zpIe&c&|#?5_y9OthU&P$2=M`o!h+}Ru{cpblG|q!zG#DsIMms?*q>nmcnGMGXwlWv zlj4{GkAmX6Z+l$xs|ERsV`6`Aga~{q;@#rNRpPy}VhYlh+a8eIU1^_)Z)!PYp(w{xsbK zo(UD9$8t^2`U;eqT5H@HF9qe;V5%f94KKfeA)=qe{u`2FG+3Y~saQBwV4k8Wg10fo ze7KdLT5ETCZ>v0%|3l)HI!220F$6}0KssOlqVRaRF$z@LK;ki&eA@lIGx7$?j0bs> zgBt`Qr$y=N`ftjWL9s_9dOGa;z5Z-p>g==&S|d6BJ*=ds=br3;<#U!_U35+ea=9Dz zE^@Ui?1r0XHY9RgDZ{s`8E@aU-Ts$IWy$|ywrx#s(v&b=d}RrI#;MO+es665;e@%* zHP4UMGfxOQji#MvRUGQi9o5b?3%EZNHg%v>DY;|dPFH|9b+&=jNxecEAbYKl$i*AW zp)aNuCXX^pXim~T`gvLYru*pMAcyUxsS>^QwdIYx*_Hy>k(eo0sg`ETk(_6W7>$pD zF_(+Q_h5wT<}y5$Q!%n2xuArpBsFyL>t6)F({@Y4Ed23Mq93j}wjm}K(rFTp5rII7 z$6){EQn;)PYSU{hO}vj;MB?UFXZzZ3B|ugt39_) z<0lE+fk+&N#u`S)rU(l|TnMz|0Ap6E0h75zf;b0Z;*2mnAc_DHiZc3`eLRv@@t>*O z7zLbAP#*o=7z0a;%&%UEbRkK-)*)4R;NX6^!NtB0B-qm`h%+di>@v1yVCk z6k1sqH06pLs^*UG*Lq>r*WF=K6~r_${y>{CbwL>+6wKYA)Y$kRtvg;jPe^h?5b_w@ z3e~BOP;f?J=pJ{w#vZ6vlP_**r!;Fj-u;v#0yRlNrJvdIkFY7n+UW3^-+RlrMZ^fl z^6mGvsvwyR92ALEhoodlY7#K1)dwHq?M5TA18!!G{C| z`QS0iA|4O4m=UY1ZGTqFBWA0#xBg77^)D`LiF?%B_xzN@3-*on$2fAw8znhrrxw;` zHzp*maRrm{8{y0_2Z~w8^48Ym`IVzbAlj`)jF)3N&DbRPpUt-ZmbKZ1gBY_8+4h_e z(`z~DQ*8?FH1>77vvYO+@>z^KQ4NRd3+6l@w>+@Z=tR0F$A)D~MJz0|U5g;L2J33a zaAf=q*P4J`grTgmpZOJe;J?^zV;Y-oGqGxqVR2rqO zxSO_HNI#N@CV~EFZTZido|k4Xk%XWadOmrVDk5v< zs2RXkS{#TZ-<5kGb?tYu;{-EogERSVoQubgiyu0{D5{hfG5=I*Q08nbi-v3CZobos z2w!_9wLPb|Jv+bsEN^>KWeO>Hw9GP9N$S~vIQti8^Z{ltCMG6Ut5th>T#!OMrf97( zql$38s8zm##17hCQ0hS5oj>t@x#nr-pP=(E!dExT>e4YdhBzz7BuR~SKvQv}__z{nZ@&lGm) zdYbW5J_01i1xErKh%C&M?`0qQ4Bc8IEIwADBZqn$jM>CTUq>-)0nxIE{Is)1Y3jT| zbY$#qV-z^i(+v_w>%sVoQ9d2$jR*5^VIJYDopN${(8Y$57ZaT>ioLi)+3&k6P&Id4WcY=Agi_ z_L!2Aqu-{srbeMsr8nkV(iuml+PD*y6)wp8Dfwf}%J}0$*GA9DtusgQHwx~~vQDMv zgzWE+>9FEJ?Gmwn1Dis$Tq&xd5hIj}L-+?b>GkngyFt$0x8qyiE*1LMnQ$^19EJ~> zf9;Sp4BZ3^TWV)!y#&mEhE%6NE|DX?J;F>bFFO^2o|Ou%ep) zOFt$S@{;6driy;x{?8sab|vg$*q)ap$k{sNscjR0R&i=rc>;?Jeryw(M!dyy%Md3} zMgr4kgHlCnh_f6z1%iq$Bm*#rOXHUes|zi_pMSLFeCVQ_pt9(cZ{)FW`XUx0@mo0d0}=1lCg+@)MUwL zGP8uO1A4&!=b*1wk()|{n2an3V_cz=9131-&l~#1zkxS_yJCoe09T(Arsc*aOmNbs zLJ=KjQw_`FrzYP!Zh{pho!UI>Yh{8FNw*Becf}JmBvLXZ{YsoXLp&Q_#PkLauf!_; z8sm-!+6q$bz1XmQ_Vb4v`kT-;fbxymqYjHD@S?3<1`bB^8a7421f+{)*iTcd1*I@Y z{06*1e^3p5eaS!U{53%zRl?y{BQBpUDf57d=4o4Uvi@-iY(UystYB6N)xR2E< z630v^l4$UpSms6%g6=`oDn|dd-xG_Zne1`uA;)Z}wbv-ISQ%|iyz)5I4X=R98nr&m zCJjPqw+RGx2bbc@Eu z#P17*^AnUUcVPZqZmVk9S(=QA>F5u}H+61@>QXBNZnED0(aIBi+2;nnC|^EF?Mqhu zgYjOjmn1g_Cpt~x7D<8FZ0HR#S;i8ju7eorU*K>!>gOO^f#CVEZE;j8ER3nL5pdY_ zL!0=Cq126wn7Q!Zx>a|#*0ZIQlLK3{Lw>fbWovrDbf%gl7|_i>lh2++Y}ieNX0bGr zx#yenbDwogRa3_tavkd_D&@?H8chF_Dff(Ork_@aDFXax8DOU5|aI5B9L zB{FEt;YTcb-pHO=VlSV6u>E%kSh7d+2Em{x3(86EATPc89CmYvLI1>jHf)={QLpqg zlBiHjAmpCfvQV1e*G?zkgAse#=xDEYXBX-`{Q6YFuvo? z-F3!+M<}aCi}tpQa0Ve<9O6(jTXA!R`l5pmBVJ&L$pd*kK%Cxe(DcWz%5uJd)}$ zdM`d3776O|cX!1n#9sP!P@oD0$HntS2go!lyma6Jsn66}hoTsMLZfyCS1)++&lGnh z@Xu>sty}ie2i-1Nkz)1#0Qzsi>u*)s5!>6`){VOn^Nw3>5q~yXbGK z`!!@-DN9hx+4J4|Ji+=7`DTfrsYH7Fr3R_wE882bTbc&&JtW1`Ji=C}l%nT8%mY~) z(FO6-scKL$l_4>$h;i-dwI+J(pTCV#L(`nMOH+5AzNAiuKdjbxtN?j{O*hyLO`U{` z2&a;|Rg3{QQy*niY-7Nndr!RRTBI7(=-Hpk`@2~a5kB5LqN@h08qG=bdvex2zr!jo zqw>34u$B^#;q8V3Xdb|at^|8M#LlVBUK*E?>oWGLoZEO!f-qVdN-ThXm*8tvN*V`( zejK^Bk9LFqmd+W!p5l6s(cAViQN_@cbQIhI&e9ci$9%3ZxtqQs=lF&+lS9D+q8+r0 z#>k3&e(Ir|CC2>VM})MJ?8Dd~TSn}+o(uW3>jWWLwf1lGEn)7-B~!@}!lrsU_PM$B zjO1g%FB~3T7x5F6inl5b_Cu8#$pqJ$QHT+O-9{o3`yn{tuUT-dNjfe;h4_|Q) z+gKj*y`qq#r~TEZ*)d98z^i3zrN#pkbt@Myxi~hRWBa&%#;(k4*<^G4%!X$k3~R1W zje;HFa?;l3tih+7gX!Z*mVPrXy~hTV!?4aLGjlHw|8#FMOdqcpu@f;&dU5FetruI~ zT_bug^0p`Rg0_dNS~foSN@Y0h%lOftotLf=YAu4TtedRmn&p#h*?`yUXNIU$#4x#( zx*1NVC58Yf=rlvQ$jnEYk7?HGFcUl6CHmd-SMTK-2pIu}8r0_+WcF)_O*$#Rm~?JW zS6d%FdOwC(+BT_rC-azTxuQ8qKpNJ474=7P<}lR59;F!c425!*^Rl*1s}LVk?JvGPg3EQvVeimVlK$w$k^>P2D#SWMP4pFSU!XZ48u`oAFPo8+u5?*nkj(p+FRkwmO z)<-l5u-$Ru^wPV`Q?D1+zzs}rdwgeEaA$IUC)n|0QvMm3k`ek!d?yE?C^bKiS)QIf z5;^s%Oc9MSfOmlu1!h5xNWtsZdr<#5y+Ez)Ao5f+wI(*w*-?oCd2S=X4U^J9ECy<4 z$){|rS*Bfek15*qjfKN)exBG?LBT~x-AO8NjK-MASe4zQp0F61yTw;f(+Bi69!L(c z$FAhQLu&sv48)($IYg-(5qxrH>)wdGK&GWZ)(v+sa1OnmCJaIuxZomTBZKr<9{J?> zilyVZh|}Idjn?%Ui|6*`Rc$O%w>O10KG8;SZU~j7@kC#(`mrk~Z!|v0_KP=iH^r0- zK)Pt1Ca~%K)*_iO0uf?8TxlR&3sA&AM9LtHI-9guRI~uCLfcmJIw1{8vh5;8@poa% zNQiIB=nW0J@^VptP$$4u@IJYJk{Rx4zG8rv`|Db{3IfM{9>coggP@z zvgP}7==SF9Qs8fjjX{}xp+6-r-%s^{O%@>|!!KzrP^&p5$EqQr6_+wI=xni{;f2@0;~}^E zjjnF`pO^P4!^^j z4z=ui^K1)LsBTEuUGoTAz0Ln-haxsl4bAH_>4U9{DIZff(nE<24@?yU-;x9(x00a2=P0 z-9qwsL7ev>xEi52tfO6Z>YvDVBy07u`R1<&RC`*OZEZ%$W7^A#Ojs7O+ zheQkO030L}VXaYalz$0R-Y_2`+adN(+OJ=VRejbr34wQcwDO%rQ1=&eBvFQ$an1oE z6EPYOG#;qc>KLN_Xznm0Ow}i4h0EckgCP^I@9yb1ceKujIeAQ`&%GyB$LZ7BqU@9A zoOg5pz1eLRGfi*X>Z_9qY_KD>iMpD8aDFy^fy~pX2q_kV#{PF2@{mBt7D(A`E;*Yj zsX@`6;ix(N&UDDrv)Ak`VR=Xe58pv~B1Zctw{VsU0(5m{XGuXO6O6^;40c1HCsTpE zf!WPDlcfnlQ(mUozjBUXBST*aNFO1Vk>HqrG85*7(*-4+J7?(i)Nni=-+0p~-7@Q8 zp({#>3VRINU)Upb)WGWBzkMO^80}(}b-=MkfEAN;$7p?14HbSj4dB*KN^r+=9nJ_&G*^+3+$CU$?p$7Eb@W%0z@Kh$INMvJ^SybT zYx|US^j>@&TISrk!mHuf9;_WU&b`wT5vm$BP|~YnfE+)u_sss;-qGgs-|l;84>sLU z30vFrW%^MftSaMf4Z3AI{dU5Ad!$tI%Uovi_+__S3En(I3LS%H8!T4dCa1L}l*?vU zMQHJ!t3`;={Oy84m=_yYap)orQ_0RE{x7)p5(T8XXN5!_Z!Xpar)H13fn=Svz@L&m z;&%PBj{1?1z@c=F)U-Ip@f!6--ymq})JO)SWZrbi|0cx0fBB2YMGR_f*8li1*hf!N z*0KSv>B3b{zQE{n`=3XxUILX{+;K+e9_cu7va8UPK(P(s>>Qk@^w(Gwrsz57ikF2Y z{6X@2kYx8rgRQlcR%nbd0K!W)dH)aZn_TKKTK>OP7v$V51SJr=qDTa#LTkgGQguW% zuxN+M61y%L7)Hmv>E3m}17M+(*y|6%xTEvw9aDd2!h@Rkd(`>Yr@_t^@O477+cEWm ztNz>5t*g^Jn|j+)J3n_ef2?Do^7#*r4P3nV>d0^*eKIv%OG0s97rE_xyS%fzvHEenRJdX)zbwJ0JR)-L+(}-$U(~vWF;ya7n^NmST z*HtE1-&H^m3#6&OWaHqc{~CVq=7xb`bL16SRzTaIYStn(ocYJM>;RbmiK@_H%4xXCLqsqj*reYx!tls{xuLV>sZT zXn-%Rxk@2gca{iE;X4aOChfV1P%I<)Zk(fV zO*CtbZ3Y|X54&DfA(0m`m{bWT$UU%GyPFDB)`d+FZ%Y^s@8Z{}NbFN>Z%(Lte-3}4 zv-%`kIAU6F<7!7t|Fv+|&i3bPEmxnBt6GAHoRMZXR_{HXDbbIo<< zo{XA$!D{B(XpH^^-^Ri_SkZV$rkWA%H%2)1z{wyj37(UU9vLoJ>*ZideZ4it%N^Qy zLz7-z(o5B@Dsh^xf9(#^uP(@nk}2wRBaFLvHBe`BRa|XjY4Rv(j#FM$d14*rCzZjv zmiA`(O)kgz)!Bmhko1a{7uVba7;9_VawR9ekYE3d+J(!}jP%g4KX@_dM*5a7Q{^sv z@DL+;}{D%uh@!i8!#Pbmvx^}-TjD^>d za7F(d_qaGd5{xH}mnTIS3YkQ}5(q@-5q)rn14SHB)@f&tr1Bbx&T>5L$0%2mV@bTK z?Jw)1Fd|NuKTmohwJS=zt^pvAm;3OqH`gz8tv?A)P^>-?;Bx4G1|X&^wkAic>ur%E z{$9{qn~hOU790wAe!2I)r@N-B*x1#--c46-jn)sY_YHL1w>NMG*V$BLyXs)u8))T4IH_cRBeev5R-r6iR5J3=(j!2*AZ>t4ih`oXMiHR;NqT*vC@g) zA#y7l4x-VYg!K*5J93UlsxjX$ktQ+IqqH!Noi61!%P%~f==XiC|L#e_s}aq5;}h>% z+%A-Y;kO6Y2J2e<`;w{r=Nlr>!GSTWco?tIm~@k^+;d)O1o5$=6r%ps|Iu{a@lgNq z|BQr$Ln8ZwLb9&1XGe%^<&dnCWJIz@B1iTp znw}FuC$svk1z;*Rfl2AT3w{G@IJG_lCD(?j2}k@Zdc~k~(h#5vhzKj2fEFC^*EOr> zP01iU(%$kYS@wMNpxAzNn-6c8>^K;vn9Y9)>f9SCJ}lr^73o`BpV>ORi0u7r5pexc z#^$^KZUZ>o^Lk!=_#hH}_kDO7?wH$(e-QYL5w7!8m+gj8Y__gOl+k=K(|9XxPR=Z;@UhjAQ*jo0# zoBl4^m(M3rIOV5Fyv=?gjly%N>7)XE7nFMl~1L!-mu|Svbr^SQuQUPo}1Un zHCMJWx3jUZMAXMMpDE2Dd`<2NBhe!^BOTmbGEn!Fze=3jB$MAhJdd~B{%BoapM%HU zeVV{L2$V#7NBXvkRrpY(^(h-ey?aT{Xt=H=l5;`RS8k2?Crv_x7|+>J{nRl%(>(B#lqVHG4SdjA)r8{7&x~N zr;qkMj!5%I2ip5Rxi{yvylH4 zW7E<@=C)shZj9Bx!HwPp7`K&$w@Ai8fPq_QV_y9V5qSlj5O&MH3YH$7A!-_5xB$QZ zwGsDY?1XlVf?Iz_Hs7P=tPZObFYa==_AiHD9$NcCxjuX_ zICX^-^ety{ZfQ(sjOrj9_Nla<2Xk5XNNowIO0)OF562EJI zUWh7fI8eiJe~n9i+GM#yzhr6|!}=0ao^V+oAUw6QMxyOQ2^SGTzITDC58nWIzma?M zLEa5DRTvFz?E)-r@5{(NM-)2eDmtGl8e=`lb~=5oH6ir37s;j!S6vUH_3xrWwp?LEK*dVIURX14qLuj)X0Hjm6FNe_Lhu@7z99cm_WFMOI_ zzRd{nwxm7=p8j|dxz&zB)ZewWN^RjuSn+_`oc3&I*!O1|+7a7kFWU?Vgq}6OtZyEU z=k5*CvWc(J*8u%PS8GVa)x89m?C_S$)b;fRoef-WUUO7_08k8hSBkLH{9ggZ ztnC8%ZCimDE@>7iM4v%3sdYE6;<=IinmtP^qDWr*XtQdbCVq6dQ45^R!POs4?L!;h@v!(3T<=XAT%z`kH%jj_EX+NDgD3%KIf)U) zVgYz>2OSRTG|J&Zp}EVVR@ITmd=552>fWLqoF#{!* zIKD?;SoDWizap2j?C0kXu6AyT9Bv(Lp6;Y(M(uSTkq>KaU#I#~IQl=DKv%8k2qqxy z^@~|W<~hj4U`~)iUMVEAM_a=aFcI@2^Bt8kY3P+@tY>?|_Z|WV^zZP1wh@xbtVc1U z23%(a*qT{?>m04)V}o~)uW+!(k%*PwgqwX%`SIO@{Q9XmnqW|;Dbm2rO4``k`Rw5B zf!L#7W`(pQr)YkLw2R*x2VjG21Q@sesYy5K^Pm_WlO%BejT4pdyM21^%;=Vkv&Pu2 ze_|ldkJiPW5GRY>9lxl^NQqOo61k^Q?~ki~8}Yb`9vudGC9bM?YJc~$3-^dvT%}?5 z<@i=gn9?+9SIM-#{jN7x6UZxC_h?n`81H#(4Y!*fzB*%@oui^xCI?rQ=u&O2mMmmqcy4~XLm!Pjn~~$C5wS%ZzA^9E5gBxNK>hOU>Zm)Dj-)82 zZVHuDgNx;Jl_kuE9T$bLgT;69xz9m#0J_S%Hu{+F^=lVBz2l@bUrr6hAu^@J^%Bu%Z*f|n4UkNdin<0W2#@ksL;#u| z!weS#NfFGUI4hVj1@3OGQt%D&|GkpBkk7!=J|I_GEXVP!IP1wU;!7rW$GKb zhmE{UDA1>@ejlj5MrqpFIoKFG`dtup;2U{R5w*4R;oGFXWY3;#oakdMi<3&j&RJ9 z3xA}_#4@-Uk;sP|&P$3l#FqX)N&a8^8~Qff?UkdV_{p{L)ziXyR`#)Z#|n)Kt+-)$ z6J$h^dy%t2g5^*LrEC&2U;^tc#5!05B0DGuZsBf_1ue{lo&JIO-m5VND5O`3%$&$x zJl4n)&(=k>_i|+eUAx04pdk~R5tFO*&r9%yE6CW*0vT3OP4EU5r{_U4=_I!JyWtEs zYn=~;_q$r;?pZ}oEQwQChU0baNvpI0{PDUf8kox`@zH_ArZq$cSj$e1oW#H5-?bV9wX+h;eXOZQCq}`AAS{^ls;SI1L zj(7yO1eB+W7%@qp`%mjASwdbRKj433oCmT5eHngeWx_JUs{RW@Awk9J~n;$ z*F~p2CBptH^Q4mJ0whB(=Hd^N<$~wjk5Wkqynrj?L2%o^h{+fz1_KJ(TXQX8z@PWq4*URNIG?qAq)xu$XAAQdFQ!d zr<~D#pzNTvmFGg)Z&xJpyhcMi`Z}oZekP?K2Q|JgZEQZXIg>iAc~RfKfEw}anm+2( zNdTE8hP{4?nDpNCM3pNg`@Qw5%H5EGKn*Q`SIMfTnI4}}7nl&oyYPWBGgFPuRt34s zJK##hysE!$bx8ymFlCK_3F@uAXH^`9cAo!?1zuNPw~N@ zb#yM3B4Y127ryH*SAwH0Zfu2scteM?Lhn|-xSg0n57VC=z4etAd133*Voj=sMph4g zX4&z#oJvhtN%q>!K;wTuE3Vgk?@N@L?qxcIkMq~XKOcHs$*$z>f>s3}=JxF$?sEEH zLhmmoNeH8o$_f1)?dlO59#L@09N-ZXL$IgOLNjvE!WFxlG2BsC@acHDR z5GE;w0EM_nYi8Ivp9~fqg$3>JOKpGmV>2)ymhHcd+e^NV++_i@up)wm;W-;(@iZtc z!zSFB4}w_Tq>q{WCqNz)-N^c;6C;=XPoGXGMqGJ#P+w5R3O` z-b8K>Z#YFXPPWS5W^m>7D5w}=lI&4~K4f9zN+yldp%OhbQ#9rY2V4w}z78SjInx5? zlXGbyVo3pM{B}#XuRX}#d#0fxRp8FypZgIVDI8`u&I#qh>@N#VVN_7wU~p2{mw8fTE%+cW}wvHczFR!`yN# z`}iQG*G~;YIn&qMvs{2NI>Zxw)&chOgn)YVRDLVBECSw#$I(B>%!wvpLMd2jJ*+f1 zYGhrLP-lbAIl}-av~cStY)9b~$RBHG*?IaAqas z@>9=z(bqU)SoI_V|9Z5Z+ToMC_;;Xmngk+u$M_EXR7?n)`}>0MvbxRRVG0PFzDL}n z0>72ENs+<9m!cwLb-YD7b{)Or6YM@N6ezyeGO^4v28kAiUkgxn{eR~MkF#*a zh>SJ#3O{VtKS{B%c~XX+U0!d)8prxhL^ws~rUAf^7Y#V!w}%AwHTS}5lSf~BURP!b zIv$+#;X7W!7X6Tlm*9%!n5kl}gc%y4&yH9cmyVm3atxb@8T9f!X7=xCdSM8$>Y>#Z zj?FVm`D*wkK~6AH1^B|zIVXkl2!;WZl8Xp5Z#o!!md#rD5gd({#$4cKjo^NbwkTs& z9Ec?hHowi@A2;i~SFBu0A?>fhf)32gH;2Tr4w22hkf%x`0gK+6Uoq}#O~>Jq zYH-Pmnu$xFSQM;6b%twaihN2N^{a@78 zx4g&L5yM5a4sC6p)k|8Dzbhg%o*mDXwlvOmziXAS_`08e1L#h@xujp)Sc#QoHQ%X{ z;Ae!+GDB&CIpphw5dEmY7!Q1?PHUnNQq)l-kwSUX8|pBy6Va6kID^>MRK;>nf062X(rzhE&%t<&RF2~}KswBH zP@o@V9y{avL1?)l81m=Ym)z-60Wla?uZXKzCA~tl zOyFKWT8|LSUhp4#{7+Ldj7^;=_ry=o%|4n{%`C^T!wb zo8L}!q;~WviP(_%9!p(2HF}fX+fjZ+XaTvovvbq9pxo#IHm8;NPfe~TLS*K&_Ra@g z5-?iBedjwIQMAJY^~wo$)0F!9De6|aiMN)p+Yi3`s#HaTXZD_4|H#uoG5u5<6Q_3#Ah6#JBzU{5y# z)3?4?-UgqnaS9p@Z-rC*6s7BhNf)i4^&jubTcnl^DtxccAMR2*4oIEtT9J8q5x=dx zdq1!&Ny%d8?&|NDR)%I`VTradv@Vjj2rDy*FZ~?oX#Z?F&>I8{mgA6at|kP=QAGn7 zPVI?ru2*$3HY z`WAbOtnm`+soaS?k}lU3)L%XafehvgF;EN_a_|q^@B@`RRY)nuSA*6-ngSnvb(ubRu)g=Ob7kjpLyNqG!1*Z%XsBNRRo2# z!_5-(L{oo0)AWJ^(Z1;0EX+?L>@!NgH~#NacRMH<(?-Fw=~mYVF?8XFYd7cm`M_yp z`C#U9dEsg^b%a=Tr#cXrYQHV-YnXDwLgDZ=F{msH=pxY2N#mW5_I5Q9j2g=_`pvVQ z9Z`AqZDCaM-!&8e)jKqW^a#7VXWP?ewS6ZB}Dy`)-%Zdy6UB#N5WRmf1;%C8sdNbch+M!2h zN|4!pc#Ujpl1wP)ocwuteWbBv(-^_IG!ZUB!s2cg-AQKYTVQ|W%32_9~o?>Q|p zc*N3m69OIMW~PFo5jsd9>T^wmx&`&O$O`GqxvvQliJ%?U@GjiuG6^sE9CO<;dR8+N z>6Y{XPZox=`BUqVZso8q%7jX+er+trkdz)=Odg6Um@6&GqK{qIRAgllkKhAlRY@PN zQk>Y*e;ZLLGa2q2$EUMUo2uK3f8aVdl4N9ajVnzswxG1@0lMIgFiHwUkftJteLw5GRh}(|zoUwb>I%X&Cy1%> z%`)1vA}W0v1mX+1_jd+MROaU6{FSop5*$57Ok@&ybb6#APDcMxozQae7;Fz5ymE_N zDdShw?kOuLrFaDiY=#p>M5c!?U0-d-6WrUGbiJI>?$~lBj&e}$5LmQYdK+y#3X1yL zEB^64UgBn&xDu*Te=wpQK~rky&E zRt^Xu)x!*pJ$x^XjGu&YSwur=v3@YNXhuAnb2wM}=N$gqY#!N;FSBYLYrcA8fBjw1 zLe6(ajq#$csp3DxLa;Y8m#?YXx>2cPq+IPCsAh~JqWzO-uPHmTB~hQX_-rekWU%xm zYG(+q6}hGR?c%}DNA(jRcBZ12*z@=B`AjG(KHD9Y@?NRG$zim0wzHCO-|}uEslQ^C z^s#*V*YRSZTVtf#+$3p zrvNKft8?bG(OhD={}Wn=N8MR;v1QTo;!ZnY-XgHED6nlXe(1 z?Q}Tb*|h(!@%SBo1EkHb_S~|JkM#LvX;)b(mrjN_klRu*&q2E$@fp?|iOxUPg<^f* zq^@MG+cl>iFoOTXmWDmGAW(XNV9k1Jvd6)Q@rQSjJ{{u;Yq25xm;Q1b%%j|IFt*hr z!*8wqF#<|6Lb^6y94mdmnVuY*s4khw@$%me54heeHL@&qRRIzP zU1cNA^xKya6%r+u#(l{~-LC`uqv>4A3k+LzYwxm1WMn2_KO~s_W5Obr$Ynf<02)T= zyCuQl4p~_6VjX#gC2TMaA*~a+(h252EZx+pg#HcX`NF7e618)0```L2g9}jkH$0w* zb)Tnt-bI(o!uaT-IgzD|=!Z;?F`_IbZcvc&dq8Oz{VrNjHX4Mwle2ExuzYFMMUMV- zmF@5OM#V_UqR&1DD4<_(tF*hEBw=Ll8M*r?8{S*O zZjZH!ru+N1T2^)^nseq_EpjZED6ncDzF9e+TBod^sIukjn`WFnIotXfRjWEg^r%o-o_&~=KAmN#? z=HJoU8AW?653W6XVfJ`X84(<=6|uXVo3-eFYm9a##kPKGFpW@$8HlHJywMjr?k2x ziRZnBc*gs#OSLMqA?LP3iXsqHo;!(5D%UI~nO`mlUSSYD2i=KA4=o;T`5tYOI`<0D zI+Tu}Sf}E#yAk6!jDA&8M1Fa)Eb~S$G*OowF?O+21R(XopHd=tw;UvAR0eDcb6#BuIuyxXS^ z%9IfNvL%M->4t^_y{o_FKeWu1U%c(n2djYnn(!*7Rv=62L`>F27RP-oD+->jm$!3 zNm&vcR=E}|(}lk-t}Sj)-WZG8XcUPA%IM16^WfTi@)c09TSNHzDKHu#x!@=AniaAw zQIi7F6O9ow4L`*Z()X_**^#l2nSYScVkOm+msuX+oR^>O;15+c!k!HcxN)?oy}z$b>(@RQOx1qLakO8IHgJjMKxE!l z>Lm+)k+0h2tI~Wn)j!K-g$unYxqgwH2AMu(r`YaMATb=cTT$WYpM@5~7=$Hqb zvMd29G25Pf29Dy3-g<#EeVRO~0P*Yc;giUxuw+x~ zxV4ZGlCN2ISi^Lh5MWlJrrj3uMNVWoqAEV#52-!kZRyzO7nAJm+wbla_It3tuF;B~ z7GGU3>_L4q*LK%uK7vZ@3#I-s|5EMCG=Um^H&Xv~!+kz{tL^B6y~!du63c&ZW)+bX z5;-zjS#QwT_S(~8<)z8{dR?xIeWmX9-VZ)6*VO&mHQ|pC7|5tI^z?p`Mt6hhnHfW# z-uZ;YGi4v%Xcc-Yt%_OBl;9L&_G%uHmZc#Rqi?ZmU4e4T&%enG-f%iP?Aa~EMz-{M z5oJJAh4Dw)<45j!*pAv->0su7n7&d|GhQpiS3J590cYf#?Giv3U;%G1{ z))S?AoV~2qG7Y98q8ftceYB587UersBLe;<<1$ke>u$F@r1ZN|G+Bmg9prK`uoz72 z2fWSZ6BS>iilz@wY=Pm}gP^oLEH}5s%{go7dS3Hsh*#QD1InC=fH6zJvn}zjrE)DN z!t-UMtR=B|Zp21X83jJ07TQ>TtfRBJDr*0#_ReB) zEf#!phy%wgX?^mPWu=_|B_Huz~RfChwY{lq~ur*sWtrT#v4$KHTv@IUgx&kK#BM=hZ2X-4Safqu7bH&&<2bQ8yZ*h0D6P+5--H+7S~noD2Rkj z{&)mpT0<@)(kH@-%g?}vGO!U&Hi0nCDtXa_HRCK zZN_gDG;&n*b96j2%HyDxP84gSo_B-<1UG3%?K@_xP5t}xHCOAU@UP`vdn4wdi}UN* zndVu87aA_UaDYkD^ zp%S#M@mhO8pOP}%g%vQZLH%~DnU)1rQViyC7*2KA32u0sXbJE($GG+Wtf_|qhW97& z_Ciqu2V>lZXR8uRLP|TL?XGSf&h`{qF7uD6_$-g5O-T~7+xI?kO-v>r=$T{1^-zI9 zC4#OBUC$Mnj=@_s1hQ3RXJ-e>opHKOd%cL##>t10Co1MbpJN-1c1kTb!*k zM1Kqr^7KnMRFeV*18)wRp=#cQVq))eTN`_#bUp~8Boe3*jG1~z32kUDOT3TBLW>V_3cNTJeX!m7; zIOpCvF|)kWUu)f zBI?`lnMt$U^_D%)p1^e)A5AjuRA>Oqni z6vDu1fy5Yc&N?DtQupU#2T%48^`)F-hbO!_)34$Qb>7p9{yM?P(*D1}CL$mwxfO>p zK*zjl(<$HbI)6#fHOW&oj~}$PN`EBshq$_WtU!YBz?$wdDqX5^-JaUf=16Mf=11E6 z{;!$NgNTY&y=z|+qLa553>=gO)6u*pWjtKG=>xK53IVgFbhkO90YCVfG$hj>{}OP# z`4#;g6eUx*ZrWutf^)OO8(IhVI4DVs3_bjU3Oo3^O6!QHc%y1)Q}JWh#7jsW)zj^x zK~yT$a;r6H{>|z^@X_5!scLhd0^0MQZf0S7I)U8X1t05QDb)F5r-*I+imiCz=5kN!Cwh#XW=$d)#*;>X^wuc(e4XYw#WCz>Mx;VLeRh+vGy#bKop zMlJT7&5~WXGmr2N(_1QL_0sMN`jxeHp*PD*vvN>2v3YhE6ELbar+8oxJ?tG|W))Nr z5wV}?yKW^BrJAW^&DW{Sw;D%fi!OrkN9LEJjFESVY#=Xs$}U0bt9~rMGM1%lk6p(O zW)2vEOzb3RP3T4&q?=sqdoBvZph_621sE52b+Vie{Xh>H2O(T1F}GLnjPF5M4p2q` zbq$&;9fv-fr_pZxcN{<$RGZS2A`z|4`G|;vk2lPsHd<3t?Qdmp$+8-`th}T2v`?LZ zBLRCSkwv&zkAuw~ho9m_vp<9q;qBB-w-q-3AO^a$5^TbzXMTRpoH`;S;pQ0Wk{wi% zwL;cQD%iyHyb$mcURe&-0QIoyj&h`aLg0h97ezS#U0E(ZTq36)y=>Ip`}b#gX7~N{ z@aNbLn#iG-C|AzqZbvJZ+YTcmCH>3(nohK3HDM8I#m3uDm+q;_!i=RA@goz|p3jNj zC+`ES@M+0tUM}k+nFmvr$4aW}(_i2i`tT(?Vd>cCqDcSlwi6aGtPy9UnMCk!qV4-f z@0J4vB8Ewqi;5bn*4)?a=dVe(CZoKZ#Y)_Z@JWbSp}4!%4#A#Q)r8$$AWUINR5XxQ z>r*iScOIL4&PU*V#ABENzn8T}hqO4A*2*mjo0vc`Sj+((i#IpicvgW+XXkqXaxM+Y z$N~x>%a@W$W`h{aE1IcA5}`c3;R26VNtY2 zc@PMD7}_q(5r4*wf0#pS#{2j5GmX?Typ?8gaq@t%>!~t0%#_+rogE#v=I95!=i0x& zf5{6f?I9!hNVVb`O)|Tze*gBKLNWCkHeYy!K*;o=6~rs6-#_l#F?ZB?@x!$@)iWWY zOVrD9^VAX5>2Kg5ONe&Tf6RN*TF+2Y?2IV#DHHvNRNazemX*)XBpB--)8nf6-J7!h zW%bMdWtdTv`hw;0?bV@0jlR=9o;40aBfzmF`?RpC@LyU=*W!CICEiV>VTfGD@8!K+ z!b5Xar-_h6yCX)k#0=EU)Ll`!*-aeQ3 zI6_Qubv3y@_|D~-J1V#)W1!ELnbklKwj|ns40`dB8DTMs#eBYZ!kaXu{U&Nd{_qE_ zbH{71jzN^kP!5^3!U#h$It3dW+0cpN6}$0W;@2SP0b}_BFVdB5LC_Gsz|>ByTtWUG z=e8JC%C5un!`+zBS>V9uBd08ZGct)8aRrHf7=*ZTn~~2^l?4I^LvlIiLQl_g?|5l) zyV$^7it{6hpu3vlxp>Jk%e-S&BS=pAO(2N|Bo$vW48zROb+VHg!Qc$jaYnopr5AL) z%_z1gq>EHhgE4YK$%=VgKpNmI>JnRk<*dYEV6cCu<7L2k0+n0i*3~Z-k%+{HIrL>L?HNTZ*w$Gnaj@NIUVy>EdhW)H$b!kvWs0pg_){{-?gA5vTzM% z0Q2bkZ49%Hv(E~gP8ahk8ZL%ikh>>m!`0Ysbm8uTVb40>FF5|kT`TE3&T~&CjK7mZ zhOCNKLZ;@58$m#xJn5TyP93u$S8#;U(RkFKJ?;JX!!y(Wer1g1X2t&90Ea0lKD8>3i1&P=}Q;k};X zB5L5Dhe`G`*Q8yO3g2VQb)-W!N0acf*nm-ni)_~kZN!ILil&77_kaQklV|mdh+UV@~1vzBIjGe%-% zPQ4-)^N1PNe_vvypAh?WH{?(bS z1DbE5A7Mccr#80H;mvl3(=$JT|MB+2+eVN}zgqsb;#@}v0W>+wxF;}^W{CU0PA1%I z%=M9x9=YaCdaql$ORuByMAYtj2J5MNq-xGrEt4B-n{Tx>=eO4v&iOVEIFwb#3zT^Z ze{HsuGZAh`cKG$-R&JRg9q6ll`NqIUr!;dwf*CeFm9GmsC*5p5m>?Oq3o@`>Mdfk> zo|QF5{8o;sdox<;FJ1V>TWDQns`^szZohlvK!pUGo^4!!9z9$40-ACE*gLhG$3?I8 zmcrRJnj{2$E2d7?|6fL@kMW}r&mC`lB3^-ns?r(v^gB1`4 zv8u2~#~J*u_atX*zJNe633D6HRg?4owE*HJveNM5N%$l_yPx5pe`sJRNjhK@J7ALy z){g%)##{uHhmmW3F(zTYF`ac@*};i02&FE1MryjvsjA1NP(n<$Iw5l@^cNM_4Z4(DumjNcBD}fN_ftlMd zo`Vcv9MFW8L|+jcqFW=(0Wn`$q38idK`;1Q-At6g%s}^R?W*tTaiel5f(1a#t@s1{ZO6!h=nK=t)Mg6$44gtF*&0% zRLdC20oAJ4yj;~$h;!GMFTR<9$NuD8(L=JY9Ms~QF|DS#&7~-R$LX!z?KYAAjg|)S za(_7oVfn4G$bWnN6oN8^RJ=b}Yr^=-YH+9ZXjl6*&;7T=gP(40=!3Ufo68UHjcl`z zO&`)*6re}Zj$&2Qi#6)@R*U`HT$vr<8haYYz7*2bO5N!g7xZ$Q5 znk}$IzZ_)D{i}t{rW|J(K@xwtdy0B=VZeSE=1abG_X) zE`Vq~Y2cjhiR!yUmSd669iD3lbcBaEHqNQ*)pjwvpu6hGVKS$5tc>p-M_k>xXxj52 zzkKly?w3xPNjufg;d&fK;v9_cr9LC5usV4hN*{9<*YJMGkmO@4c@bFOzTO{>`uz9P+q8vB{uf!E*oxu8 z48szP-wmW&pZb6FoRtmKd<7XK$=)k!gf-qe^Ha^cNb_|grKa<+^N{FtK#6)6wIlVR zx_|8fF0LyuKM}i}g<-~rQT`KgE|Ga3VUXR?Ad?F&!IfU#BYU$~)6&=k!)FCaj0kQx z2PbX0=tbws=#^g^p3cS&{Cxiy`|rbGOfF5>I8~Se6HIi6Pkb3*?ae?P z^->^845UGWX^qSY$Nwe+(c~F9h?iZsUBpUyrlIJ1#neMc34uOag3(zjM5rVwB6~UC zo{d&{|66|M-LDtRt4EvvD2Ho{W06O7GD8~2^n3-g=%t^Pe6I)YrtTb1*k^< z=De@9vSVXoAiAx7YJIyfz#DDyF)h#CDHZdngB$tGAI|Ov#Bnw=sq8vE(CRD^_Pk07 zp=f|KjiDQ^Wd7T;n-yT28c2N8=sLgWZNHqdaL^i0r2v2R&wrWJVfPnhy@l_12NV8I zAN6Q+mk4r!o%u?oPo?4hD{?%gziIvOfWr(OE(Mw2hDBqV)Cs4ZtFtanKG3pp2~#m6 z#KN}yZka=js&Efc=(Yp|DBym1QDzt)18mk*JZ(UzH$fJEHLn26kxX9XGa_t-aHOr~ zx~?BCgb+2J3_tw60Ps9)dXCz1EZ+wnJR1+1cOZsQF@u?lPDk3^aoYI`(ti}_gOL|Q z&7{Sc5F*~-`#gRZAiZyHWmZIham5n6UYqhI*e;>ZLg@R{xmJv5x1;nRQOv}~s~Xay zLo*4pKdNt->ZONerPJviKc>>m`51h)9Odt#{BqD=x-+vCdTeR&PWM-~89odb+{%$% zz%Sm(5Q6EjK|`wIKQ^}XZ31xjukP$^c?Hc=iQNgzFw6kZlj~`Wz31+nt;y}5E_4$i z)=x`RC%^m(*6yIs^hpv45p@R~?{_lNENv9<0vnwdY`Jlb!ZKh)Nq ziE8q%BwUo-h7}lJdXEQ`Q8dP=7bJPX#iSt4bc{kbaq=0qAcO+w5eu&oe6KlQ+QoBi zg{!g>F|m29Wmy_(2&--kj1~DH2qv1*6d(bywFNLE8K2M5?i_DI3`Nca279##wfG)1 zTt2RNmE*Ug)sKv2|C-*G&kxilGcx>`U@|;BQVd`L2zG(N6?U5qDWMZ|ygMtS^=hnc z9utgH5mZBCkc{EkmOzJHf#LIkedkPCs*wcMchbj1!G9I}uJ+FZcLict^*D5zj)O&H zW@jOOtHFLAhnL9I+_(z!fBzKrwj_5wK}WX1>dXsB-knPz{jY_$jn2Zt*g ztO=O!z)F)afK(skfsgGE-L1G;^6EOn`vqGQXimPm}%Nn1- zKX=1gUphV)c=3j$%^yQaf>3l-c@{vktn1}$Y-n#I<|hxnggK&xzLW9ITOG2iRgIKv z)%v-y)#HSh^@^RL;!yuEVg2FxRRrSP+lY443WS&(VGTTI7t=E2bGc`o?@bpli^1qb zswd;Eiq3I7&$9PgelSWgYiyX#(vI8_b64#QnLL+2T49ynFgE=+oI&(WrQ9V&$@1}h zwZlJa!Qr(0r(CZ7XY8k|;}NU;Ri7rTQdr-0&NjSkRlF@JYoOS|Aup>s)>LTbj)mXX zF-z)>7LH>44n9D#9JFJD+_`~I>K^6&pps|!%Fj5)4gTU2|MFVTs`mc6Z{)80(J$qw zEvoceW-Ei9J>GQM!Q-Zd!uFIahwbciw*l+X!Qf)d|_2S z>@H0&t*EzTB3(u&1S|a{6fIuTdX+JN9;U2=6gUeF&^v9-)pIa0K9A7sdpjO6v$a>N zO|<0*?I2fdxO$z0dktQ?a^ZnptwgYj)6WwN#?WiO&l#|;Cr|iicTXqjB9nOF?CAk> zVXkO!m?&fZ!(lCrDbMU+(bzCbAtQWJsKZ#=P8q#z4!Nu-GQsB*1CUL}!B`8VU5~~; z%n}>M1rGNbMWS}iilYwKhl9*UeqCw&UfA7t=fXL(4#HIe30hq2?}mUs;alIyzzYiu zS0J=%0EDn{KH?qEPl4$<_l~P&m^L%_)XIjb8%uk$9S;s|Hj+^VfzBM@y$qJ7Wg<$u zc+8Qi>^rB?sR2^It(>N}zHECtJ^iz5-MKs7W7ZL}u|2XGJi5`Xm-8`B@kAo?;X>4& z{0BjN8;3XX$(HLq5?{)g{K489U(v0aup6%fwNhS=>al%S>4U-0XU(Bk`lA&n8BN*x z(Se^k#jyQkx6Ak{Rc3e{SRU$ap))?PPhn%A z9tpWwUdA(>fOdi~;^(j%JO-SD3Fas-hzq

kZId`T?2u9KK)vSq<@jOx#zRM}&slnvV-RVvs-~8+A(|I6Q zUQGPcZ#_THdK&zrZz`6DY$7-(~GU?*##gc?i)+9c0lQ?G4ds1|qaF?zd`0%9b zNFTq`aCA-px!nvv_5~PG7i5y6cv+Fw8LgJvcbrkFgzR z=--+uF7AI5McZ%e+$Ym6X|0cL?C!Q87_T(3`*FuuvVyZ8>{;&5C~)fEt!@tjKj3<; zw>=W3gKRp+N(p0dgN&txW;zO4k1a{SAAy&PbBBm#Y?{P1eplvqt+`pVno%FIe^bO( z!)IzX!u_dRq3S6Y48k_hAsZ@3;2s1$Qu%1Rv!jw+3@5ngTsYy8wE3gcHBZ1geDdw> z)IG=k23p{Ik~iB&(&f-;ch%POClp@V^==A3mblY@?H%Uo2+m{SJ=TyCR=A{_K;yZJBEE5Y)Z&jZ3%+KI~sf3=Frt}-r z;U9oc+<;BG%+)@@kZZXRSnKgj!LnBF67-bzR`vg(>CB^{eBVF*U}8wjAo0mE9$O>X zrm}>@V@-C27TKmGOP0o(A-e_eFY&{8((K6LN5vWx4<GID8K#Bfr_}xMoJVWoyX#t;qGr?XS+8dxJ;SIG$ffAKf?;;3~z= zjCs`P^szcFr8uR_I&qI@st;6+<6L)y92Kf#(=xi_Z0xw0{Tp>_R{ zTRqY0EwdphOuyagrydYB8UC*I#)G)#kvKHD-!KLR2M;KFvezz+t!CV-;_SI1hMFU# zciqMet~Q^;(Qc6r1}Lwe;H051lb(6PX3;&JJ(bz!GN`E)D)<;9BOpbi?}Qo+ z1WvgHEvb-H)c0z|1tsA>xsF|CYxauKB?aI;PN--L$ zRhN*q0?-=4_wu3vZ^L{S-om!k!}CO`^2`x&e+f5t0%8}9c0<8ApBQZH+U*+$Pe3tk zmdeWWkDA3bPwlLzIp=%&EsZ4E1qul8W`QyeT9%inr{CvWXc6C-oOHAJp>(hJPtv;UJmF1~HgwZq$u*8A&6c%vS}ehNqtn z_?v!8x(j~V@RzZsFv%3JG2V8|T_PZJFy=E#_?Zf@czOtk`;-C3w%-riK3x8J zwwF#cBs|K2D|to8((?Qqwp%OZty?|Y^Uj+S<=YzfzCJ3os8TTh2a{~`{+16i6&MAr ziF!Cr!bb^b;AEd|mZsvKcIK1tnK3K}&vGI2P zn5viJY$>XNygYtf^R@HwTJ!NbIuDhZkHxp%FHy!ee?DrGx(!7H6-xH`JARwEgIznA zRe3U?C`aKCZGXBk#^s&(^IeB**#TUpPxF?j+C2qsFG)OAV&7$Q&EVjFP^WnNSz%QY z)UA1M4s<1K&B)m{@5@-)iyP<6j`70p{a_wmayDGm0+LPEDPyBs(-+1zw<;qyeqA+@ zkjNLTl9v(V9H;+K*~iTRY_4>cVluMTM^gZ|vR#X+Wb;NE6S%@a?Xu2n3K zIZIf~PS}(u_oy9na7e|Ho1{7MFBZ3kw|TYM>4(15F5Wul8^O4LXzTC$T7&pk;d#<@ z1DYsmqW}(1D8Rm2HX$u#azeX6(*Jor7a1+YSihj~S6t(P{{z zYC^a?nnqguu3a_CaZc*Y>G)OQjfr8@Yba=Fuv9#>qO!_-Kty^c_R3p28`X>r!wX4Z z@o*ag|5%`d!ePYeCg?PCfKdsA{OD+tR&8)X#>G3uNou6qg4QwhN2p0y(&X8FFi!x2 zZ7;Z2nu)buq?-{t@@eAxR0Z-N+>`$+W6-?#M{#Q({6HVzLx~e>-rrK0fsOdumBs0Lk~$VIVKYDE#HxB!0A%& z2EUW&I7_pQoQqt282N`ka%*VgVavQ^5i^rUgU|{Se+tHu2vjhqH9#ecDRp}FPE`wz zzDHBd;IFO`fNv_-Ji**7%AtwIO3(0e=Q_$6b)L5*?vkX7k*myJATd7;actDRBdUp` zH;%DYNpNmlt(ds_seESG+pDi`E53YreJXcuhdA4Lg{6h%K*#TlTSOOg+tIlydp99^nk0Borg%k9xRoD1-~oiDPRTvnRP46 z=1;$kS(F~arv$$s!25y^4zFxY*n-&0@U4x=ziShh!n#=+$xSlCRQ9h1{kU8$1=8xM zq{YnW#+3i8w{a(H{V(|Qb1hhm3x7kE$_?2SSk<~w;+qe_X2A28{K`b4r0fS@iPtK6 zeus(8rSw6k)fUB`aNUnitNo|F=$GB-EbT0vcHL;Iu7y$vW>>snOy9aZ8l zKO=8}4Kj1_`68{4!=>-AHCvmHWEC|y#W>cvd2N&pb26tTk4kQ!Z6D&5W?qL^%(JxX z_)5*e>p}O~s;(t5Co{IZeL!E+y0>uWPB)s`H#pYT6yf7F!SO;dXcgx%dUA>zFG`rk z`K=0>3x6^Gbjskx`5zHK?r@|oG`{H3eQ>Kbju{%r51H8Nq+EY}+)P$A1k#XJQ3)}D3ZzI5z*0U63 zp;z1LBSUKD#{<*>Os%J#{Fl$$_tjL)>GF||9MvVjctgbN2C82@SOvmt321jUAS;Gy z&E&5}_*Q>B^S`yJpa^$qTq-zD6neJEWK7L7J@aHP57!Ex^pdrQ{6Nr7s)ttf#%Rhf z5aB@RB(|P;h&yS5>$m4;R{iPSkkpLf@$M=tSIAG%l~8&}-^!XX%af%~b5^OOGX6eTdQ-EC*pb`*Xi}^3ef&c)kC05&Qm+P6^#5q^CHp;Ng=l zfvf#m;JMC@2)kd^1G5)oNqL+P58&@31tR`$=~z96L8^{dUrlnTsENy4Op**)yFxnd zwERsb|D@#h{MhzF-}dj-qZ_675=>m(r){2qord}e{2OZ|VPT``$?q<1d2;L*tMS1| zW~Qh?jD@Y#Ro;S`yqG6ey|OZ>iJTlP??&F`F|A^2&m&C6uv7J*c0UZntM*Zq^T@Nq zW!m1xNAkS?RiI4^V-eedd;Nwt9X2%XXW9r}V{JYA5FC_U|HiYDSlTnC_6mBTbDb)jXK7h^Xy9hH_+VKXEY z({==Sc}pt5!~i^P+D33Hnt#Uh6H|t0TkR@TL#`BviGq-B96kmM35_p+&B!;-myd}a zF+a_J>gvzY`$0pWd>Zvc#cO?-S`A;zXErY;+C3be6BbUhQQMmktC4s&Uj$;0La8wv z#a>w-7)WRLaboWgIK%4SGdMHT9@r0UcuJ&Pk8(z)!}>8!UaU+UzO9!RvQYq$AM$aT zoj^iBQoeXu`&pMtTXTUaH<<47UcEdt zqp#Ul{-(5AS>o!`t+{1po|o5ry*N?!PK-qUK!dk-xo4h2NtMKx_iDfLW1dHBYd-#V z>cVlMax>&lQvft|@T|qILzcVamYc_Xh68o?pN?>d<{kM{Tie%F-}J?Ke5#ZyXLdd$ zB%qk=$2b`8c~r4yUP9s7BVqMzmw5i z0l{?R3)4YI=&-}|`rT>N#B|}(3AVwFW0(?e01(*h*xu-gTw`tbMJ^kR?!wb-sw!)) zY96ro^g7M6I9j5LjI&B?j8E0v^D$-Gjn}Bkv0ZylVFq3BrWcXuVIh0xk-G))4u-_B z1LpIE1*Y-oxsV-b1On#wMe2_#&pFDq6^g zSgM*;pCTYW#c)H4>bwXPhwoiw)nv!g2BuC!qlJPu{pBiteJ128IzrhmR|IUob$qwqlA zvA;CAq&$=Qx&;gU)Z5v#(wyzts8dv?mDP!2&N0J5_?`BL5eN!f{H}UJP^IArcMCAc z4i08!grZn>gU-yJd#sL`@>Fh<`RDoB+Wk!RnXY)xDvhFGnqhZ)7OkYQDq1>jeGd<` zb}MpAl2khCeL84tzV>rZHDl#d@5k2(0;;!73DI#!NQRiiimDx>Y@v8l{p0CczdAgW zM9YXS(fO}mXp-c)r34VNJBn?8s10CyKX;~Jx!RBL4>YDsj~emHjz{R+ujdQ@{OaPP zpw?;R>N9M1(e%TDv3JETa`^2ZVn$9!rxyc=SSm;`GT)OsI!>X`?) zkFP{%vFM*qU=9q>YJ?Kls2sI}`*Hwe!-BZ1ic_5t`**|~>kqpy zJqjLwiKyLiFcdFH*(ZvnnUfkTL2lDMQ?7AEy57Xnfmr*nBVQ*blEarq4d*;V z^nTAU=RFp|{HaH*nX;?pmf+{_U5_g}^t6ilL=V6|-jCed9kQpk%el{8y*&KBldwIc zI-T!TjedH(s!3D2@VeISt2j;@qD|qw1rOOnV6U?CGVvkd%$jR)^3$< zn?=hCv6yZ_?t|F!btTym5XvS6X-F%)$Y5);l5gMy48YA0QR9r`5)bl)o59BXfKHN#Z`LPxCe z6Ryt3a25kU5*7-vRSjYaq+9dB#tUJWTbnogTh}{^fO2+qi`f^uQyL#`zf+L)4nd?! zW6YsqVEuSx|XjG>H zCIYI~@hTOP+EcpLE8LwUk~g3(kxYnIrA5#Etd<*8_u{_iwQ~L7J%F}jf&f&G^cj1d zU4rc(m)Azd^~r52&+zFif^2NkM(G&F3pMfifw)g70jFU3#(UGljWo@ZI^bm^^NpP+#3 zg^IzJx)%P(n>72!gNb9Cb=!$1PVwF~%!QeN$mW`e^Lue7gyK$exzZa^ye%SDd!eJ^d)sj|qS zhwJRyzg_TZ*72V;jtkdKmS>(Cco0MQ zW`3Xt0CzLlLLVShD8>k+LV~P>!6+K`SBC}CLki<-yd~?rpDh^5lLAa2!pUs~uWC15 zM6r|r>$#W38h0K%fd*>;i=77qJL)MPoY^{Q>LLfDD zY4_A>Irp|sWI(O!F!?}ow7v7|H?t}?N6snlrj{3vX&*|S@^dhYSf7_y?A7{y{s(Rh!c^CqRZ$#Kh8x(RYmFy7n^%~AA8oJte zLwV*`M_13x@F>^l3Ha%&M%tKo&*Wop301`A7Au^{l1xduequl-mZ75p^x4U{UaesF z7!;8srTQUP@A8^U(ukdq9t3)#;6uV+X^2o72GxYwV+HV0u^&pfc}RlzedN5C-mRaz zoEKaFwrsw9*f+L0{rgGXeGnPD-OW~1STuaKv7m4M>dStWo7M|@ft5FhTRwRDo(9JD zJleuJFL8;5!UT%M#DZyfU0pUFew6u!Jz554HXPCCta;S0L-MM|zH{oKgJcD%{+7iC z^98i+1~+s3ix|}}b~5dak+M$G#f$SQLz8E6-Z-77#dwNY zJ{3|jbpMKh1Qft?yxI$vKGf|OY{0!R;r&hJph$IC-{T7!V}m{%%FItI z&y$07e7$_(m+9a3vr~&@OcY<6P{3FSlSXq%L0Q`JMqaxJIUcG{BgVX{q1!Mt4)V5h zOr>G*T+Oixf1=0IZoK2Tl6HcL6|663gm{5($pz_511&DPV&@t^(B~9@)&_yp%n!Y5 zhrf2)k5YZ>Zb|Ub zLbR0PJ0|KLI-g{g`;MiXSzTNEdB=C7D^=2Z?bqyUsiBtW@y3qVEkA}N-$pD)+!jym zl58$&2~i}eYF_{NrqrDC$HL%{N8ZiK)r*G-qM5Wqu-Z*RmI!zI$SYxd)f;C|a;P2| zhVhQUKI6f-cJrVpvO(pi@WMOEInYxgE##~h3> zXjNDlSIcX2`d#&_IdU*)lykQsS|uir@C9pw-TO%9D%R3x5Zk&uxEO zbZ(U3oHY4czTLkV(#|g#X$1=JI$8@I@;#J{Z zMK#*K_RQeI4~#5U%5iaqI#U!ABbrNNX_H>mdXs-GEyVyO{aHCkzt}S`>v*1Y?COeN zem2~rrX$Au#g>|+lvGjthy!z%L==pjZ1Bxt?NUkUntUeIw9&Ku$62m>bhEa@qbBEg z>pX{*byA}sNAs~x^m}vPO{DV?Xs7VVEsgTmxF>f_25Mx-Pj()hE`4M}Y|jFMoC>T8 zRBTItT?EnzFHhkpQ0`lEQBFQIx6nnRx8LpHQ?lyCv8L3fvm4c)=MQUr1kPLU;3u*2 zzy0&Qh}8@s>zKqe+~i*9JVHuR33VgW4*~)9B7&izxuC3YDk}4w_d>9<;_M!Bx0A5n zfQDWkHgPA*-oP5Wktszpq8;3-123+NsNGD@70m#m?%dCSOf8uAe9@FVd>`C z=7-+CVSCQKdK6MP6m$c%NkpHA708oES9Rc+q(&}%cFzMcu>`t#R+k8#4Y-IX=FJqA z-ABFW)y+5bzD-uf1xZC{zfIUUQ@(r{S1+-A^|WuVgyg7g)M)MZ>cKbh*Q?^ZDF)yC z!p~2OL|j%+Oit^XBpNrKRgwceJ1GY4%3kKd6A?e`k0aqB5j1{zB)il6dG*G`>V5T<2%C=T_C6 zNIivZUn%cnaJ5JomQ7q-FiAAbt;r5ac5&lK#dKiWF<6#G44l|T6FtUF5#pxcsVKuW zPtN^~`GZekMRPC=y>r?>zze0DBBm8pH44y=hyYU5`L40>)t=V1>qpm_+pN@wk=wtZ zSjEIN6eACgGrWr&tc8ryEC(_r=wj)h^9O66>iynTX&&8k25Tbl&8vFj>UbcK{T6{>csOtwBbrx_C8BU?;?+9V)XJvs!?De!Kc`xQ*VlW^N-=6O5v*Q`>OHkZ{gN=ar@hC3^|nFu9B;(*{6^ta@dip6n9zCTNku)_!M3FS{m>nrM;RfvI>PUc$N0`xCjWI&M6q!52P zSRW%3Wt>c%@Mez2?Hzr4EB?$|rq6S4-A9GK)%7=I&YRs<`v~Wcv%ltz73l{H0{7U@ zv-(CqbROh$fzmT?)h00rAZ^M}5QFaFnD&A$0!*kh6ppXb=Ob|hl8p%DlQs~R7yfUs z+>>1$jt+!y?4Z)JkG%^V9H_}Ez#>T%Kf~j5(`YuDBvTBY!+*F|b>u*Yj#!!Je{R@x zG_4!lZM6CuFX-(a$u6SZGEVjTWSh^{II#Uom3Jz+k*|!t9%VnQ{P0pXu)B&cio#13o&OSp126{-+A-N4HiO0MpMS{Qc&;#+yop4!;96F!FfK z*lx}hAXU1W>s(`*@NXH3e+G&W{YkW_!)FfAs3}Q9#>7Y|sWrl3D zB>mfQE)vK$>Q%08dJMZu+N6r5p$IhH@nmtMD`&|3c5#}1JMy(8{s`5Dz>_-R^yXKv z7-U;u|4}i!64!Qc=S*;TzU9+=iLwrxvOFi0pvq;V!X{mLl60i?+^`5s`$V}T#-ZLK zaeJ%vud?>+lP+e^nUxVPM1aQ)xqBiDEN6(sf>3imPV;Z7Mi^{H*ZJLCqa2Lz;8Yt z+ML)u@oZ^S)iXItuT5L{`(VC-Z1C$-x50Kv6}K_P-WO zi4nlR>87u9k`~gq@uuLr{O1$U*Gg_a1_2!gcI>0p{DwpV^iBsDFfT2NXCUr%_DH4* z3=AFi7|4ei+UU*trRP9>7bpMgY}o}eamp{hc2gFXC~A?)&{ZOj9zL_c=YP77+#;6h zbm+ZCiTX`XT3IPW>97;>!PAsj_1{$kpr>%k_B{M_J`{Az)0J|1)_X!6vj=7mzS5i6 zti3crNww_Q1w9x76sskTzVhpKpM9AP=Z+K0`TO*86(ff4bbVU!6Y1^Cx#~H&1T-0I zsVxzI`=1J(oc2&CSv2{ENf*toRbNpo`VFP;?~bT zkhJp4c_ZiakHJ^xJC|-_3h$yBh7EnJ7!>E|6|_qK5dpPnTP8c+6OJx7sElSIsLFxe zxef_5>ohbC+)kPcU|O8;br<^`e@^z}WFtLtwDH(2noCAai?@!R&(P-7^3~>L%bAtg zk)uA79>)!)ZT8Q&WRzc*sFIKiy{4f{55Mn!A1&$KDLJjFflpVsK(77|pP5E8e;2j~ z0KUsmlf7VC3amE&To-(bCueQ)H$AB5IWjCu7aB=ma>@00DRSI{(TbTbsa-w}SX6M3i5U z6b)V`+>J9`Im)7$`sQZE7&P}g=mj36h1S{JGhv%54suLcNbyYmxfJfeBfI6j670T%K`t#^W-mUvl5j1bW}aa?li-q`B5pFE@%cHW` z836QHoxmmwcxLL^IC+t+4bufWa!Q5Y+ORrUZk1N@rU{-lt^`>DK7Cwvaa$%0lx2K0 zZc{WD5lJuEDGl9Fb;ZcVgzzy>8*~kOClwBT3}p?m=3WdLF&>AET%D}s4N$W2iv6x& zmt;YwW={hUSYN7T-LQAE1iof4V$8lGlO198#7fBWlzTF5h&9JYR*&x z=`MF>*yvKblH?!#G7zM~pO2A$>@D14F)Z+$mU)a|!;Mz|sL&RZqm$%v1- zu^LI>m&Q@v_TesKYWZBVDdNRZc6|^Q8~#2t zJ>{2M=2mSn#$6B4UrX636q9~WAhKWXTd>Zd+3^6C1PB7bmj#{e)dEU%B-ark$d<&E{A+Rw;q9{kETZpI zZM7H=^kMSZVq>MX1@lb)Wev=6ej#;Q>|0FH4x;@h7m z^E0>3emP$NImT)fxIV&>!a7Yb0*GST>|t%RVi#AcR^{r|{29mDC5LQBNekH*fpGQL zT$`jpedGlI-mtf?mu+0as!I&=AO-_zw6bYOb3`M{92Ma29o zZ6%M$Au&}CHb7EG6r5{%UfOkxf;VM(IDV-ea;k1b zHFlY}k|~)sp^`G4bc>T-%haKv<8aoKWbmeLs;d>xo(j|gt3QKgp9G>)fXNZaB+h^X zzmp&HnmZ;iVsp_mXq+%YNSojj3_6$FOkD=pq(k=u<&e~An; z`8`L|B*t`hFV)WXPE7o&98T%J?BD!AZF2V4-8aMDGYB%i124r(NQ+7O@VIVxP+gMy z;8O*g>QLPxuH^Xmc-lvXvUlWmZ`gTk_wa(<^aS97TlST%J4sj+MXtSXyH9q zXz8{djp&Z|En1r(5_aC#pG`BpB%`X|&hwZ}{|Q@sYGTRB8wzgEeRn|@1^VlKam}Tz z**2W?;nAJ(s8xu0=JKDaSbkPueGA>T(!S#=gQR$u zIxh}*o|4Fa@MxH7@v)evlhpDN(qbULwhQQa9G<-S)Bu`VUbEDfdgvt~lBiq#ArzKUSFm(YX1lOIxJbS5ONM%gBA+3-jMty1);d-0|D z2t$U$xP_lUp(^fO-Qdx4wa(#@n@Nkhn|~%h<*%mHMUVpYfVd$G2u#6nk70kYa7IKN zuk#~VyCF4#F=Crez{3aLGs4`fYYfL~cZp_uSPRoN00$X%uB%;{rg9(bBe_;x8h!}1 zp%9@|MxRE`c;9&J>$Tz3E@@lc)-Yslv51d9b-?DL=5IBx-BKy$d+c{v-hKPQ=&9}z1YSMA-= zaj#oO<3O_avvVp!5bo?^*Q zmT-eykVhy8xxnGy1M#aFkqkU$6{`*y|#%#C9nN18R~N`lRNAnebLSn7WEqy+*SWUg zN!s7#tE;`WFu+PJGS;xkFMRfv(D&wQ!6u@i$ z^6s-okELB<$kyd^Tl$H%^8Vf>mLX_AjS!6Y*c$beg2*{E$0 z1gH~8w>je8#*U!`$R3a`sSA#9bdFs6y-7Lh9J#r^JkuJ!`EvFB=?BszdzoSm^6rz_ zPWz=@Ga9AI&9-(E9@7RNE7N$M2<*(jlB79EZg(27#+`Hw#IE1D^w8wZziiH$#?lZn zk3jBqgW*u7ZgL-U>sL>Yhxo%&+aGLHE|H0ACCq`DPI(& z362;&5(cBKDwq>ilS^G_yHda6yfyvz)L6tHR_o@{dYn}bUL({P5q?c~Icv{WoGIYa zHQ8^pzf>=n{5I%PwIi=&T=yIO1kf7o5-~MLq+f~S6@&7oog4>@F%F%&;W8Mjy#=%! z4}odb!ydxvbW8?X(ZiM=wn%?p_gG904GwiEES#B%pIW&mY$RMi&Ft z*20tW9&!h|V`<#P*Hzt^+L0T*B9l{7pwX!aD=RD0)_Pfu ztf|R^2ZqsgKPSEqtJRC*rfY$`l*hV*5y!)WLxo~1?Zzg0ax8lq*8{cAwA}af9ORh1 zH5T$^wWalNUHIQ=Dw-I0cH`1n53LVG@s)mY|H>WO3X`?oeiaE;rb*v(dh8ME?+N3@ z$po?ctv=6(pVtia4X>D4Te6OB$}eue-dMjJ|U|L%bh2)+g{tf#yP4YMeY;MTzj~6I zD>+j>fPb|x%(L-lOCvH^J3KhIYRzSO(z6i61<*8i7Xqb;3h5#Y>y;Tt2k4C&Vgz7z zJw)&8SveyN=>m8dgA60ES*gV6Ci-hM^rSz-aGyxot#W+K+6J?qp7l}>8Cc*1K ze8{<>L+Jt&PgV|xLQ+6xjEzD)4G`*UI)ufGr=cp32mhOn?|_B+`nt*~@iSea+8-UQ zg<=x=6R236Dh{epKRvWtHrqof0KWro^akPrU3%#6;`Yt4R4Q{6dnnfK(%N~wG7SHO z`!w>Lg^&aRzqwB3DV3LHt{f9z`Zo4i+ZCeW9SeFQt5|LKT?F*xqM8Ia9+_<>3VGQk~6XWbX1OppkCblgR zbYC2vOIk{wC<{a9gN+e6e~P`hn_`e>_3nPu3JlI6{OllVveR`=ra(rGs$ zp00BOF(bP1f_oT4ZM+}{n^{8y0f0!c4~jwCy8jXD4LkWITbj=(0d4M}Q7}QuTdE7!)7buadn@2YYT_b<(Mfl=vSDtwjNJq0}Ql#{?YInDJ96PXc z3S;`ZZs`-npyQyWer`-n+OegpE#V5K<~>|eUwm7oiv2?`GoA%oA9^~c*$&eCcef!BC+;T_lMp3pqiMYhd$VkLL}@H? z{b}TCSLC|y_U4~keT(Ij&%G(JBCjn61Uz?Ww#U}1oCv_{mz_~4oOa5jJyzTu3rg0E zDD*i*N>v4QP6y+jFodPx6I>E7F*+)EhYMyGPjjT!`gnQ*yu@%zYENxz)8&nE=kV=N zaX;~qbS_n^qdO5q^qT#3}*;L=nkDXdRk(N(fNzzf;laZ18ICRoOx(CmU}T)K0J z3_Hn~O8W=7$!H`I!RE)yz7hxG_I>4Cr2PVScMIz^dQUE2KDzNTr*-}1)Z#UV*2|YY z{>u&XjeX%H50<(wh&(a-N!NaBI|}r#AKyC(f>>Pi+bwhni!4n1i-D&rDjL?{6s!(} z0`8^c#&>UxgbXw`3FUcFuou=E|DIw#aFByKf*)J|R9;CZZLbYe;!AXO=;iTh`yWHc z5D|RB*R!0@!$^{DR;80y`V|!QFw}HkXzVX#`&Vl>eNYRa2C8?D#D2bCu6ak$8YUFW z=Y75_NTSrKa;mZEH4sY##N-_LoOb727kJrZJ|J_uEgDSXWZXtofrW5X)+#&;qMAZh zS#;j?c5PCB$=FGi)d^jg>p@+=5%{b4c1V}{Hm7zun~h+GQwG+Bs z#rU{@!i5Qh$kBeQstqXgZlk1~ z0Js$JFWx$r4dUqK9kV;I(L$2P%Z88v%Os>w`ZudQhqmViMRIJ6#!vf4op4I=M+)4^P1n5wDeo?`dll?xjvMldaLHD z?aYVzp~J5@e_$u5?nJd{5t%~5W4$*=xt_$_|Jl7WQLpJ+?5hOac;}S>Z|AX=qpNlW zRbRp$9-0wz`yB%x=@fAi-v^_+n{&Li8?5APQp7~qC}KjD%VzAUqhmfkwC zthM`?RziM?M78tI7CI|AP%^=2uihF#g>E4oms6(hSKQrSB;KS%IbNVFse5_2tb7NS zTU?u+JIbrNncp=w6@+l=*++FSNChS%;!*5u{~EXgQt-GSd(&%6pg0z1r-_LAQblbw z64Rzk&UdWKk8S^cxV?B31Y1cmoF6Pk{!Vhfw(RnNL^|N4^Tkf#mmcuV zFI!(xqhODoP^k?Mb`+8Y^!TcVTaA1A=?6(J5^e-5+FXreK{-r!C))?|T!`SZG!)GUFuwwy{)EK1Ab0`I}Xyjr_^! zDn*{E_6(a?ES2EfzHg$Wd%_5zd6IMQRw4N`aIu1lj6016E=3Cqz*7>GZp1B(9@VZ< zx14;qz0ym$w(|Gm&BfbxGjApE><3d$R%P3y%vV(+-4(D;fb4`S=SH$qYf?FHH$O5JU4IXN}EHUW> z6OmYzfc(MP@C@JZUw)LMMSBX!LPkcdQ6{j0J=(2(+DnIvl;b#4Ukt%GF6f(lT8w(+ zTG5s|a?q&*L(PAtr$rL%+!dZd18VQX#e;bX!lOJT)KkdosuXvHb z$Vg?ImC@0N%|J1o>pr;847@csc4BwR9|cf;T5 ztiIzHkNu_Z>}>a8LnVWprac>4nreDi_^%ym+2+3%_UG&0xxG%8ig^q)4`Z%B;(r}I zV+{SL+?r3DW}TIxnQsJL7_Q>s-b*0JKPD_syI^D94$&3EodQ#yZTg!g49qS(|H7Iw zl()8re){YSf2w#K0k3))pqP@omig0~pqB1Vl^r3#z?5|@8U@D~2ph#D)7cOq*JI!5 zv~H{|es%vv&odiQ%Feo*YuoFC%gS4Gkiw=+8?3z_i3?7TqYPTBL7{!I6uAGx}hSs8TeEpz#aWlgALm&e{PF6#I7 zK%H8*AM?Sh&zzOErafE5ay=pz$lXI%rJ}!A!`$B(2kq%0#jN^kzvdo%eSSK}BO{1r zX3Z{W_;tLUjwVA?(7Z~ARNC>X2N>dlNK85#)rzi2SU2LvbFkrc3ZNvchdUqq2rl9fnr^#Y8Lx>cB{;s_Mqa*-ysENvITZK zMN^#qHD_PTz{D6-haE2kC^lI%5`LuJ*nx|>)Xi)0-Pj=Y+4nAu2PJjM-{!S=zpdAw zRa|v@HqlJkc}mzdcR4QNS>;7Ch`?FsD6_G9-K%Dcs0AeBfhOlwMGe4Wrl`{I4<@SP zz{8e4^2Guv{J>2&e0>*kQR?p zkDb}))x(NKx@V-*OxgC4Zr#A%N<=j9Lrxq7Sla*@5d@$qI*^_lEh0cp!J3LTciMUB zHC^RlF&Z2a;y8U1#gsHU4_|3%HJC76yOLebf9j|XEU;Y8jGyU*d!l#lmp<&)mK)@h z9r*D6;#iYr%H-Da4DA2ovcc7q`|sB@ z-)~UtxjOG6HwRe<{hmmRWxK(Lu-{M1$8;P3APKw_yX?IW<|9SQ9bXzA82LZ5-g&Qz zusV9PjZq_vmpTS)$ZH^O7Mhjjqyp&xl#J3lEyp=oaZ)r?OEqYX6p9Rg|T(oe^PQD*$QSA`jAzku$29|#QVn`6)Z~? zZ{B8iHJh;E;hEfZ9S#0(1Q`V?#TXA@>uvJBWpM}&tlF3|Lq?7Ho|0$ZH8?-G>OC3y zEXQl?zosy)mTPl;y+?>A1_oVAmV9iknJs?j;+#z;?3Y=&{28*B^2e->B#zF}k?W7cwYpWk3S3EWbTK}$F zwqDxhmwHhpts+FIlKy_R#O`IxvYnfVi@TiQeP+XABkEq(CSm@p5@Ct&sDo`VN&g8Z?i zm-iY&N(U5~j#x2&cP$PM94uU*ZA89Gg3@SAI|oxC2hDau0jfd-Rzks_t(zO$LHL?0 zY4TAASiBO@c{WKryB^3Z7koY?BEdGGovNBwQ-2bgq&vBvgjWWfm@?%o1XHSp%~p`% zRd09Ni3{O0O~5802uMb!)b(cTC$4H? z2q0K8Uy0aSqXaWK0U5Gfx2kq*%<<@Zb3ehTI1a7i*={wV^dXp6=41>112Nwp>V|#q z7;A~x^EM}kqrPI$BpDsdJ6$wmhSPhdm=r5$?IjH(yCD_N3 z&s-N`)4jjgb+5}<-?Mrb*1EcfS0wp`jBJ8AHmK}_ssOsYK%$M+>%{7j@TFz5tZqy17*o$HCBFLbi+7ypkouZ15_2M&} z@55Yb^8V^)_XBfy1oY1slI#+}%_>DMjJmGYTr~Ds9xy=O*-nNxaDrZMZL(Zp-vF-w zl=@EKRSJYYCx!VhAsl2SKmbs~Q8fV$^PK&6xo?ChYBS{LgGRf5*GCpoNY>O9AF zbe_V<`yi%}S<7T^c|*zm&?~<(98J0Xgi@1n2!2*K!L*&3gVrM^)VSMntAanFRv-r_ zJJ&`PHGnblAHNsC0jJsc|f!*VEGR^xvoHCt)GWFVsP!H?(I7QX`t2h%G(&jZz z7n3SUm!KVIc%sW-#pXOXYm(?0X(VVs)3AsO^US^140f5REaEc$TIc!fGX~-zB+lrLYC9z2U)?>dXAkVMxgjPkv4{P^p z_WDr2l;+-Gu#0)iou}*ln;|L}%3_Sk&K%FZv*5SLWCX$`a_iJU-dp#}9ryk5V-I}5;<4#d9B#}V9BH`up5LlZ zKJ@t7n^Qky&iK?{7^g`a^f=Y~B)8abcFE5!`AR=RtZe1$xn(a1=v`%*wla5rS$BI%)ZO=~z7ySrL0^}cVa%jB!E#VJ19$R6Y2g0jmJnBHuB-dJ+~ z9QiJH??S*=8E<64w5GdtGFiCA=Y?ax;<3x8IWqWfy>{QA`QCb7)WS+!3wEZDG4|OG z=-Jq;aZ!ES)QlH#hDzOqjwzIXR?I>sR=u*1-mP!n$vRqZ-zh&@9}1!9OwpT_SkBs| z5B6C}J{o$WF>Rt?^zu!u4vq7CXA##~Ah%Ayf!gFMBi(y(_-E$wVBlhFHMia)+*={` zgnOQvHrxGUGWzX;O9$&fEWEkCx-}uAuIkIcIw?LpZ5vi4b^5Aakfux%GIDOU#WW(i zq6DweoKF*W;@T+9TWN9NehD~Z$o{d=R;gNxcfR$@qDV=$Xpz3NaJlen{fM0rP_auU!t>1* zD4S9iK>O-)#q=TIZ39i&sadxf+p{BYy+^LHH%V>3eQvfbW0qwx-`}tOMqq?A7WfNM z-DV8@rSb8{N~Y?Ga`hfEuBW9_xt7=A1)wh9CC?pfe53rD^%_fmBE>o7_yVt@jL{1^ zm6jP`*?9HyWkZy73=ZRrjmyO&86k}%+zMMX6{y5ccoN=zS2P)mFT<)rsV|y|;+MaM zS6a|GhHzY=ek8#)#`MGG-`$=zerl?EVfN%5+RyI_UJ6BDU^=Rhs)9OvjB^x)Ix@5wKj-aj)SL)gAJTsZci(!phOUe*3O@tN=8)V?jRg5I@$v8f3 zm<0&|!vc7qO0huT5p+v&$0g|=j`Ym9l}#-D)WDtVY}TK6Cp|#tZNQGAm49-N#<@`LR;j{&-Aq<9tj69T$?}A=ETsr1LG?}% zI&23!W%OG6!HUD-ughn~b>L3r>Zu`J`ZUinzxqWR7YlqAQ?97pqt{YQ94HGk1}*RZ zQ6Gr_?g^Ne7lnR?&ohSsdYZJ?(GgYS)Tq>6_jY^rFjVto^ycB-{O3HjNsO@MXC~&@ z+d3Ye6^WY%SId7Y4>VnEwC%gSu&XvgY|LePTBh^5AMO;n#6)+Wd-31}0z>29m&Y#g z&ikB$kwi-2w}fM^Rw^-d1a*hW$@+bVXll84G<6`^qkBR{pVi=cX6$(k zr~k9!r9DL>VN;=?o4137(-V zDm=ZVWFav9->3`fJoUjZ*YMI>jkZHjZD)%QoK3Lqlq6-joqLwT*00na0-8I5H%He? z%d({uEU4sXG+G?AG5bEj*b~Bpf(H#~W(#dh9~>-RK#DVfkV0@=^iKGZEGEto7;ivv zF;w!y8&IBw2N+q=Lsy(1VM1(NOIzN&;VqI!nY&t)uDf7g7nEs- z%<(CK$Y-|UV_c{6^ZEqGhE{XrdV-~ScNlE0Kovp_iwSppbO+4MqEvv!&SLjXFDd*c-3B8 zuP6f(WC=YaI0Ns0mT(^S+b*MrvJ>#ad%PmDhIsb3!Tv_Bg0IpZX&ATfhqpIJ_-@}m z`kU`b&99^;&k@b-(%CP+b3x;qQzV{t&^^!<9YFyEWDgPk+2B>i6tkQsSd`H}JRizx zpg{N2#sjcn#7F=`~}w?6!uRkAL=yer8p; zBqLX)^k(D_Z?{Hj|N|qz7M9!Z<4}lWFK(fFnc!~Xy!KrJo ztw-*6;^%ilo;L*5D?INK+M}oIMcV)E{W?Hp%}@5gSYud}6vV=@+?`$gV@cVH8yCO7 zPHfbW-c*~FUj0UXcP%#Q-8IQYq`Zftx%Q>}8@|edbY!$Zob4%CzE=@VJ0)4h6ehu= zrI(p@?JD_T;JQ%W&9!jNy}e1zsJ%Zq9;P1dn+WFH|0J9st*aT*!PSz>7dF0_!CHhbo2IPmH?E4JO-7&5WPDGaK@IkVpOkPmc zHg~G44QWIH_k~p4R7g~N>e@M83DkbqH8h{^1Y|Z_r5Qiycvb65clqAWWuS+3{b2!% zpU`#GM{tM&T^JJ6YmC$f3l;NUBU7`-ST7(LAo6y8gVQVyIb=RM&1OYk-1#}YTQpdl zW1R~Xtl#H4hH1zPu&}#!_Ag-jeYY_Pv6&w`Y1KwE`|k+toLzp+kbNHM@d`zg=7P<> zg$pO(AyQc9N)pmlu^RITm**>j!C#I8 zSIb9bdI-$JaF`v1X<~yq;{NNZN$cQ{?7IoLD$w`_F+5A`xj@zASMKMl0VNY|z_CmM zPk_~ln2#$sG?!?1-#P`v3-;TUw)(f*Hu!Hv1fypSf*RR zn+1B5U8FHv$)fFwGWHl8^uk785AnA(0a!ZIQofh9aIL(C@V&uL9{6{Rdr#+4h5FetSR^T*MU*^^7eL0IRBLzU&$28TqR*N8XIJ@O#>%RV9z*Tm;!1L5xQzCn%mi)KDNKM}S2Gg5DQ^F2;;vZBg)5fK3 z(c8|i2G;E|@m8slC~ZLK9@Xr#V4?w+b(-(Pw%LRawWb25Jr79r{mm~fR2m(7L{rT! z#_x0)CzaGgO8*K!TR>u?Rr{jGK1x5pnhV}u^o+8Wq2!IXKLy|Cnxd%xg zGzcf*00ricBetWHGyt3>-n7|*azDO4Q{h)X2hOx}3655nF}wvrDF>O6081Xq^)T6;IJJ~iQZE%+FY#6@o<;PmPghPJ1w19A3E z+=U6Xq3krgx^n>aPp73;vQzfT&dv?E{PO|%-v`P%PbGDMZ{ft4H9HWK)ZO-j8)oI2 zY;k^svcx+tE#k!JXwe+^H8_HA&x61=MxGk7uHSxi4ANpmy(ht3^KixygO21c?2r9`!kX$Rn~Qm!55PebD+NPE1~;w?E1Z#r z%nf~mQOQUExI5lbin-jAu2F4Z} zaRtsa9Pca6wB7glXlDfQ@nRUnjStVXuY0tw2OkZB@lBWL@PpN0por#?5izeaE`UDq z6^Alpf}6u03L{?)t|S&+WWu)FGtha`j{_`S(IBO?AInxXji@xv+S}6N zQsx=fd-dQxZsIB7%0j6O&E;d#xqBQ{idD5mhuh!vW z4YO7cv2f(k;~%`kDUr>;L&~`|g<)wz*_0+PaE4N^4yNmzW=UHp-H5cEKY3v+P(W_#v%(fO&;~HXQTICf?G{wY znz=VMr`4yjy20bRyhtkJ(MX(J7#8?9?TAh@lX9vGL;lZ;bxv4o#o=fqAoP2MCpQL? zu0Sz-_-`$@Pah6bkLY0Ib8!%XaHmp&a=z)bLx_=&n*`Yy#>$w(axdVX|)2 z2H#j)T=ps$#k6ii@dababIN^JMGCkRaK`$uYhg+%&K(S9`1l52JFgG}!`0_7q+9@d z(T3QO0~KJ?q#k6ohezF*j-rH9qju&?W)7N`R^&h0Wz}XF|7DjSK&|}yXXjx*e!g=` zJp$Jw|I~9*-al_aR=-Q`VcBUN>Vd4o_|r5DEwaQ-r;8L38g}@1b3sHzf#wBOUshvU z6Tot2FUrXeFrO`oRe8dDQjX-mNt`}hyX4roYPeQ%5|))A+t@~NWBTa@XKn-S3q5_w##N)}gKHwO=R>K!NVpPge3iu9)&n66j1gBKHq8Sxyoeu#pcNkI5wn=^6(?-Q|e}emOd&4E0>5a(U>N4+94A zQ_E0NjI2+o&IICYn$_XyXSf)41rcD=G(azP&&KdxhIRQ+15F^`g{t*Zl28RlHP^gX zRElEw4ar};`V1#Kc=4iHS%eHs(#Tc%e~I1^hu%kvJSmkM%WC9&@yx52oq4;L^AOp+ zgzdTCmomP_QBSuhcdT(z(1zYmmZ&h@!lq`~>^f$I4p`UY zc(O~zK1v0sk_DilNkNp=38>h@Q?Z3nydbo++qY0(QVgx@0)8$2Tev1Uznk~o3u%(t zmI$UOJu$*>N@=o#%@QnPHJp6E~;1hhNCclu&#hTp+JC`$wC8>Babf14UV2VDiGt`O$5;VNtep{6ed;rRHt+iGL33!Hb@9QS*=q|r z9@;;D)03=QxN0+^?vAs0C&XpN7ZylEFVBelKo0&VTIdU)yR+VXX2#R zTG>cYQr&iFsikD5kbPzN6>e?FAkH`r46pYp-k#q;JHi{r*tWV$aH<%fgLtQYXHZv2 zyoO=YgI17MwFWy9hLRvoTxJ;8tsM88QaK&HZFV?+?C9T6)E1BC&dRUxg+BqRGS5O~ zYPicd$z(Iifhd>dS*vUyUY36?WuS*Za{$RoF^=QEffL$ouN$Hm4v6OS$q;y*ci2|p z)l$_0!8;!04>wn~hn9#M=LYIYEJ^%*KbA?KNxFSOjboT$8`Q%9JqfF^vdY0tuOIR2 z0eP^|;5y8tXi{9*&$u8z@lMJiwf1={Gb^(j)$Q9LUj151t0`qu@%G;DW}NK&J82LxFW^N1ng<5_BS4+j z9eX*1NW$}>iZXl^;W#t)KMW|ZY$WQz6TDnRQkoO)27;!8_P5?oITU0%-$DiA=r$%rR#_{s~C`ne_Edu(9>H zPDp+a;}st1&rX~@Ka@a7LZj0x<@SZ7#Ou#k6>e-ej3B6*_1JBvPwhYx>HjUfof#Bj_P9Xg4cU-9NjVjN=dml~_j znW{<-&fy;wG!{sqGU3#P&@r1C=5W*Ah3eF44V9lGZmpX7ec67~w><_@QW72xa>~h2 z65H-_<&LB-tcBdX*?u!FzU;!%w*JUL^=&0;Yxw@)rs9V`vkzp9ricD*Fb<5Yg-C}^ z32aU0*0l*z-B|RjsqUskL-9U0{)+lKJpftd*f%d-n%evJLnC%l)wjpCUL@sL_Ciq| zt*vl^eDLFI*RP+5O&3!TE&w|56F%+jKhnd4A9VP{9Zx&X=IPPYI&F4qddWRlX}C`8 zPOfzVNsgWajT~!ws6A+HtKUVab>bbX0OuleOs6yyLzAXsdwyKh8rG4(sDY@5!SZyt zP`8{|P%!GslH1P-8~@MyhwrbqZ;j6U6Q4PpP%4@`l)b%v={+&~;pGbN;sYnGmCo!D zdQOzMfh5iS<|m&Xw7-{z*yo|H^Z^|?+-uu2lG~p2{^sk;o)m1$-MxmI+KJUE({eDa zMJ(<%IpcVp>S-crO=ZVP=Fp2+SMnG0BqT@(Kcd)|OUZuFz%(77UM)6fs8BNtFO3o^oI+gwC{GeOJer7yJ;KkOR!#nD9YPpo)_Tas7 z5ZQU(4zH2{8*p3I60H8lF2K zlG8wgkxwjZI)w_AxcP5l=${ovs;Fjcsx{-Yv5cYQvZ;5&MaaBEx1K$1#EtKvwnu%1 zBy-bK>2)uI0%A66vwV^TLlE`SWeg%gnNggnQ|mT%1&ygjV*uF|EnkgpaO6-BfmVLo z^1N7|!`~k)A$V7LwR%k-rzTS-{?u#=I%3Ipdx}_o)EZ)F(0;TXE9K?ogg_r=AK{ie z1b~lcge(kAE<)%ZYD3uByG7LtoA=B=Pb(w%+en> zET3&H$Fw$GVjfh<;(%*{7Sj83mH)`t6lMSD-QA*#WoT!`2Xbe3e%>1>cq?}GZ#?5x@Y=7J?`LLPq+1Isd8=`f32&hXVqyU!ulx#Q|afNg9Lu z+6*>dUW8mpy0-F^g1Ghf4m>l zR<444hQ;R&o~~h=S3Xzq=v^~%A`V^L1S69<^ zHDq@$V!P_O&Fl3@LhBRM4JE9EvEX9a;zv?O^?47YQ~p=foi@mAe=VB&e*Jkesn-xq z`7>MX9X&6uvHdeX@64&p?BI*ibRt}Kmjjzae5c#?_f|)5g@(N-4-KJQ_B!9T`Fzth zTKkRyT8^4$18sXO{CGeh#m!1<{_u46vnx1n-aIa1!%z z6eAt#+aMt1JM(dfBv}eRq6W>)%KkKVb>v3Va1wxa(D=*`>HU|kyawFXNuLaDG-wWyvbt*3emCn}u=@{^Ts z;6(e$Dlo|1mZi3exOqJGTRd*?(B}M!;#0d*S(@AT^Y?3Jq9Y=XHn$IV?(ehJvuTDP z_$_-&T?+2hx+{$gCiposiFesOD$4_}_a)PW#_e-my6I(JfhW?6!<6|uHbbTxjGK3> z&aAAgnA6dO24|a`a(?ilM}an;l5NB*p)J4yu?oA?jE23trZ3d(dmBDq^AW3ldV(5U zb@pwO*98ar@xp=QY7CN~c~!cyTl1W%Wc-Wt+@It6n`xnmx<7_-mVA{9x4ktZ7mnp+ z*^k98G_iytpbV~So{?Xd=6SnogRTpRa$cr}*$vJq%RiAFH}UWe&3q}!IGs!xv%T)$ zp>=xpU|9-G9y|en)dqke1~F{d)R?9Mw4tZQ3 z)0^PuBzn1U!h~hishTZ{n$y1NaP%=za}Qw38TqOEn zN9V=1=`4vw3yP6OImH+wpwXsO_cve-C6d_E(48?{!@X z(w+bN`o-Bdrx39r_cy2ZOSirDbLf!0YC21MKSuYSr~H1!BNTaXpn0&hJrfIG#i-B_0i$w>?icSO*Vy4IEB8p}9P%65erQ>{6Dx>9q>=Lk0;OSoptZ&3I1 zJ87?vV6x3(qV6%yoiL(^@+SmNP(O(XH`>&Xk)`=nMvJjp5q2#?bgLIJTSZ#zT*5#Se&9{vRCurg_D7;9pj1kVahKLIIKdh6Uu+mZ$ka53 z3m!uWV}peZFLzDW6&y>KbJPkBc63RXihSrG)KhWpc8AZ%OmtVVw_!|97cNObpUe903jKc&S$-Ms$Pyw=>ewy0B@l z&GpeT^`b&72`E{Y7iERE2&IHk(wd!xljH9|v16$B2_jmyGND^&{nWIHcLlVVl2DtP zk@d_JjR3mq*zB*>-+H62!I5CFE(!fnv13e#YF5$kM%{&Xo+ebdvuL;JwUxWJ zMi#M}xE@?#q~~&$n3`utz)G@7WG5D`9EOZfa9?kJ`Illp;(99RS`d-D$Og%g3i)R5 zg^60d4QTtsndLjBN!QuYJ(*KdcY=jhLm0I(c$q8bl49UT$IsFnvW?oKR{N^+8AUN% z=e?s$#j+IR{DYMenH6s7fy@EqGXkD=W|H^^wuKq&-`;=+T}cN2%WXj| z6&^>X<37x_i;r-*BM+L(-RF%#ehqqv#$=4p`+jM=!a#j?GWT#}`AqxP_TjGg0Z`*A zC#^gEEVnoPFt4+o?Y%W3`%_qa0r;0YNS%&YM?)Aw@N_}n4KBmBfx)JH31uhgamcV6 z)pNwsjJ1^z)}E5u$fLP&qxFdqvhTr;uex_#=!PkS_B~g2%E@U9L)Kv#+RQOwBZQu% zlk}~@`)WrUPlh566&!ZXX!@`J_4)cMRO!3Kz%wmT7JDK0@uFMfEXQeKR-)oi-0WC< zMr;*+v3qjYV`l&U%x>$LG&!cc1L*#fiOvf#YbsE(>4;=9OI_k+2L93S8X(^QbugkE{txtCU}0mL7-20%_)ob0_agH1#FMmS)slPKNO}TD&D@0rZv=t zXMwl3hYZ#nuLLIB+G_TQ2PCCQNk;=&hpH&XbbMnoE-sXCe&H^bh5-Vj2@y1Ru)OT| zP&9ytQA~5;r{ZH3p06gZ&*D^ywME~uB>4Y;h2hwHyu5H13^|%4NTiC;rjl9YKGC5> zbh6(^*qeSj9zM_5$>p{Lz=o4?%4;Q7KB|Vn<1e9qUBW zBG|YQ5I0Rs@!6uENHC;;dXHH)73For^1j!_&}L6X_Asj1+3I8>6287_4mX!AnGTSC zfbY09>263b(IFuD;*?I%D+ZrMqlxvD15(Pro}uVLC-umk+0}z)w!`lq`l%XsjWB{~ z+J1f>Nk3T=Zg}1j)w3O4qPze;KauyO!DWyw_!V^(%Pc)`JKE;9IYgDl3+2>hji!V?YoYZnNnbcIZPx|XM><`9M zC?XII=Jw!vL%V8K&x8<4#sA(5M>_Zg&5c-E-18pWOPb=`8Q<gr?X3KD`wy+ zuO1e%Fcu^w5zcqXR@-p^8WaT4&2`FQa0t1oE?Jh?FU@ZC)8XI@g}aXz04N#*I8_~B}_!{4;do=<5$lsT+F+7~+5^B#*|>J4dlHlp@P zaecIcWZCv@h&@tT1z};BN{g}t)HWDf9MgbKV%8F7_1S<>(AO`YE{VRK3*ANhoV;b*`z--1+=p?%1x_vC#mL0!W@&e9C4!x2#-+Ma=$}QnZw#>>ill@hc3zu_|X^8VJx7mg0^8Vns2;b>)^G4IN?>eYkz`9iQkojW4Zo2({s9-j za2ort2#CaTqwLFZfSDsE3GVBawX8%R4IpoGCmg?Z8Uu7>`6(s1*CyomsIc|&@-y>_ z^BWsmy@#pZl_d+_)Gbb4~eCdn5uH+++Tjklu;JlnJs}KN%PBq z#R@h{{5P_0!`L%_EcV~1!;UMaGY0np!hVeLob(%9PUZp;A`q=3wRi$PSF1IZ+^pZF z{Pd=hbHjw07*V;Ne0KH>xw%MQML=ZdyxX*{qA421(zJ*eUK~=?=_8K;PnjWt zG-ZPSh6Y(Ny!Ak~3N2p(7{)<(5VQ?o>juyBa!l+-qRv{!V$f)pK%?A}+?q0btwPMQ zeYgqQruV*ZN%NM^!Sr%}^nnpEobzt?^_jipty#ccU7PIp(9Gw3U8qwkT5rlraOX~_ zO7|PL5j|h;%P)C_s-6gGO-ex+}NL*gduY2NWF}tY^O||^qZA8Jn4UW#ZvOKy4EmQ(&3vv z)3~`R=0}&XGvLJQ!7FNPz2&#LH882UA;`pWzxHJ7vvI|&95Z$AgsB%o7mo#3emW^g zH94{NGyT-dpBde>26KnKb)JqTh_%(kTa#R(TkBp*GaGfZYNY)$)vL1+>0~Bj$PA)i zO~S>}nWsxv*w>HNT;Uk}fYu$y%ul4rDO>e>Wxlq6lH(z#kJA@X8`IWE2xND$i$xis zQwcch*B8348KTmVcWdj%rtD!H9qQg646QIb5~cEXV_Ioxd61-X8`FtYAVO#tELsN^ zM`QRb`rg%aBT}{Ry!Srp_dYy0T2{JwG`x+?D3@cCWWD}tD@-t6%~g``W4fH>ARm}A zRM0~R%PP>@AznJbvM9)0OI9CVe*r-S5cTFb7Ms%jK`#2A&ss;(?zTf-ipSB8uYH-6 zn7>6Z1497I@1`d0WBLZ@mf8z5cItU%IAvp^EmiFxv3+MGdRFG>%M-IDx~$Ooq>tFT zKS6fmHseLxizA1Zo7xyMKUK%1&>dH~=;ZNOl0PifI_6~b&gMQySc4fp>h<TY z#y#3+wlc2p@s*y=3HQDvQK)Uwj8r3SM71|yPn!t|34vk`4&Cj?A#}l*%^;s72uVZG zy#bc#I|Lf2)i`)^aT~4}#1-KK)aSWA~&Yo7QYBJI^Huj9ZY|*Ly;&VZxObRiVIm!Ctvr`d_)3v-0dk&4I2b-738LsvMJ*u4enpC zHN9=%sU(zYG?SqmMvgIAV!TrA)FIa(-p6@#R4%noYou}LxRFm3u> z&L$YOuhh2o_=vLIU!WBKHkEp;ZTfg~=v0bp-s=qb@|w9UkzSl>9m#`b(uaXmDo{iV z(&>T%kjDyQkT4syY`|v3)swt3*Q`&kalJYE%l-)1O2cd>=kJ(u(`X_3NM#e`FPIw! zE^cJr;E`;(>D`yXpo+4a74?HzbI;|cn-=fX8*6)mmoqZm-9@*x`L>&F4Rtvs=(&aB z(;=PvAA65ybB-}lJ}+XeGvg1pqnDJ9R*&{B@|}*AMmGAaKLFI9vpr76rJG{6g-zj~ z!$y7o8M9+>bIOl;yz0YOf9O(!H1ZchPZw0Enpk?D!7xO{O5&}l0{>mFhfzm{^4bIaBPGV;(=FI*x_!Jjofg9Ams(F@ZC{y%#`h5zZz;^?}U9woppdn=sYMiGzr`{Sh0F+{t+cKzwkS z&;4rOd;HTQQIz%g`4mn_6!?>)ZUmTJDtmgBP}Bv$A2Efejmr)Vx`-atB>)cs~rnJ7HcLer@%p%N~A|Aqv+`& zgqB^y{YD%htT!}Ykp(#(y8cxE;=2Yk41|f@!sxXj0LJ>a6kQdy{b9f>n^REU-ek9T zbaq~rZY+bBzF*n#H2f1@J_m=;0QwXMYLPn`*&&+Dg-VVSTsEb7IaVj=UQ%>!Ah|!t z#>Oftw0%lwa<0s1RF_Vc{)u&i6?|1eUq1NKe1=E8ucl6H`wDaPp;7eaLS9R_#KneC z`6b*=NUUJx*!rsP+Yc6k@g?JFpzXy%RAv|7A{pzzzhBRG;^VH|`;xUTcO(39^w#9j zBK6{{u8ex)w3Cw_!L_Kh?_a?~EW9r8I&bbdaPEA+CsPFxv>DopQPpmC4|4tE9^UrfQRr!6iUgWC??>S!A)v zRk#j^{k7;g9Kj1DSU4mwlTFDgo6~?#a-S9m8%gY>QOiMsBqoOs#rQIGCZ&iaq&k1W zo;z8XlS%uYJnL0n_A9tJ0RdF)1@Pn8WB`EXh)4RNxtwKF5-TSE;JBLt8Om%Mz>79F zR~M+3x48O+_UCMXsU!e$5FH7RiR{D2E*m5Z<~}JKTz^w`>9mzD3#d2a?hqasG|)k) z&1s+!W`J6^-Lk`|xty4XFUnXOMSo0Rb_Dn|f|8d-uL6%KVwC z$6fbRc+Gy_j{23Tq!~ex(X?@hia7i}7tfYVVUFIMb*`hZZZC|x{q)7inn^b50exfc zPK1WpIvPU>L&S8+K7Q5wD!%-w1&bu6iLc~Kt9;QN<>czb9No!9uRIY_!3evbQ(@Vt zyI>-kxoG-DP7sZLwEWP*zNJ0k!ee|1x$7rmg75=3XERBH$Hm~587Mx(NOWVJ9C^1& zZ7X-)^!rHaOelpcbXYS%9x1n1_f&yfKydJnBO`wTi$m1@hoz-zzwEK4B#N(XrqhJy z!y9@(3MY7<-gp`0<=3Wpa(i|K!8UYX*HJyG*WN0UVj{ApruE%#O7@N>&&#fim`^3DjJ?D&&(1>Jr&!*e0inYGswvx2uz?v7q+)vWVRB<#N}I|fdu zQnY0g#tBbQyzdHs-1BZ<|Hu5_0e87Szdb5ASa~O^_#TTbHD%$~tGV4#k;2}5az>8$ zt#h?o5w-nX3c8P09GSJaQSi(SEgCNNk5dy2U!96l+I$a(4o8j_FFNeM=u$TL)9S}~ zUgUfEuDO}`Z_nI2{PCmvsfma%3ZWLa5|itH(vVPF3(6SFR3Rftu)7vty5K$-p1DAeDCbBneeFa=H}3M zIr4AgWbf58Q<8D>URZ4~j~&X(pnvw#>Gu`o$nrY7*JFd`Q{O@Lwv&X+#alJwvV$@5 z6qVbf^UFK+Z9Dy78LKQ1&cQB@fOr79DOhfAv@@0lVnJi9YL#p}F!C00?!r>gJuRLcMm;o z8HYOo(;9m{4nmXNO|4}MJWIchu`Qe(bD!7loHOsBle2pY$KA|IGYV*acj37()=Ym6V7!nc9u&HxVE&4Dy(A!glHCU*mw}md(51&P^{%$h# z$mP1*`S|NI(HofoXV+V198xTOSM*0ttm*zSVqVzaoH{>WPB}2&Ta8kekuiI&TWJzs4a5+{Q$kj z$ga#0iSaWtb*}XnC74+*g+}!!y(8TEaiaX#m=)P4SKji1QODIyB#YbEL<2aUS^qpN zh>?DA;xz5p1Zfcc?cwv4@BO`G4D&s4Zo3A@l3{7CZB#*KyX#%bHyDusl6%`9e1DXHCGow}+1Mo4pFj{D=>L=!S} z!|}|>&fdoBhw1U=n*6Nw)aj_%toD^#n!6&@sc9Da?eO{EOA*Ix!&eW>d1O**=EgO&L z^M99Xzn#~r})tbozul~;Ke4nfn8-#w| zBWz-yo)g6xFbt|F9D~pRu#5q(b6CTr?28DnQNuR?ctop(2IA#L0hY*4x#oe8Y&BjO zg94Y+QIg-_e@pIvd?x7o7!4X@m@q`RH9#h@yG{f|a7)kaR zje96q6ke|IT4puoab(Tcf{^Rx{t8#AR{LZ1McM`wAC1wu&s4!N!my2}8<|X#hP#oP zX5mb$RprDxWjpvyN0=6RB#OL^h1z=(2BfFareF%)YnC0u@d8By{&el-LHHkIKyarP zyutt>@+BnYIG#Wqdh;mum*jMkr>E2GaOhvnJ!Z|rZ_!8VCQ>a?n~SHYLceXOe^ZV| z>t7YKIjAMAzYJtXI3fKp$Z9nfYuJkUGBvO?N4y~_jG2o4uIZqU zC&?G4mis0{?(yzc?^L~|{m{Jxv6SY!*hx3W$6j1KfWF5^{=Oszp;ybF%8N z+!*+xYGEe5u+FDbS6h-XPu|$^iHHm~R;%i2+R*#6*gG&#;uSQ0KPlnSCq=|f796tl zHp36x^1&C=;M;mKmlj{3C6`z`E-5VJmR0Cf88vF0O9>Vq$=sY{W?l3vZ* zW3V&>!9WP?B<04&;47rS)r$@hvNUvCepRot5SHM;8q!;)Ih z#|TEIIAfKft6IEi_)Bo-CPs@}cb`LrNMqD$h+==Ik-YZRIG!{(#;?e?|kzt0xF=xqo^ zM5$*5L|#PEWxY(gCB=tlVd-F0>wKaGvxE9vQTh#c0`4_;&rcZjUg9N|JUK>b$^&fP zT#p&|N`b*qhZeQSm8vg2IsVm1D(bCQcg8W3FLbi~hwj^)nyb6{`)fS;2P*@IL7-~L z_x1YSgNW_bV?q%-4K+C>`At!jCHZAxr$L>&1n+bvNS4MP0d^i?XJwAGG!cxr0siGW4;ICJ0kd%3RH z6_u=N#SFWDHCCYL8yKnQ(I=lb@)$IJV86G-LE2z2vQO?~)AA*3ldlF!MYzC^K&y@7 z3R9`PL4!&yauaKIyIJAP?R17&F%CO4m8*yPfTN=#;3IpC{y)8JLlikd)uLJ$Ih z#fsu4L7^zf7t%$E3jfns=nP9IIgy#iP^jGdX{ld3`%pS^mpSOvY=sc2;mYjpav)S@ zjv2$cYj_@0^ZxGI)an$}%QZM~hHrT7<{Xo^a#1x@X3t z?XINq=D)>^l_~N_8?@od)eq+5LRK?^SO1PE-*~$IY4GnnP1VY6n}81T^mf&C7Y}aP z_0`WDcH(Mw4?cVLO*Y;&u>zt%O5Fw6j@iVyAq@w$Operp3*`=P2T z-_)C!yM7)q$7iFoB^@n}hjz&!U=zK0!t%IDopiCK9KYVQ9m)&;j%pZp<9SPKu6c#= z$N97KvndcZZCdTZFCSDx4V9qu;s2d~2>mR__uu8*sl~F@cApsU;oHC4Cd@m+D0~ac zI%3rId=~p-58-kztfR%8p6`F^Zu#z4dPJ$Js*YY0)xJh0SfqgpuyxHZ{yy~dXBW$$ ze^uNy7MI;`{_r2`2P+xYGfLBCCKRsX>q$|)1w}S0hOuca>SIc?6mq%zV#-+Efrvx* zPe~&%+P!n6tvrD6lV3FgLVjFf6=+| zekpcZ6+_7|C(M;sa_=4v`Jd6-9Iu9)QHEy^bJkXzbodoIrda%Pw53b(4fTa$GacU; zrS6wa;dsE9^D@RhE{`w(j38j{ABV!eRp1}^MEI@q9Upky8d;k+o^8$QXu2KZUM2OL z#hI$2j4L$JkLLlUmA7F8qr4??+MIVH8~pcEhFdXjk;9S?ZKU5R`7&5U2R-RKqRe*q zA3U8&oXhQsv3s9z!EFj0xk6!GATKdCGE$qbVgDc1KZdiUb&Vdco#*x8(Nz-})^ zg`0=U1pbZ&W%yZ4EZ>$z4}t3n*j$i9{BUbL`dvGcIZ49FFVC1iS}JQxLm-~P9MHXv zj_7lGqCQrUTwfSxu66F2=%4Up~x2gKRPwwdZs^W3|`0L&l<;pqFd~?f3+RCoN zzn(+Tw77`q$9m}!?&%7sV&CmrdTD|}3?>-W7IDgu5r8~4@xdUBd{hxKs?yZRHL7O< zfiOF+P>$cws;Ee;UOk77C{9C`StJIsWc2w+wiog&Nnv}x_bBg}XwVffdQ=)jalLi8 z+(Fq{zl>R<`No~(U8JxF?B_!u@@k#+iFQ!Xqeg-&N~ z9(wKX>FTNizd;Gn10$90eY}VrK7HxgkN;@T2 zS*h*!)6GX-IRY{QcAH<+vxQUZ8>cRzi*r=jl8R;$WBr2sewBv0ESBpHovc8GZ>hcL^kON%lk<+feST3UZeOm4TBCxy=_U>+W5R)nTf^lv}v`2)e3^D24*99hdnZg*%%jX7?j#Hv- z0F~iM!5J$>=!di}ZETM=-}k%B)R2fMEbzb7F>Y=ciBuQi#7A$ypy7oUkX!>3uzEMi zC#PrI*&T-+$7OAShoim$Hx^Gc8?w{1`WPbBZEPfVE$Z(*+#y(^=MGXD`t zH3TxFMq55>+!y&;);8(qRM|_~4$RCLd$?)SXo^{LalN}^WvWF@uNezT5gXgu>n>3~ ztv)MPK44d_3(Bf;zfNZ%kkrWZ2ULQdXTQ6`KmalMc9Fr;TF{I1Z%~6w#3v{kT;%~XNmc63h`5D$bTps7w+UUQW zqYiC$&h`@3fC8hsqB(uWqKH_qeg`|OCNQ`H3~Yj_f}16DmpnlzJeKJr;g+10B>O!S z96+XOMKZ8amF+7C!KAPrJz`XE0C`r5I}7d1jDxz(X@=5iM#ny<@+POgXCwqF@!U{gc0AjA zV6t||qrZG3+DY$iMi^Iwyi>1JR_OXOr6(p^Mi~l;Z+GSIe4Enuwz#qA8#Y#+4s#}*CU=GSX|IxYcbd{yLt_JFgt?s>yavL)--Nj6P}UEp?wW`<6~ z!k1}t);rFUOZicJNvyvWUyNshC7=*NUr1^RrwPwC%OyxCLA0l;^<2QpnIi=hGtWL-peCXlYy z#y4=9cu1iwT9q1*#j2PHvvK^4rxg_*!1ly6^R)+@(s%s1)^Usp(J|j-| z3}ZQXN#TEZRglx`H|rmueKFuUzfc9aVNbdfd(9fZcP)&Q3 z2yV@M2W?Wmdo`8x*X-w#e?Sm2_;@tnsJw>s^F+KTfoKsL&4K?Go?YPvdW3;3oL=P9Z%5eUzq%<@I zzo5~nLECkk7U1C9gg*ciiig=HR8LjWvGL?1E7 zi{SZewsatpV+d+u|KM+kf+>WWiTAKux{+EE`0MB&r`AgjWdIb&swhMp1pL5=a7lz3 z3MuS?W)VqA5M#L~2-oI}4@L7f{OO)OPN_cIV~q%wn(Zh)^8K^7v30nz=fAG?ckiWa zTC}sOBP8yF5ae>W^(ASz$`8n{6enzSi;OQsAeKS4pikp{+-$!S*x2&zrJ2PdY44%!;C%g*{`n`CXDv> z;O>7jZ0op<&lmLMW?Fx4ruls)_>cYZ)R>oILYjhc+#|O0iJ^LFQDqXltNMPGFJ?;h%veKc}m?UvT-Uhip^ zD{h{6^WZbHpAv5pc~c&~ebbZpZbRQm(6S|eS}$1QQP_Mbu6O5L8Nah`tK;y3nZvJ4 z2IzzjvsGl~b2uV`FYE~eV2*!`j?@#%R4j7H3;?t2ksupDOAV#+jH2R20iQvzJADla z?V-BQo&2VL36!6Sfa#K#5gtDESX-4&2)*v4j3j%FOrz%v6OQPkuYTaROFCY~C?EEo zEmY^6ZM|F;4B7wmGA)R!DB+SgE>b6x4Vq$@$^A0>;qW9$#|QlTO}o40ZQ3XpsB=rRXQ|#;i_?t$(Q}1_j}}BA&l{o4Fh~oF zjQG_1x~Wp2Oj-MDX{Ece;>pX9t+kFnvZuFedorWmj<^a6o|C`CF8Pd+K0Q9|&&I~a z>gww0>8Udwi3B8T9UtDzW(Eq=mi)j}MU|hsEl@_Ct3@75hTf)jeDM`)aoDaE%S-lN zYHR6OA1ik^*@>g=h3eMle;syzuZ$bQIUOR$4pmSPOBdnPsY{Hyy7Jq%sh?4;Zom?; z+mFExc;5pQ9RS$S_&;az2+-lYfYqjgh0UIphFli!%$%&yqP0siQX3~EhvOK&kYFj= z(9v%xaKubOJ-VzY4JDJ%>|h@~gc=iJjY7TCW5WuegjGd2KI;*9>^SpPQEIW+Z(YW* zIRLzG$oNK93!UF{Fq6u0W2x2ncHU9*)XI03Wn#9eH;PsbJpBT6&**q`HUBX>`hTOT z6RAoaOkP0H4_rZ=$aAVrf~~*mAAN8frKkpA=mCV+1a?k{Gi*~58J(o#zj=|RZ!T2t z9;3H8{{s`GM{9b>`f|vRNmhm3gSP$oO8x-rUzFel)+2R=R!e<)7x)lRBk3*v+X~l7 zFtVo&ZFze0Qcj$eSkT;0mil^$M2hj(+Ud)WxSv{y>piKRWyWzsTf*?t`uNhR1nt2f zX3q5d7|qf1x7lgcQ%JsqR*^m16w5@bx4v@2U;{dDY}+A_FULphX0UQT&y9)RSvtPS zy!eTppZO@?()(g+Ap=C6nqA<0)L2X8=gALQG35XU>o2InmYlf&k7$x{((?W~LS#Do zntG@dQ)|QSgU_fyrJOOhJ6$FdL=Lgg=(OH=pP<`!-|8)V^>MP=kz|hWsdseR}#!YV8^BJXsbkjrH<3#!I+~%J7Nic@bM298=Cp#?|Khw zoUCZ{$u+s-5E-O{ywjj{m&kxZepTf)D=W$bR=8Y1wKxuX6@lv z@h_R&-Qo~3=tGA@o=2&nm?Pp<5R9}Tw7?$-TpS9_G60=te;=P_;mdCzYRI~xd{!ng zVy9qERXncrY6=ua8~1N945qjohI{lGF@oksS7ApXV??H}FjQU&9Y)=!7bO8w1g}6q z87({%&LkBHgjTpzDrSYh1%`W(Z!604(62LyX)UAkcBSRKEX&@m2sS;mNKa9kz@5ta zpFITmmu0$WY#;4hitbG$xP2Z6uc0pi5)T1D5jtAO-2Fv?z@(UogOXkgpn6C{Ol4Xj_?4BYM~ErR-~3p)|6__~puux$*R_2Y2gi81$y@sz^I( z=3Q4PUP~KTIo0RkN*pl0) zY0K|LjrFT8FUTI(>i9CsxJtL0raMCLXJ5_ex*o<@+Flm7PZqk|w=w0q;&1&PT_TBWtSh z^0wq>E?tN&Rpgfd)6K4j&l4a$aaeLbacUif%DE}(2s+x(;{+ZC7!?j1K1oQ91@l4p zG$JARfPROhFWtNq*AVO}S?npu-gATJ;4OIbX_osBT+1Fr+kX6oOEIH@JN(A%@4KIE zD<2-6dEj{tY)wsd*tD-&dU40daM;shdmwXqw&>VT1YnW?@WPl&e@Y=%Elq~Xm_;R) z8^reKJ*>@J{p5Jlea(KqlwBShc>MN)j?~=={IX5^No?LBC{0?h;jcRU+eR{c_j+^v z`BPSRgS-x8D-pn5~4nwjwCa0&4%AHSKO&&R4TwQd>uAkUYi zv3dU>a0+}Rq^f|41JiA1AjpvaEaPl+_5!(XyffmpucN~p{1H=6oSUj13U(0&>iW<` z^E*QI#554Y@FfaAVGf%lfX3+&Sy|b^nx~kt=|*=7{zJV8s#_A+^{Z@7aH%!PzL6KF z&^#5qS47DryO9GzLfQrwgdVA`kCUKIxbi4LFZT!0h2QV=i%KEbvY;AGApYPvEr6xv zj0E6a{_|nrc7_v#qGN$hl0XhQ?ELY8!O4GM#fW^FW&&eF|@@^^p$uDJb%MRDBWj9tFSr*V1~xzgFyj@r@=gf4^t$B!J^NRS8!bn+so!=a-< zr(UXhYI&EK$~?ZovBG!4xQg z0!ix7Lbj!!B;Le&pUd10y)liAf(j?6(;ZuzXTslq94{rN2Osa@b>wB*{{9B_WXmrb zzWBT8(Dln)e$B)XaWhmc_K$}gWn7W=B~y22|M^jck+R?? zyb}&zPOHVzqNoGySF=O9{}EKNqp)d_G_1WqZXVv%DnvLisM47Y4b5 z!L=1y**1-1@(*UMlsEm5)|=lFBa@q-ue3xmcvF%Uao&N8c;1CPc2=-msyB) zFd3rudCkx!^t%mVkF??(e((#%G2=})!PWB~;k#Ec@(3iWq_@%RmM7mh2j8a<;! z1n|owEMPBb1&_cMCWskXEz7P|pM9-9;XK=J9Fpa5RDbBg{LQnk?1t2`6cU*tF<8Uf z2xePvZYaH)e|9UX9IIkD%ndUcr^afNEL^%}oYywm^KLp{6NG7r7CHOSt7G3xy_J1@ z^^quU|FCu~Nh$E)G=44na<}1?JMZ6=@4LaMR6%uNNl(s0Mj!$TojZ3<#S8_Kr%OQ) z8w_Fo=Nuy*4~8g#x(C^{w)3fAS}u;N2}_%gUzj~y2|*%Ipj$N+2SWl)+(?75IQDm)$FJlRiri9 z7*P(YU~kF974?NC6BJ=k0|>UwOsp*}!g&n<_54rfLHiZxwIt}#f_*jTFYuq}3-gFn z_Uwm1l-~tt|MJuXUwxEhnsLWwuhMB)$6t1pdWp5;ofqx$g}OF@lwig0%ON|y@^W%9 z?5`rRe#t&o*DX=Sc9s+4q^L~!%H>135z*xuUU#>u^!*$totmj|fdUinN7Bs*)cv>_ zL}I%~|NV=t(c(9&PoRV74sK0uoxo2yso@_`1K*~mI?J;P1;PrkdQaM^Kh4|%#ve^w zC^who^H<$DQ+h&k%6BZjM||H z9JRlNgynS`CLPqRKT(H*NXf_gIgHHQfNC}%?rOKhH=JX>xzMDuz{Od-=G4#<&HWg~ z_Q^w#P24ZMXC9&l4o*K>poHw^Y3W$LeYzeVhSNUg6{q?6G+`cocUfYi{pfZFhB9&{ z$!op&cAd6FN~b?9GA`3ZL?afSsPZ|@YzAL;yI81bja#zoTZ^DWC9y2)Q@Jc=a0>;|@5BvIlqg>{ok zA?6KZGB3nnZvYlDK@T;<8!j%X&5!$ZO9GXXD(U@3iZ)8&X+~Zb zfj3SV3|I@Qd?#kbzwsA00-0Yt5S0(#+Mjr zQOuIG8*O-Xl*~E+dMqJGwlF9R9vX4`IRGG=@fv{2+-# z&*j%gKlBonUv4107iX{vSS#MGaW2{aX2D9}GqWJyZ2#a#X)Xz8yLrJ_Z*hf9pNQ`SwI|Gsw-mP0ejBJAO&Cbb^j&898d zogh3vug=x?!tUeJ%=SeyEZu(Lo z`pc5@6bWMZjb*A#=#fL@M@7$%3U1fm-Mp*D1ga-)xc&#oF(8UP4aZfW_LvD`R}TAp z8ps+&ij*@e%qT)HydIQUGU}QORB&cyRD<=iVOgjEvC=fQNOUxqu^75hRR+5!#O(Lf zg}&>C&t>Y1LRb8{$qYqI7I&S9(LV@*XG`-#n;|v9%ePnJO-%=#3{o!Zt26q(fkPaO z3Oug%#cGuL+<%+U>HLajrYl>}>q&*n_y+6p;i;V>&$>tWSpNpGiSuHf^2r~q_RXV^ zy%EL3)mlz|vVJy)>(VVP*R}a)3c336PPWZ0&CNl&I$r5%-2Ehr(0k8Y!9=eO2*}}zZ&FV6Y<{Nmw6$jZbiF=XdFA5=A_Ge8o=G6mkHlt&o=TW;OhWMd=Zw!kLqWGc4I?;E_FA7u5Htpyuq*T#1}i zo?%c|M~lbtq_j#b_AC=Dm;8-4f*Di25 zYe!RQ>wlbQA`K0Hp$V!pneee5_Q`!@Wnb#D_8enLD2&MVXH@~;JZj`nY%KsgM}?q+ zQ^Wl)^rt#k>UpGKL2?81L*tEQ1*YyAv2IDR!BnF-2$)v{en13hhK$ex@2@y56bJDa zB14k39QK`q>i!!ZeF~nl+aP33Vk7ux^Q!voq5E&g@>&&wgF`63!H1VIkD7ybcU0uw z-GbQUtc?;5jua8*YX}^WQO`>}7gaK}w6f3KV(%gbnbRlwYkgdofk~co8rQhC?x)?) zqIj{i(0lEfm2Ub+vGjH^lj|PWvQNZoznNdZ47sSwEsXq3nl=>>r*$AN1vlLudVF8c zEV_iB^gD08cl~|Ef*>)(;#GD;xM1c_lx&d3M45K!Q=9pbTmHxpPG_lj+vTJ z^_-f+pi%vT8_&RnCG(8`w5Hr{9yC3W*D5qimEPFWa5OTaorgA z6lcq4TS@IFvZofw*9?c@3<61rNV_;d#q%Qv?h}E1uzMOmguXxFm{kl$V=wDj+W+koo|~YJKp@0=>MCI77J}X z{`X*B2h-T&fr#_008|q!B+;6(CO2wxkf==Ve|CW3Hi}0A1g3+F5z1bd)bC;wzAws+ zOqLX;7STrO2nnNPHo=3=+4l?vu4%xk^9htmlQ?w5;16+g9e0$k_P(7Lg!3t_^i}3@ zUVmTbOc&jmgZjn$3~^Ch(txiX)&46t+dgwBkwmo%;DUhGgN38+vN}ZTMN!?=N^icOx#+)0^0DIK^*uaTaUE_Z=S{EeuFQX9 zh#7)#XsTof<=%ASDp5_3$>^Qv^(~Oz$nQDmH(U^kZS78OSU4Ck9$6Ly-sQ6ofDYf} zFhM){8U8zJu(+)pq>idyW&N$ZJF4k*U%tD13Z7vSbax-=jmmg5FN5bzT4qu~IcDc6 z1eDpjto1SAW}IzYxTM%p7mcoDEZKcA;^UzWy)qZR^&0S?fanU^$g%-#^#R!EbXico zP|YN55nqamwoh}&n+84*yf#uYbjmm&H$(nWmtm)}IX7R0zVHjgZGgdNGyp=a=e;Bn zf9QVj;$sHF{_SnbS7_)e!-^QAX$B~>B|11U!#4rjk9>B@g;oG}yGLZ9yI*5CGI z=z?gwp*T3eS`PMIn?rjY@#fa0Zwr3%2X1>y-<1`|&(Sxvx1S7{wjWmqp8WkZbi_}7 z+PLHKCpUDzVVHG!|9hvAYjm2d=>&e-g^yhdPNxRX!pE3^9}0yQx-U};p#?BW@3ATl z2x^ZeI0sA@how%&F(h-vhH|G61g`$rL7&8QYz>A`Hjml*Ot?5x_1-<;R?Xywf_4*- z-B{ILRbCc;=-cSz;)6jMn;@u#FxAc*xp%l@J`%V*t{5RCKM4H47Qp3hEQ#tR!P}~3 zczLRhec+QElR+LiaO>&WBCGGFBFWOmRalkHL9_nB#4urFE)1A^>Zn}`DWJI(%1#Xf z=P3E~|B}zBR8Vqr=YXDbpU+Xxy~Wygp2wJuL*NA}2@wCtvmx1#T&!dQuJRh9R^!)%K;gg`Mn%CAswDd@@J5_ zye^W@<|YFq+;*8$f$h07{`*@*|DzUux|4vw+*!MXZDm69Qr1zX)gx+q^;g7ftoeO) zj;V|9;=(yUQRdEnIsYF=9VeTx_4E9~RI@La{)^+hP@3Xg;Od+uvaat-w2o?bm|}NhL+=RDLTGa0Pv>QC%S-W$^Wdw-br!BdL{wGKTz{}w0F$9iz9n z=Pya4t~Xa4EJ%>gi0Ex z9_H=vcN)=+*$iGDGB}wVG6*_54aj4O(fyz{eXuycv)5WZEt#7>Bo@`Azw4ctV-ev6e8<`kdu0=^SD1BmriUNj#a@8$YB zFS$2y9CrnT)a)FZ>_wV%=>af-GnC2Oq(}(p>*>3Y-X?EW=PFndv>Wun(d7uee8-0K zwnU}ucYhjNDEh&jJG+OHDj1b=a}+m#2*c_+VlH<|athZaUlZCbE@SZ<2dfLnm>}}4 z1?r0yE;bF+I}n(XK_L_P*)_gVqOk8dFiDcSQ@7tBX2_N@qy2Wp8XXwdZZ#E!Ew(dWTC91YQEuS81JJY3!-Rs*M&uSF{-`6WvGaDvhcAYNX!N}t4o(W}{aV3RMJFcL_Zn5~-H2BtE zVo8gXzA9|D+#56K_d)Sd`)yvgm?P6&NiSQ8ZbRJ_(mk`}^yQM1R*q6fkiR_9c6}`u zC%qb&lhrVnNqdGqq5s58 z)3%Q1@9fzwwV%r{FY#RAG~tc@C?@vkVR~6#q0q&p_m;GakzpTpqj+yRmpYbore7C3!u^a&09-Q}SQv(jvq2vHCSQAFY4mjKb zo^#!@v|}rLj3~vX<#rM}Jv4-{W|?(cH5~~iM}Ep@TdY&=XE!^}{42sTA4X~G%As$W z37xwlq`F6NSR%GifH$twGk)GD9@~#O6-YB@v6~@($CN#v2fQoE*ZbBGh4|Z2os@Jm zRhJ;tJvgNkPxQP+@R`$#kufP20>1(PwvHS?$HpM2p(0{w7fj2Wa{i8ph%ii801$qO zWdpZTvz#kF2+&A2Vf6&jK zkQ;4CZK-&?Czo!adR?nmp9&^P`+>MdDz8)%0E?6d-8F%cSO!mxJ}eh(Ltn=f+{#$? z6QtQ;;4TN094T*9P!T!BPLr~-*Ni@Pw>=gCe+DDO51AKWcsov1%@h|4{M1K@ z+M`j1MY$R6uczS)#iz)#9p(MYr-ym{gGmDE!NKw&2Tdoxjz4V%*DGu$kG}5?9a1cKeG{+&F&SCmY|_fyFP}I zB-VFF<=JCm-qCWe9HgG61);XwjGwtMzrrmZxBA92cUKI>+=FI*<>9k#aMkMa-+%4i z269HTrirq}MonpPVe^E^nh-O)^VGxPe#S8gkYY8*+}@RL{D}0Ua}#zg%^~VSF%PL8 zIq?dP-li!_^O^N1!|&U+HV2%0H}u!9!))3;aV$Tfd29?H<7q?*cDnPr@0-P%! zRSxbXBo|@*1j8Zke-1jjFiXQi(^ke$<^yI=_X1`^j#h)SlH`Ys&Oa7;`?wg)h%IOO zcD36qmFbIiD6SG?r-4GoLl6Uc$onP$7C?4ugMd{`##LBY3W^p0pB=(XmH;A<19HZH z>~i+Q1{~&Sp&*aYYv9zE=#QrU@*jd)B@nzHITXJ}fQ|&d7eKka1`h{|`eka@qYdRl z(&7)ayGQ_Z73CZqrM*EOUKXd?HrH*N4`!^XCHJdWAaTuOwAou3<74iCza8#J`8c zZ9f7~1>(wjBkJeQBHr?#q&jzLp|mwOru&_SnP?<5<=)>K)1Pqif9j=Lk5xR4OsbPR z*S-oUQcA!W#?MSIo-|aBju_Er8HnAl2qHD&8yhv(YZQ8%r|3g=b{Co-EOP6+yZw}_ zuj9bBsnavmki=drtL@qvNpm&a%zv7JR2}2yNjbdYxO#MF^EFh!iRUrpH}_DESz-G2 z^VK#jT4cc5M=JHk_7S9-wmQlw?Zmxk|{yfiuj= zSulA}69Hhc$B-)O>K1yWEE2KPPSa_41A*$HdG?MEr9mZ$NVBvny>PHNm=wGq+nXWb zMr(Lg+J|xT1p!+qc!0XVFsMT}J?&?q=y7Lno}prb3&$ox)8NKuBOQll$1!Ka5|mmF_^*DHuGlMugb({fXra-E zrF!CA0=5i75_}C%eAI<(rO(6r^>!cV;+z>UjcxC3^J(Xd8rwFbmv^TgNLWIG#EvUv zd-(%5hZ@_1LcH(Wbl_scwWX2Nh%P~~N1NH-j2K1Pk!4U-v&a(4Ru?s(SGS3WgFz%& z+(K51V0DY@1QRW}`2`S2wtIhN!L)dWPB0m*{=sqiS(gN&Kzg4nrHEwk;GzTi2o3+7 zYDFAiV%1VK133bSjK|gg)_4e|`+RR+@`6c)cWKPmf)~3mTqrwCHQX5cUnUy_NKLMi zgGznuWDA8u;Xo*CtUG%e&BYz?8}FXcP}SD>{UF%-@4~0_PctMj#oDwm>`&e8t54NY zFg6%EMsHW{+_IHW~6STn=tY;|X&G30n(`8mbk|72tS z-22~(5*g%h zmbU0!xGMeNHC=?7W(!+%AMG-Lj_PxrCI)3lzq*C!C0bC3f^Riw_X*~`M@L7~rj61L zy;XvQnC`=`triEGq1xjV%^`w?=@dcGNr`VmxlOA_BKUZG=b2sM)mu5M^Un(-5cfg- zmqpw!Fwc5{We9~P3Wvvg(rdrSrZbsz{g3?L=d202AO@cVNWTxam`+jE zCB0cw!y;6LA2%%^VTP8 zxt5u_w@R-j(`cgq4LT5@b`ue&jXnTR0}4?~u`XQt&j=-CkFxbgBWPic=utb?M`h8a zMxn|5QWBTP4^DUH8_%qd=VwDSI3hdK95$?|;r;Ac8oa$eRzpm0Ic`@Oo{VQ_=bciu zmi}F}oi~cRQeV>Bs!&(5i}uXi4Bx7$+ijRQeC~~zzFwRyh>%tfkMZfWP1q7LYT2$9C6r(zsG1%Pr&KUB?IcaIbaH!`j#b}^?vz|!22*F5p*nu~Di z_u$&pzRcOd)I`YC#65^B8vKP8qfqEXh`_L3IQaH*q(HnW#ESsaqyEloKu!CExy3X} zh-B@$M=@Gm_&*t>+c=68}D}zD`Da*2|>X+%rPQc5UA2BXa*AbW;|In%Kwm&7dN(qe0jT;U4Plh_Z=p!ty z80pTx<*TB5y{yCHvvSRd+!0*6kzE;(BVpHE&Ug(Mus3Y^Ipy8c!K?=K(o(69SOlZD z#usxBqX$#~MX&gdd-qBj?nj}Ri%oC$@!rm_J9+u2=|NStcfE7zB);M?yC(TUCUjk; zOI?)cY9abU`F~=DHB|Iwmo^FzwD2*O*Ri19z zkt?3LHe_3ABhC{5v^*$%38n-IiU5*tZ3GZ&k}qPZ^$0DP%Sfw<;D=QWuG2?1nIiPV zvA3>C3GR|#>sC4*sHh3)>Lr1;=f8A7j~0Om1-_C0#(cxtu#ZOOE)_+T;RM(`5nRnV zbDO$AoHOg05NbJXiXAQT6%yqqe`+&k|0r*K6~sfr9LzBWUVSL6(8q>|L%@G_wKRC` z_AYDtIJ)TNRw(T8-P+~lW!4d~&VG@)|l#05aDwSrP`ix|Ak{7&g zdir>k=CWfgfyeo}UeDYI5CwIJ1}7Fq@i>N0SA@R`u%)Cs>cCO7so1SwxQDOGE>Lq6 z)XM^rNqS6g$gkOY^Y!oh1!0~a9UQE$jgd~kg|df4Tx2TAe>Y+44;^bk*_+U`zRm=Q z-(t#!cn^4Qp*8=lZed~k55bEHAtdcBLal-Z|7e<_8DZco3CHjOo5^i`GB9#tk7o4V z4C14MF>J!-$sk7<)WycW!wX2qhN`=R_3AsE{K1DScKTg{%Vy2SY2p6w5<88w&p%;< zU43`eM5KTe0sc=gD(b2)0#dO843a=7TdT!viz_d0V0=(*!&J-n_QRU`!LJ!Zxi&We z+pq8eLMTTH?R^ti=JMh3jslaqdDD@cF<3Zb_~WQmU~%qTNE#Ki?=)C|bPIHZZCJb1 zx3k}iEG@&2nN&Th@FK8KJvFLErTAmTMoP6^cx#Ng;(J7GbM;Y zNw(7$#7tv`xl2u%@viIi)L4V?jvOndx4Scv^xeuNfX>Bfeq}Y=F9MxjzEf%5Q zxj`0V+3B4^YdN>8*32&#Pd?8?y0zu+Ia=_&RPD9nD)~v>*B>WF1%dbi19nD9IEf{h=qAEN;{5PtMK5Yqgzl?0Yo-cEyx> z-Fj`PQ%y&rSfLQqO1is*NEKwnGgRbR9(a@g<69HJp6OZ_!2#64(EU572|ZZD>jb?V zAZ10H3d+;kJ{@wXeDdmSvHEN_2T5b^P|V+3N^}ORoaJtr@>SnS1QXEou91byo4ci6 zQlh9`!Dhgast+;8D2RMzSJB-r>*z$hBRbz%u|dXP8&e==Pp_=6P0P9)U`kdzzB*O) zU2uY_=F&3pZIHVk#>NkHqG)-rTu@vn0}d3!JusW$l_VtKcH`y-K#*vxbQmBw7pF9! z{5cfIzy<2qP0`V&XXDBgPUBaxGrTh<3r%i;QiA9 zxmVH*qK9VBxl9;;6!x1%Gw7&L!%E*^3jxGM_=!{~02Hdg2UQ$;TEn*_Dd-OY2YW*n z@$FL$pBwfw23susy@LXqWY!|>f#;uO5(UpYh!pU%#4MM9Ukok?3@3@yD6AKW+)!fH zKpg}PB^lrN2<}_D1lR@PUEl$vf{&ThPg}bPhZ1_8A?!6NgdVJlj;QnS;3t0?M|~2= zwLAWq_gD7Ja*xtnxz%(+{y&<|JDlqOf8)ofju8$LWga6W>s0ng$Cj;+J;Et;?9D+E z$H>Y$W@H{K*<1J!j#O549FpyDjDr)x@BR5**Y_X)xwy`Fy`Jyqecz9}{$l&%wLqh> zdbO^YF=^g;)ZvuGICUz3)rWi~SX_H!Z*R|H65s47qPoKbigS}sIy_#H8LwL3^Zs=g z^yg%+Dr8+AH|ftQe#LTu#YfZ>{t8)6#Q|CwcNKXeNNK;#>o6!CnrZ_-4(>Jc!-vsvTYC(NA6-QhpF z_m=1;QMRmbGAWq90KoG74vY(3B2a#9YRHzH?RMl1>D?TwSx<(1v)$=Dqc0P;!}7>6a z3req-&MJy>Oo^x*W=76DACS`#GHBg8@c~6gJktvDy`D7(le+j04LDFf?*hBn1axP0 z7QawB?+6Yk2%DLmR{{>!Drb*7R?=^Pb+e>eP+rt|!hA-x&F18o`qWY~^60ov7!yCuP%2m1bF}k|lL8fSM+V5JLAyHTS?0e2w zr}pQUIU$7~aSvS|P#XvYrkExiILG6s_P_mbC;iqbU`k(R-Ttd_! zVSu3NQZ5ZFT?_t`L>8qWCZUclt#(#NNC*U|){^{beLG;Tmc>?!~qyfx>zU@=inSTtDMXOhFxK z8FZg2^4>xI{-LOO>_{%j^Y)$Tz!Ii6p||@gCqUgYurNGZ7Y3+ zfAH~M;W8Z>3uSVPb4%vICY^q`sYHM)a7Qtny1ofi;6;dbsN6Xl3nEUAC5MnlZgMio zUUh{x@(_;3Bt??oH2jl4LgA?g_`F$Yh3k)*?Re>QDu|VFV~h6A*w8~hFN7NJDQ~#j zm*;D$g(BS7E~(sPD{HUqc9?;_W2JDA<{7gPOHOrhkh?EXt$~V+_zz)nzNS@Qi>)fN z_!&RR>Vaq-;b0FjVc>c6eOu?DCql0jn>ruXx?A$n3Cnn=E8`<@paOE8hmDx<3%JY9 zWsuXExBN9OXh)JHD(r-?;1KcWN-J@aiaJBE>P29xee7eAumaS-|MLR$p!_Io3vo35 z9~LdE9hur&M%}+l!q&o=m(c$daAqgDa zN81D4pTYdFz5){4B$>N=Tm=)lAO9QkOa+%I1LY2{xddAkrERO8qu-GKJP~;P-l>K}sF8tD^FxFA0qIm74z%UEiok4kFe6dw)S!6p z%oupa2GkT-Qi68HRoIYMpwjNVUrn>`bqnkBI!sQS-M-jbH0t^3X1AcW8mC!^y4XBU zWk2wOHir<*eB<$J5cMW~I!vSi$xL>)ZA!PQ`(GJoczqm?A4887@!8*b8dW7r4Z z(z6YbE=X?9pkkAyWS92g`mkB0~02Mj546fb+D;o@dw zcfQ558=0�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

ZfWr1Tmhu?mc5q|8rmR7 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

4A*sq5eNi} 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

Uc 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 T`dJb&5cttl)5R94S_l0P^gKP` literal 0 HcmV?d00001 diff --git a/modules/dnn/CMakeLists.txt b/modules/dnn/CMakeLists.txt index 67ace67ff5..e8c23f6a44 100644 --- a/modules/dnn/CMakeLists.txt +++ b/modules/dnn/CMakeLists.txt @@ -10,7 +10,7 @@ set(the_description "Deep neural network module. It allows to load models from d ocv_add_dispatched_file_force_all("layers/layers_common" AVX AVX2 AVX512_SKX) -ocv_add_module(dnn opencv_core opencv_imgproc WRAP python java js) +ocv_add_module(dnn opencv_core opencv_imgproc WRAP python java objc js) ocv_option(OPENCV_DNN_OPENCL "Build with OpenCL support" HAVE_OPENCL AND NOT APPLE) if(HAVE_TENGINE) diff --git a/modules/dnn/misc/objc/gen_dict.json b/modules/dnn/misc/objc/gen_dict.json new file mode 100644 index 0000000000..15dee3fde4 --- /dev/null +++ b/modules/dnn/misc/objc/gen_dict.json @@ -0,0 +1,41 @@ +{ + "func_arg_fix" : { + "(Net*)readNetFromCaffe:(NSString*)prototxt caffeModel:(NSString*)caffeModel" : { "readNetFromCaffe" : {"name" : "readNetFromCaffeFile"} }, + "(Net*)readNetFromCaffe:(ByteVector*)bufferProto bufferModel:(ByteVector*)bufferModel" : { "readNetFromCaffe" : {"name" : "readNetFromCaffeBuffer"} }, + "(Net*)readNetFromDarknet:(NSString*)cfgFile darknetModel:(NSString*)darknetModel" : { "readNetFromDarknet" : {"name" : "readNetFromDarknetFile"} }, + "(Net*)readNetFromDarknet:(ByteVector*)bufferCfg bufferModel:(ByteVector*)bufferModel" : { "readNetFromDarknet" : {"name" : "readNetFromDarknetBuffer"} }, + "(Net*)readNetFromONNX:(NSString*)onnxFile" : { "readNetFromONNX" : {"name" : "readNetFromONNXFile"} }, + "(Net*)readNetFromONNX:(ByteVector*)buffer" : { "readNetFromONNX" : {"name" : "readNetFromONNXBuffer"} }, + "(Net*)readNetFromTensorflow:(NSString*)model config:(NSString*)config" : { "readNetFromTensorflow" : {"name" : "readNetFromTensorflowFile"} }, + "(Net*)readNetFromTensorflow:(ByteVector*)bufferModel bufferConfig:(ByteVector*)bufferConfig" : { "readNetFromTensorflow" : {"name" : "readNetFromTensorflowBuffer"} }, + "(void)forward:(NSMutableArray*)outputBlobs outputName:(NSString*)outputName" : { "forward" : {"name" : "forwardOutputBlobs"} }, + "(void)forward:(NSMutableArray*)outputBlobs outBlobNames:(NSArray*)outBlobNames" : { "forward" : {"name" : "forwardOutputBlobs"} }, + "(long)getFLOPS:(IntVector*)netInputShape" : { "getFLOPS" : {"name" : "getFLOPSWithNetInputShape"} }, + "(long)getFLOPS:(NSArray*)netInputShapes" : { "getFLOPS" : {"name" : "getFLOPSWithNetInputShapes"} }, + "(long)getFLOPS:(int)layerId netInputShape:(IntVector*)netInputShape" : { "getFLOPS" : {"name" : "getFLOPSWithLayerId"} }, + "(long)getFLOPS:(int)layerId netInputShapes:(NSArray*)netInputShapes" : { "getFLOPS" : {"name" : "getFLOPSWithLayerId"} }, + "(void)getLayersShapes:(IntVector*)netInputShape layersIds:(IntVector*)layersIds inLayersShapes:(NSMutableArray*>*)inLayersShapes outLayersShapes:(NSMutableArray*>*)outLayersShapes" : { "getLayersShapes" : {"name" : "getLayersShapesWithNetInputShape"} }, + "(void)getLayersShapes:(NSArray*)netInputShapes layersIds:(IntVector*)layersIds inLayersShapes:(NSMutableArray*>*)inLayersShapes outLayersShapes:(NSMutableArray*>*)outLayersShapes" : { "getLayersShapes" : {"name" : "getLayersShapesWithNetInputShapes"} } + }, + "type_dict": { + "MatShape": { + "objc_type": "IntVector*", + "to_cpp": "%(n)s.nativeRef", + "from_cpp": "[IntVector fromNative:%(n)s]", + "cast_to": "std::vector" + }, + "vector_MatShape": { + "objc_type": "IntVector*", + "v_type": "IntVector" + }, + "vector_vector_MatShape": { + "objc_type": "IntVector*", + "v_v_type": "IntVector" + }, + "LayerId": { + "objc_type": "DictValue*", + "to_cpp": "*(cv::dnn::DictValue*)(%(n)s.nativePtr)", + "from_cpp": "[DictValue fromNative:%(n)s]" + } + } +} diff --git a/modules/features2d/CMakeLists.txt b/modules/features2d/CMakeLists.txt index 1d29320a14..a586d4606e 100644 --- a/modules/features2d/CMakeLists.txt +++ b/modules/features2d/CMakeLists.txt @@ -6,4 +6,4 @@ set(debug_modules "") if(DEBUG_opencv_features2d) list(APPEND debug_modules opencv_highgui) endif() -ocv_define_module(features2d opencv_imgproc ${debug_modules} OPTIONAL opencv_flann WRAP java python js) +ocv_define_module(features2d opencv_imgproc ${debug_modules} OPTIONAL opencv_flann WRAP java objc python js) diff --git a/modules/features2d/misc/objc/gen_dict.json b/modules/features2d/misc/objc/gen_dict.json new file mode 100644 index 0000000000..dd22459e52 --- /dev/null +++ b/modules/features2d/misc/objc/gen_dict.json @@ -0,0 +1,16 @@ +{ + "enum_fix" : { + "FastFeatureDetector" : { "DetectorType": "FastDetectorType" }, + "AgastFeatureDetector" : { "DetectorType": "AgastDetectorType" } + }, + "func_arg_fix" : { + "(void)compute:(NSArray*)images keypoints:(NSMutableArray*>*)keypoints descriptors:(NSMutableArray*)descriptors" : { "compute" : {"name" : "compute2"} }, + "(void)detect:(NSArray*)images keypoints:(NSMutableArray*>*)keypoints masks:(NSArray*)masks" : { "detect" : {"name" : "detect2"} }, + "(DescriptorMatcher*)create:(NSString*)descriptorMatcherType" : { "create" : {"name" : "create2"} }, + "FlannBasedMatcher": { "indexParams" : {"defval" : "cv::makePtr()"}, "searchParams" : {"defval" : "cv::makePtr()"} }, + "(SimpleBlobDetector*)create" : { "parameters" : {"defval" : "cv::SimpleBlobDetector::Params()"} }, + "BFMatcher" : { "normType" : {"ctype" : "NormTypes"} }, + "(BFMatcher*)create:(int)normType crossCheck:(BOOL)crossCheck" : { "create" : {"name" : "createBFMatcher"}, + "normType" : {"ctype" : "NormTypes"} } + } +} diff --git a/modules/imgcodecs/CMakeLists.txt b/modules/imgcodecs/CMakeLists.txt index f1deb920a6..f8bfd18e1f 100644 --- a/modules/imgcodecs/CMakeLists.txt +++ b/modules/imgcodecs/CMakeLists.txt @@ -1,5 +1,5 @@ set(the_description "Image I/O") -ocv_add_module(imgcodecs opencv_imgproc WRAP java python) +ocv_add_module(imgcodecs opencv_imgproc WRAP java objc python) # ---------------------------------------------------------------------------- # CMake file for imgcodecs. See root CMakeLists.txt @@ -115,10 +115,10 @@ file(GLOB imgcodecs_ext_hdrs if(IOS) list(APPEND imgcodecs_srcs ${CMAKE_CURRENT_LIST_DIR}/src/ios_conversions.mm) - list(APPEND IMGCODECS_LIBRARIES "-framework Accelerate" "-framework CoreGraphics" "-framework QuartzCore") + list(APPEND IMGCODECS_LIBRARIES "-framework UIKit" "-framework AssetsLibrary") endif() if(APPLE_FRAMEWORK) - list(APPEND IMGCODECS_LIBRARIES "-framework UIKit") + list(APPEND IMGCODECS_LIBRARIES "-framework Accelerate" "-framework CoreGraphics" "-framework QuartzCore") endif() if(TRUE) diff --git a/modules/imgcodecs/include/opencv2/imgcodecs/ios.h b/modules/imgcodecs/include/opencv2/imgcodecs/ios.h index a90c6d37a8..0f15820892 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs/ios.h +++ b/modules/imgcodecs/include/opencv2/imgcodecs/ios.h @@ -45,7 +45,7 @@ #import #import #import -#include "opencv2/core/core.hpp" +#include "opencv2/core.hpp" //! @addtogroup imgcodecs_ios //! @{ diff --git a/modules/imgcodecs/misc/objc/ios/Mat+Converters.h b/modules/imgcodecs/misc/objc/ios/Mat+Converters.h new file mode 100644 index 0000000000..73dbe9cbc7 --- /dev/null +++ b/modules/imgcodecs/misc/objc/ios/Mat+Converters.h @@ -0,0 +1,27 @@ +// +// Mat+UIImage.h +// +// Created by Giles Payne on 2020/03/03. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import +#import +#import "Mat.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface Mat (Converters) + +-(UIImage*)toUIImage; +-(instancetype)initWithUIImage:(UIImage*)image; +-(instancetype)initWithUIImage:(UIImage*)image alphaExist:(BOOL)alphaExist; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/imgcodecs/misc/objc/ios/Mat+Converters.mm b/modules/imgcodecs/misc/objc/ios/Mat+Converters.mm new file mode 100644 index 0000000000..3ea3117267 --- /dev/null +++ b/modules/imgcodecs/misc/objc/ios/Mat+Converters.mm @@ -0,0 +1,28 @@ +// +// Mat+UIImage.mm +// +// Created by Giles Payne on 2020/03/03. +// + +#import "Mat+Converters.h" +#import + +@implementation Mat (Converters) + +-(UIImage*)toUIImage { + return MatToUIImage(self.nativeRef); +} + +-(instancetype)initWithUIImage:(UIImage*)image { + return [self initWithUIImage:image alphaExist:NO]; +} + +-(instancetype)initWithUIImage:(UIImage*)image alphaExist:(BOOL)alphaExist { + self = [self init]; + if (self) { + UIImageToMat(image, self.nativeRef, (bool)alphaExist); + } + return self; +} + +@end diff --git a/modules/imgcodecs/misc/objc/test/ImgcodecsTest.swift b/modules/imgcodecs/misc/objc/test/ImgcodecsTest.swift new file mode 100644 index 0000000000..857c95ed74 --- /dev/null +++ b/modules/imgcodecs/misc/objc/test/ImgcodecsTest.swift @@ -0,0 +1,50 @@ +// +// Imgcodecs.swift +// +// Created by Giles Payne on 2020/02/10. +// + +import XCTest +import OpenCV + +class ImgcodecsTest: OpenCVTestCase { + + let LENA_PATH = Bundle(for: ImgcodecsTest.self).path(forResource:"lena", ofType:"png", inDirectory:"resources")! + + func testImencodeStringMatListOfByte() { + let buff = ByteVector() + XCTAssert(Imgcodecs.imencode(ext: ".jpg", img: gray127, buf: buff)) + XCTAssertFalse(0 == buff.length) + } + + func testImencodeStringMatListOfByteListOfInteger() { + let params40 = IntVector([ImwriteFlags.IMWRITE_JPEG_QUALITY.rawValue, 40]) + let params90 = IntVector([ImwriteFlags.IMWRITE_JPEG_QUALITY.rawValue, 90]) + + let buff40 = ByteVector() + let buff90 = ByteVector() + + XCTAssert(Imgcodecs.imencode(ext: ".jpg", img: rgbLena, buf: buff40, params: params40)) + XCTAssert(Imgcodecs.imencode(ext: ".jpg", img: rgbLena, buf: buff90, params: params90)) + + XCTAssert(buff40.length > 0) + XCTAssert(buff40.length < buff90.length) + } + + func testImreadString() { + dst = Imgcodecs.imread(filename: LENA_PATH) + XCTAssertFalse(dst.empty()) + XCTAssertEqual(3, dst.channels()) + XCTAssert(512 == dst.cols()) + XCTAssert(512 == dst.rows()) + } + + func testImreadStringInt() { + dst = Imgcodecs.imread(filename: LENA_PATH, flags: 0) + XCTAssertFalse(dst.empty()); + XCTAssertEqual(1, dst.channels()); + XCTAssert(512 == dst.cols()); + XCTAssert(512 == dst.rows()); + } + +} diff --git a/modules/imgproc/CMakeLists.txt b/modules/imgproc/CMakeLists.txt index 0886c6991e..8ee300c320 100644 --- a/modules/imgproc/CMakeLists.txt +++ b/modules/imgproc/CMakeLists.txt @@ -10,7 +10,7 @@ ocv_add_dispatched_file(median_blur SSE2 SSE4_1 AVX2) ocv_add_dispatched_file(morph SSE2 SSE4_1 AVX2) ocv_add_dispatched_file(smooth SSE2 SSE4_1 AVX2) ocv_add_dispatched_file(sumpixels SSE2 AVX2 AVX512_SKX) -ocv_define_module(imgproc opencv_core WRAP java python js) +ocv_define_module(imgproc opencv_core WRAP java objc python js) ocv_check_environment_variables(OPENCV_IPP_GAUSSIAN_BLUR) option(OPENCV_IPP_GAUSSIAN_BLUR "Enable IPP optimizations for GaussianBlur (+8Mb in binary size)" OFF) diff --git a/modules/imgproc/misc/objc/common/Moments.h b/modules/imgproc/misc/objc/common/Moments.h new file mode 100644 index 0000000000..cf64625403 --- /dev/null +++ b/modules/imgproc/misc/objc/common/Moments.h @@ -0,0 +1,66 @@ +// +// Moments.h +// +// Created by Giles Payne on 2019/10/06. +// + +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface Moments : NSObject + +@property double m00; +@property double m10; +@property double m01; +@property double m20; +@property double m11; +@property double m02; +@property double m30; +@property double m21; +@property double m12; +@property double m03; + +@property double mu20; +@property double mu11; +@property double mu02; +@property double mu30; +@property double mu21; +@property double mu12; +@property double mu03; + +@property double nu20; +@property double nu11; +@property double nu02; +@property double nu30; +@property double nu21; +@property double nu12; +@property double nu03; + +#ifdef __cplusplus +@property(readonly) cv::Moments& nativeRef; +#endif + +-(instancetype)initWithM00:(double)m00 m10:(double)m10 m01:(double)m01 m20:(double)m20 m11:(double)m11 m02:(double)m02 m30:(double)m30 m21:(double)m21 m12:(double)m12 m03:(double)m03; + +-(instancetype)init; + +-(instancetype)initWithVals:(NSArray*)vals; + +#ifdef __cplusplus ++(instancetype)fromNative:(cv::Moments&)moments; +#endif + +-(void)set:(NSArray*)vals; +-(void)completeState; +-(NSString *)description; + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/imgproc/misc/objc/common/Moments.mm b/modules/imgproc/misc/objc/common/Moments.mm new file mode 100644 index 0000000000..20fa47dbd4 --- /dev/null +++ b/modules/imgproc/misc/objc/common/Moments.mm @@ -0,0 +1,304 @@ +// +// Moments.mm +// +// Created by Giles Payne on 2019/10/09. +// + +#import "Moments.h" + +@implementation Moments { + cv::Moments native; +} + +-(cv::Moments&)nativeRef { + return native; +} + +- (double)m00 { + return native.m00; +} + +- (void)setM00:(double)val { + native.m00 = val; +} + +- (double)m10 { + return native.m10; +} + +- (void)setM10:(double)val { + native.m10 = val; +} + +- (double)m01 { + return native.m01; +} + +- (void)setM01:(double)val { + native.m01 = val; +} + +- (double)m20 { + return native.m20; +} + +- (void)setM20:(double)val { + native.m20 = val; +} + +- (double)m11 { + return native.m11; +} + +- (void)setM11:(double)val { + native.m11 = val; +} + +- (double)m02 { + return native.m02; +} + +- (void)setM02:(double)val { + native.m02 = val; +} + +- (double)m30 { + return native.m30; +} + +- (void)setM30:(double)val { + native.m30 = val; +} + +- (double)m21 { + return native.m21; +} + +- (void)setM21:(double)val { + native.m21 = val; +} + +- (double)m12 { + return native.m12; +} + +- (void)setM12:(double)val { + native.m12 = val; +} + +- (double)m03 { + return native.m03; +} + +- (void)setM03:(double)val { + native.m03 = val; +} + +- (double)mu20 { + return native.mu20; +} + +- (void)setMu20:(double)val { + native.mu20 = val; +} + +- (double)mu11 { + return native.mu11; +} + +- (void)setMu11:(double)val { + native.mu11 = val; +} + +- (double)mu02 { + return native.mu02; +} + +- (void)setMu02:(double)val { + native.mu02 = val; +} + +- (double)mu30 { + return native.mu30; +} + +- (void)setMu30:(double)val { + native.mu30 = val; +} + +- (double)mu21 { + return native.mu21; +} + +- (void)setMu21:(double)val { + native.mu21 = val; +} +- (double)mu12 { + return native.mu12; +} + +- (void)setMu12:(double)val { + native.mu12 = val; +} + +- (double)mu03 { + return native.mu03; +} + +- (void)setMu03:(double)val { + native.mu03 = val; +} + +- (double)nu20 { + return native.nu20; +} + +- (void)setNu20:(double)val { + native.nu20 = val; +} + +- (double)nu11 { + return native.nu11; +} + +- (void)setNu11:(double)val { + native.nu11 = val; +} + +- (double)nu02 { + return native.nu02; +} + +- (void)setNu02:(double)val { + native.nu02 = val; +} + +- (double)nu30 { + return native.nu30; +} + +- (void)setNu30:(double)val { + native.nu30 = val; +} + +- (double)nu21 { + return native.nu21; +} + +- (void)setNu21:(double)val { + native.nu21 = val; +} + +- (double)nu12 { + return native.nu12; +} + +- (void)setNu12:(double)val { + native.nu12 = val; +} + +- (double)nu03 { + return native.nu03; +} + +- (void)setNu03:(double)val { + native.nu03 = val; +} + +-(instancetype)initWithM00:(double)m00 m10:(double)m10 m01:(double)m01 m20:(double)m20 m11:(double)m11 m02:(double)m02 m30:(double)m30 m21:(double)m21 m12:(double)m12 m03:(double)m03 { + self = [super init]; + if (self) { + self.m00 = m00; + self.m10 = m10; + self.m01 = m01; + self.m20 = m20; + self.m11 = m11; + self.m02 = m02; + self.m30 = m30; + self.m21 = m21; + self.m12 = m12; + self.m03 = m03; + [self completeState]; + } + return self; +} +-(instancetype)init { + return [self initWithM00:0 m10:0 m01:0 m20:0 m11:0 m02:0 m30:0 m21:0 m12:0 m03:0]; +} + +-(instancetype)initWithVals:(NSArray*)vals { + self = [super init]; + if (self) { + [self set:vals]; + } + return self; +} + ++(instancetype)fromNative:(cv::Moments&)moments { + return [[Moments alloc] initWithM00:moments.m00 m10:moments.m10 m01:moments.m01 m20:moments.m20 m11:moments.m11 m02:moments.m02 m30:moments.m30 m21:moments.m21 m12:moments.m12 m03:moments.m03]; +} + +-(void)set:(NSArray*)vals { + self.m00 = (vals != nil && vals.count > 0) ? vals[0].doubleValue : 0; + self.m10 = (vals != nil && vals.count > 1) ? vals[1].doubleValue : 0; + self.m01 = (vals != nil && vals.count > 2) ? vals[2].doubleValue : 0; + self.m20 = (vals != nil && vals.count > 3) ? vals[3].doubleValue : 0; + self.m11 = (vals != nil && vals.count > 4) ? vals[4].doubleValue : 0; + self.m02 = (vals != nil && vals.count > 5) ? vals[5].doubleValue : 0; + self.m30 = (vals != nil && vals.count > 6) ? vals[6].doubleValue : 0; + self.m21 = (vals != nil && vals.count > 7) ? vals[7].doubleValue : 0; + self.m12 = (vals != nil && vals.count > 8) ? vals[8].doubleValue : 0; + self.m03 = (vals != nil && vals.count > 9) ? vals[9].doubleValue : 0; + [self completeState]; +} + +-(void)completeState { + double cx = 0, cy = 0; + double mu20, mu11, mu02; + double inv_m00 = 0.0; + + if (abs(self.m00) > 0.00000001) { + inv_m00 = 1. / self.m00; + cx = self.m10 * inv_m00; + cy = self.m01 * inv_m00; + } + + // mu20 = m20 - m10*cx + mu20 = self.m20 - self.m10 * cx; + // mu11 = m11 - m10*cy + mu11 = self.m11 - self.m10 * cy; + // mu02 = m02 - m01*cy + mu02 = self.m02 - self.m01 * cy; + + self.mu20 = mu20; + self.mu11 = mu11; + self.mu02 = mu02; + + // mu30 = m30 - cx*(3*mu20 + cx*m10) + self.mu30 = self.m30 - cx * (3 * mu20 + cx * self.m10); + mu11 += mu11; + // mu21 = m21 - cx*(2*mu11 + cx*m01) - cy*mu20 + self.mu21 = self.m21 - cx * (mu11 + cx * self.m01) - cy * mu20; + // mu12 = m12 - cy*(2*mu11 + cy*m10) - cx*mu02 + self.mu12 = self.m12 - cy * (mu11 + cy * self.m10) - cx * mu02; + // mu03 = m03 - cy*(3*mu02 + cy*m01) + self.mu03 = self.m03 - cy * (3 * mu02 + cy * self.m01); + + + double inv_sqrt_m00 = sqrt(abs(inv_m00)); + double s2 = inv_m00*inv_m00, s3 = s2*inv_sqrt_m00; + + self.nu20 = self.mu20*s2; + self.nu11 = self.mu11*s2; + self.nu02 = self.mu02*s2; + self.nu30 = self.mu30*s3; + self.nu21 = self.mu21*s3; + self.nu12 = self.mu12*s3; + self.nu03 = self.mu03*s3; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"Moments [ \nm00=%lf, \nm10=%lf, m01=%lf, \nm20=%lf, m11=%lf, m02=%lf, \nm30=%lf, m21=%lf, m12=%lf, m03=%lf, \nmu20=%lf, mu11=%lf, mu02=%lf, \nmu30=%lf, mu21=%lf, mu12=%lf, mu03=%lf, \nnu20=%lf, nu11=%lf, nu02=%lf, \nnu30=%lf, nu21=%lf, nu12=%lf, nu03=%lf, \n]", self.m00, self.m10, self.m01, self.m20, self.m11, self.m02, self.m30, self.m21, self.m12, self.m03, self.mu20, self.mu11, self.mu02, self.mu30, self.mu21, self.mu12, self.mu03, self.nu20, self.nu11, self.nu02, self.nu30, self.nu21, self.nu12, self.nu03]; +} + +@end diff --git a/modules/imgproc/misc/objc/gen_dict.json b/modules/imgproc/misc/objc/gen_dict.json new file mode 100644 index 0000000000..87614924a1 --- /dev/null +++ b/modules/imgproc/misc/objc/gen_dict.json @@ -0,0 +1,121 @@ +{ + "module_imports": ["Size2i"], + "const_ignore_list": [ + "CV_TM_.+", + "CV_COLORCVT_MAX", + "CV_.*Bayer.*", + "CV_YUV420(i|sp|p)2.+", + "CV_L?(BGRA?|RGBA?|GRAY|XYZ|YCrCb|Luv|Lab|HLS|YUV|HSV)\\d*2L?(BGRA?|RGBA?|GRAY|XYZ|YCrCb|Luv|Lab|HLS|YUV|HSV).*", + "CV_FLOODFILL_.+", + "CV_ADAPTIVE_THRESH_.+", + "CV_DIST_.+", + "CV_HOUGH_.+", + "CV_CONTOURS_MATCH_.+", + "CV_COMP_.+" + ], + "const_private_list" : [ + "CV_MOP_.+", + "CV_INTER_.+", + "CV_THRESH_.+", + "CV_INPAINT_.+", + "CV_RETR_.+", + "CV_CHAIN_APPROX_.+" + ], + "missing_consts" : { + "Imgproc" : { + "private" : [ + ["IPL_BORDER_CONSTANT", 0 ], + ["IPL_BORDER_REPLICATE", 1 ], + ["IPL_BORDER_REFLECT", 2 ], + ["IPL_BORDER_WRAP", 3 ], + ["IPL_BORDER_REFLECT_101", 4 ], + ["IPL_BORDER_TRANSPARENT", 5 ] + ] + } + }, + "func_arg_fix" : { + "goodFeaturesToTrack" : { "corners" : {"ctype" : "vector_Point"} }, + "minEnclosingCircle" : { "points" : {"ctype" : "vector_Point2f"} }, + "fitEllipse" : { "points" : {"ctype" : "vector_Point2f"} }, + "fillPoly" : { "pts" : {"ctype" : "vector_vector_Point"}, + "lineType" : {"ctype" : "LineTypes"}}, + "polylines" : { "pts" : {"ctype" : "vector_vector_Point"}, + "lineType" : {"ctype" : "LineTypes"} }, + "fillConvexPoly" : { "points" : {"ctype" : "vector_Point"}, + "lineType" : {"ctype" : "LineTypes"} }, + "approxPolyDP" : { "curve" : {"ctype" : "vector_Point2f"}, + "approxCurve" : {"ctype" : "vector_Point2f"} }, + "arcLength" : { "curve" : {"ctype" : "vector_Point2f"} }, + "pointPolygonTest" : { "contour" : {"ctype" : "vector_Point2f"} }, + "minAreaRect" : { "points" : {"ctype" : "vector_Point2f"} }, + "getAffineTransform" : { "src" : {"ctype" : "vector_Point2f"}, + "dst" : {"ctype" : "vector_Point2f"} }, + "drawContours" : { "contours" : {"ctype" : "vector_vector_Point"}, + "lineType" : {"ctype" : "LineTypes"} }, + "findContours" : { "contours" : {"ctype" : "vector_vector_Point"}, + "mode" : {"ctype" : "RetrievalModes"}, + "method" : {"ctype" : "ContourApproximationModes"} }, + "convexityDefects" : { "contour" : {"ctype" : "vector_Point"}, + "convexhull" : {"ctype" : "vector_int"}, + "convexityDefects" : {"ctype" : "vector_Vec4i"} }, + "isContourConvex" : { "contour" : {"ctype" : "vector_Point"} }, + "convexHull" : { "points" : {"ctype" : "vector_Point"}, + "hull" : {"ctype" : "vector_int"}, + "returnPoints" : {"ctype" : ""} }, + "getStructuringElement" : { "shape" : {"ctype" : "MorphShapes"} }, + "EMD" : {"lowerBound" : {"defval" : "cv::Ptr()"}, + "distType" : {"ctype" : "DistanceTypes"}}, + "createLineSegmentDetector" : { "_refine" : {"ctype" : "LineSegmentDetectorModes"}}, + "compareHist" : { "method" : {"ctype" : "HistCompMethods"}}, + "matchShapes" : { "method" : {"ctype" : "ShapeMatchModes"}}, + "threshold" : { "type" : {"ctype" : "ThresholdTypes"}}, + "connectedComponentsWithStatsWithAlgorithm" : { "ccltype" : {"ctype" : "ConnectedComponentsAlgorithmsTypes"}}, + "GaussianBlur" : { "borderType" : {"ctype" : "BorderTypes"}}, + "HoughCircles" : { "method" : {"ctype" : "HoughModes"}}, + "Laplacian" : { "borderType" : {"ctype" : "BorderTypes"}}, + "Scharr" : { "borderType" : {"ctype" : "BorderTypes"}}, + "Sobel" : { "borderType" : {"ctype" : "BorderTypes"}}, + "adaptiveThreshold" : { "adaptiveMethod" : {"ctype" : "AdaptiveThresholdTypes"}, + "thresholdType" : {"ctype" : "ThresholdTypes"}}, + "applyColorMap" : { "colormap" : {"ctype" : "ColormapTypes"}}, + "arrowedLine" : { "line_type" : {"ctype" : "LineTypes"}}, + "bilateralFilter" : { "borderType" : {"ctype" : "BorderTypes"}}, + "blur" : { "borderType" : {"ctype" : "BorderTypes"}}, + "boxFilter" : { "borderType" : {"ctype" : "BorderTypes"}}, + "circle" : { "lineType" : {"ctype" : "LineTypes"}}, + "cornerEigenValsAndVecs" : { "borderType" : {"ctype" : "BorderTypes"}}, + "cornerHarris" : { "borderType" : {"ctype" : "BorderTypes"}}, + "cornerMinEigenVal" : { "borderType" : {"ctype" : "BorderTypes"}}, + "cvtColor" : { "code" : {"ctype" : "ColorConversionCodes"}}, + "dilate" : { "borderType" : {"ctype" : "BorderTypes"}}, + "distanceTransformWithLabels" : { "labelType" : {"ctype" : "DistanceTransformLabelTypes"}, + "distanceType" : {"ctype" : "DistanceTypes"}, + "maskSize" : {"ctype" : "DistanceTransformMasks"}}, + "distanceTransform" : { "distanceType" : {"ctype" : "DistanceTypes"}, + "maskSize" : {"ctype" : "DistanceTransformMasks"}}, + "drawMarker" : { "markerType" : {"ctype" : "MarkerTypes"}, + "line_type" : {"ctype" : "LineTypes"}}, + "ellipse" : { "lineType" : {"ctype" : "LineTypes"}}, + "erode" : { "borderType" : {"ctype" : "BorderTypes"}}, + "filter2D" : { "borderType" : {"ctype" : "BorderTypes"}}, + "fitLine" : { "distType" : {"ctype" : "DistanceTypes"}}, + "line" : { "lineType" : {"ctype" : "LineTypes"}}, + "matchTemplate" : { "method" : {"ctype" : "TemplateMatchModes"}}, + "morphologyEx" : { "op" : {"ctype" : "MorphTypes"}, + "borderType" : {"ctype" : "BorderTypes"}}, + "preCornerDetect" : { "borderType" : {"ctype" : "BorderTypes"}}, + "putText" : { "fontFace" : {"ctype" : "HersheyFonts"}, + "lineType" : {"ctype" : "LineTypes"}}, + "pyrDown" : { "borderType" : {"ctype" : "BorderTypes"}}, + "pyrUp" : { "borderType" : {"ctype" : "BorderTypes"}}, + "rectangle" : { "lineType" : {"ctype" : "LineTypes"}}, + "remap" : { "borderMode": {"ctype" : "BorderTypes"}}, + "sepFilter2D" : { "borderType" : {"ctype" : "BorderTypes"}}, + "spatialGradient" : { "borderType" : {"ctype" : "BorderTypes"}}, + "sqrBoxFilter" : { "borderType" : {"ctype" : "BorderTypes"}}, + "warpAffine" : { "borderMode": {"ctype" : "BorderTypes"}}, + "warpPerspective" : { "borderMode": {"ctype" : "BorderTypes"}}, + "getTextSize" : { "fontFace": {"ctype" : "HersheyFonts"}}, + "(void)insert:(NSArray*)ptvec" : { "insert" : {"name" : "insertVector"} } + } +} diff --git a/modules/imgproc/misc/objc/test/ImgprocTest.swift b/modules/imgproc/misc/objc/test/ImgprocTest.swift new file mode 100644 index 0000000000..f9f89f3c88 --- /dev/null +++ b/modules/imgproc/misc/objc/test/ImgprocTest.swift @@ -0,0 +1,1743 @@ +// +// ImgprocTest.swift +// +// Created by Giles Payne on 2020/02/08. +// + +import XCTest +import OpenCV + +class ImgprocTest: OpenCVTestCase { + + let anchorPoint = Point(x: 2, y: 2) + let imgprocSz: Int32 = 2 + let size = Size(width: 3, height: 3) + + func testAccumulateMatMat() throws { + let src = getMat(CvType.CV_64F, vals: [2]) + let dst = getMat(CvType.CV_64F, vals: [0]) + let dst2 = src.clone() + + Imgproc.accumulate(src: src, dst: dst) + Imgproc.accumulate(src: src, dst: dst2) + + try assertMatEqual(src, dst, OpenCVTestCase.EPS) + try assertMatEqual(getMat(CvType.CV_64F, vals: [4]), dst2, OpenCVTestCase.EPS) + } + + func testAccumulateMatMatMat() throws { + let src = getMat(CvType.CV_64F, vals: [2]) + let mask = makeMask(getMat(CvType.CV_8U, vals: [1])) + let dst = getMat(CvType.CV_64F, vals: [0]) + let dst2 = src.clone() + + Imgproc.accumulate(src: src, dst: dst, mask: mask) + Imgproc.accumulate(src: src, dst: dst2, mask: mask) + + try assertMatEqual(makeMask(getMat(CvType.CV_64F, vals: [2])), dst, OpenCVTestCase.EPS) + try assertMatEqual(makeMask(getMat(CvType.CV_64F, vals: [4]), vals: [2]), dst2, OpenCVTestCase.EPS) + } + + func testAccumulateProductMatMatMat() throws { + let src = getMat(CvType.CV_64F, vals: [2]) + let dst = getMat(CvType.CV_64F, vals: [0]) + let dst2 = src.clone() + + Imgproc.accumulateProduct(src1: src, src2: src, dst: dst) + Imgproc.accumulateProduct(src1: src, src2: dst, dst: dst2) + + try assertMatEqual(getMat(CvType.CV_64F, vals:[4]), dst, OpenCVTestCase.EPS) + try assertMatEqual(getMat(CvType.CV_64F, vals:[10]), dst2, OpenCVTestCase.EPS) + } + + func testAccumulateProductMatMatMatMat() throws { + let src = getMat(CvType.CV_64F, vals: [2]) + let mask = makeMask(getMat(CvType.CV_8U, vals: [1])) + let dst = getMat(CvType.CV_64F, vals: [0]) + let dst2 = src.clone() + + Imgproc.accumulateProduct(src1: src, src2: src, dst: dst, mask: mask) + Imgproc.accumulateProduct(src1: src, src2: dst, dst: dst2, mask: mask) + + try assertMatEqual(makeMask(getMat(CvType.CV_64F, vals: [4])), dst, OpenCVTestCase.EPS) + try assertMatEqual(makeMask(getMat(CvType.CV_64F, vals: [10]), vals:[2]), dst2, OpenCVTestCase.EPS) + } + + func testAccumulateSquareMatMat() throws { + let src = getMat(CvType.CV_64F, vals: [2]) + let dst = getMat(CvType.CV_64F, vals: [0]) + let dst2 = src.clone() + + Imgproc.accumulateSquare(src: src, dst: dst) + Imgproc.accumulateSquare(src: src, dst: dst2) + + try assertMatEqual(getMat(CvType.CV_64F, vals: [4]), dst, OpenCVTestCase.EPS) + try assertMatEqual(getMat(CvType.CV_64F, vals: [6]), dst2, OpenCVTestCase.EPS) + } + + func testAccumulateSquareMatMatMat() throws { + let src = getMat(CvType.CV_64F, vals: [2]) + let mask = makeMask(getMat(CvType.CV_8U, vals: [1])) + let dst = getMat(CvType.CV_64F, vals: [0]) + let dst2 = src.clone() + + Imgproc.accumulateSquare(src: src, dst: dst, mask: mask) + Imgproc.accumulateSquare(src: src, dst: dst2, mask: mask) + + try assertMatEqual(makeMask(getMat(CvType.CV_64F, vals: [4])), dst, OpenCVTestCase.EPS) + try assertMatEqual(makeMask(getMat(CvType.CV_64F, vals: [6]), vals: [2]), dst2, OpenCVTestCase.EPS) + } + + func testAccumulateWeightedMatMatDouble() throws { + let src = getMat(CvType.CV_64F, vals: [2]) + let dst = getMat(CvType.CV_64F, vals: [4]) + let dst2 = src.clone() + + Imgproc.accumulateWeighted(src: src, dst: dst, alpha: 0.5) + Imgproc.accumulateWeighted(src: src, dst: dst2, alpha: 2) + + try assertMatEqual(getMat(CvType.CV_64F, vals: [3]), dst, OpenCVTestCase.EPS) + try assertMatEqual(getMat(CvType.CV_64F, vals: [2]), dst2, OpenCVTestCase.EPS) + } + + func testAccumulateWeightedMatMatDoubleMat() throws { + let src = getMat(CvType.CV_64F, vals: [2]) + let mask = makeMask(getMat(CvType.CV_8U, vals: [1])) + let dst = getMat(CvType.CV_64F, vals: [4]) + let dst2 = src.clone() + + Imgproc.accumulateWeighted(src: src, dst: dst, alpha: 0.5, mask: mask) + Imgproc.accumulateWeighted(src: src, dst: dst2, alpha: 2, mask: mask) + + try assertMatEqual(makeMask(getMat(CvType.CV_64F, vals: [3]), vals: [4]), dst, OpenCVTestCase.EPS) + try assertMatEqual(getMat(CvType.CV_64F, vals: [2]), dst2, OpenCVTestCase.EPS) + } + + func testAdaptiveThreshold() { + let src = makeMask(getMat(CvType.CV_8U, vals: [50]), vals:[20]) + let dst = Mat() + + Imgproc.adaptiveThreshold(src: src, dst: dst, maxValue: 1, adaptiveMethod: .ADAPTIVE_THRESH_MEAN_C, thresholdType: .THRESH_BINARY, blockSize: 3, C: 0) + + XCTAssertEqual(src.rows(), Core.countNonZero(src: dst)) + } + + func testApproxPolyDP() { + let curve = [Point2f(x: 1, y: 3), Point2f(x: 2, y: 4), Point2f(x: 3, y: 5), Point2f(x: 4, y: 4), Point2f(x: 5, y: 3)] + + let approxCurve = NSMutableArray() + + Imgproc.approxPolyDP(curve: curve, approxCurve: approxCurve, epsilon: OpenCVTestCase.EPS, closed: true) + + let approxCurveGold = [Point2f(x: 1, y: 3), Point2f(x: 3, y: 5), Point2f(x: 5, y: 3)] + + XCTAssert(approxCurve as! [Point2f] == approxCurveGold) + } + + func testArcLength() { + let curve = [Point2f(x: 1, y: 3), Point2f(x: 2, y: 4), Point2f(x: 3, y: 5), Point2f(x: 4, y: 4), Point2f(x: 5, y: 3)] + + let arcLength = Imgproc.arcLength(curve: curve, closed: false) + + XCTAssertEqual(5.656854249, arcLength, accuracy:0.000001) + } + + func testBilateralFilterMatMatIntDoubleDouble() throws { + Imgproc.bilateralFilter(src: gray255, dst: dst, d: 5, sigmaColor: 10, sigmaSpace: 5) + + try assertMatEqual(gray255, dst) + } + + func testBilateralFilterMatMatIntDoubleDoubleInt() throws { + Imgproc.bilateralFilter(src: gray255, dst: dst, d: 5, sigmaColor: 10, sigmaSpace: 5, borderType: .BORDER_REFLECT) + + try assertMatEqual(gray255, dst) + } + + func testBlurMatMatSize() throws { + Imgproc.blur(src: gray0, dst: dst, ksize: size) + try assertMatEqual(gray0, dst) + + Imgproc.blur(src: gray255, dst: dst, ksize: size) + try assertMatEqual(gray255, dst) + } + + func testBlurMatMatSizePoint() throws { + Imgproc.blur(src: gray0, dst: dst, ksize: size, anchor: anchorPoint) + try assertMatEqual(gray0, dst) + } + + func testBlurMatMatSizePointInt() throws { + Imgproc.blur(src: gray0, dst: dst, ksize: size, anchor: anchorPoint, borderType: .BORDER_REFLECT) + try assertMatEqual(gray0, dst) + } + + func testBoundingRect() { + let points = [Point(x: 0, y: 0), Point(x: 0, y: 4), Point(x: 4, y: 0), Point(x: 4, y: 4)] + let p1 = Point(x: 1, y: 1) + let p2 = Point(x: -5, y: -2) + + let bbox = Imgproc.boundingRect(array: MatOfPoint(array: points)) + + XCTAssert(bbox.contains(p1)) + XCTAssertFalse(bbox.contains(p2)) + } + + func testBoxFilterMatMatIntSize() throws { + let size = Size(width: 3, height: 3) + Imgproc.boxFilter(src: gray0, dst: dst, ddepth: 8, ksize: size) + try assertMatEqual(gray0, dst) + } + + func testBoxFilterMatMatIntSizePointBoolean() throws { + Imgproc.boxFilter(src: gray255, dst: dst, ddepth: 8, ksize: size, anchor: anchorPoint, normalize: false) + try assertMatEqual(gray255, dst) + } + + func testBoxFilterMatMatIntSizePointBooleanInt() throws { + Imgproc.boxFilter(src: gray255, dst: dst, ddepth: 8, ksize: size, anchor: anchorPoint, normalize: false, borderType: .BORDER_REFLECT) + try assertMatEqual(gray255, dst) + } + + func testCalcBackProject() { + let images = [grayChess] + let channels = IntVector([0]) + let histSize = IntVector([10]) + let ranges = FloatVector([0, 256]) + + let hist = Mat() + Imgproc.calcHist(images: images, channels: channels, mask: Mat(), hist: hist, histSize: histSize, ranges: ranges) + Core.normalize(src: hist, dst: hist) + + Imgproc.calcBackProject(images: images, channels: channels, hist: hist, dst: dst, ranges: ranges, scale: 255) + + XCTAssertEqual(grayChess.size(), dst.size()) + XCTAssertEqual(grayChess.depth(), dst.depth()) + XCTAssertFalse(0 == Core.countNonZero(src: dst)) + } + + func testCalcHistListOfMatListOfIntegerMatMatListOfIntegerListOfFloat() throws { + let images = [gray128] + let channels = IntVector([0]) + let histSize = IntVector([10]) + let ranges = FloatVector([0, 256]) + let hist = Mat() + + Imgproc.calcHist(images: images, channels: channels, mask: Mat(), hist: hist, histSize: histSize, ranges: ranges) + + truth = Mat(rows: 10, cols: 1, type: CvType.CV_32F, scalar: Scalar.all(0)) + try truth!.put(row: 5, col: 0, data: [100] as [Float]) + try assertMatEqual(truth!, hist, OpenCVTestCase.EPS) + } + + func testCalcHistListOfMatListOfIntegerMatMatListOfIntegerListOfFloat2D() throws { + let images = [gray255, gray128] + let channels = IntVector([0, 1]) + let histSize = IntVector([10, 10]) + let ranges = FloatVector([0, 256, 0, 256]) + let hist = Mat() + + Imgproc.calcHist(images: images, channels: channels, mask: Mat(), hist: hist, histSize: histSize, ranges: ranges) + + truth = Mat(rows: 10, cols: 10, type: CvType.CV_32F, scalar: Scalar.all(0)) + try truth!.put(row: 9, col: 5, data: [100] as [Float]) + try assertMatEqual(truth!, hist, OpenCVTestCase.EPS) + } + + func testCalcHistListOfMatListOfIntegerMatMatListOfIntegerListOfFloat3D() throws { + let images = [rgbLena] + + let hist3D = Mat() + let histList = [Mat(), Mat(), Mat()] + + let histSize = IntVector([10]) + let ranges = FloatVector([0, 256]) + + for i:Int in 0.. 0) + + Imgproc.polylines(img: img, pts: polyline2, isClosed: true, color: Scalar(0), thickness: 2, lineType: .LINE_8, shift: 1) + + XCTAssertEqual(0, Core.countNonZero(src: img)) + } + + func testPutTextMatStringPointIntDoubleScalar() { + let text = "Hello World" + let labelSize = Size(width: 175, height: 22) + let img = Mat(rows: 20 + labelSize.height, cols: 20 + labelSize.width, type: CvType.CV_8U, scalar: colorBlack) + let origin = Point(x: 10, y: labelSize.height + 10) + + Imgproc.putText(img: img, text: text, org: origin, fontFace: .FONT_HERSHEY_SIMPLEX, fontScale: 1.0, color: colorWhite) + + XCTAssert(Core.countNonZero(src: img) > 0) + // check that border is not corrupted + Imgproc.rectangle(img: img, pt1: Point(x: 11, y: 11), pt2: Point(x: labelSize.width + 10, y: labelSize.height + 10), color: colorBlack, thickness: Core.FILLED) + XCTAssertEqual(0, Core.countNonZero(src: img)) + } + + func testPutTextMatStringPointIntDoubleScalarInt() { + let text = "Hello World" + let labelSize = Size(width: 176, height: 22) + let img = Mat(rows: 20 + labelSize.height, cols: 20 + labelSize.width, type: CvType.CV_8U, scalar: colorBlack) + let origin = Point(x: 10, y: labelSize.height + 10) + + Imgproc.putText(img: img, text: text, org: origin, fontFace: .FONT_HERSHEY_SIMPLEX, fontScale: 1.0, color: colorWhite, thickness: 2) + + XCTAssert(Core.countNonZero(src: img) > 0) + // check that border is not corrupted + Imgproc.rectangle(img: img, pt1: Point(x: 10, y: 10), pt2: Point(x: labelSize.width + 10 + 1, y: labelSize.height + 10 + 1), color: colorBlack, thickness: Core.FILLED) + XCTAssertEqual(0, Core.countNonZero(src: img)) + } + + func testPutTextMatStringPointIntDoubleScalarIntIntBoolean() { + let text = "Hello World" + let labelSize = Size(width: 175, height: 22) + + let img = Mat(rows: 20 + labelSize.height, cols: 20 + labelSize.width, type: CvType.CV_8U, scalar: colorBlack) + let origin = Point(x: 10, y: 10) + + Imgproc.putText(img: img, text: text, org: origin, fontFace: .FONT_HERSHEY_SIMPLEX, fontScale: 1.0, color: colorWhite, thickness: 1, lineType: .LINE_8, bottomLeftOrigin: true) + + XCTAssert(Core.countNonZero(src: img) > 0) + // check that border is not corrupted + Imgproc.rectangle(img: img, pt1: Point(x: 10, y: 10), pt2: Point(x: labelSize.width + 9, y: labelSize.height + 9), color: colorBlack, thickness: Core.FILLED) + XCTAssertEqual(0, Core.countNonZero(src: img)) + } + +} diff --git a/modules/imgproc/misc/objc/test/MomentsTest.swift b/modules/imgproc/misc/objc/test/MomentsTest.swift new file mode 100644 index 0000000000..01902f536f --- /dev/null +++ b/modules/imgproc/misc/objc/test/MomentsTest.swift @@ -0,0 +1,42 @@ +// +// MomentsTest.swift +// +// Created by Giles Payne on 2020/02/10. +// + +import XCTest +import OpenCV + +class MomentsTest: XCTestCase { + + func testAll() { + let data = Mat(rows: 3,cols: 3, type: CvType.CV_8UC1, scalar: Scalar(1)) + data.row(1).setTo(scalar: Scalar(5)) + let res = Imgproc.moments(array: data) + XCTAssertEqual(res.m00, 21.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.m10, 21.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.m01, 21.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.m20, 35.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.m11, 21.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.m02, 27.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.m30, 63.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.m21, 35.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.m12, 27.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.m03, 39.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.mu20, 14.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.mu11, 0.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.mu02, 6.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.mu30, 0.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.mu21, 0.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.mu12, 0.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.mu03, 0.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.nu20, 0.031746031746031744, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.nu11, 0.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.nu02, 0.013605442176870746, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.nu30, 0.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.nu21, 0.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.nu12, 0.0, accuracy: OpenCVTestCase.EPS); + XCTAssertEqual(res.nu03, 0.0, accuracy: OpenCVTestCase.EPS); + } + +} diff --git a/modules/imgproc/misc/objc/test/Subdiv2DTest.swift b/modules/imgproc/misc/objc/test/Subdiv2DTest.swift new file mode 100644 index 0000000000..0a57fa363b --- /dev/null +++ b/modules/imgproc/misc/objc/test/Subdiv2DTest.swift @@ -0,0 +1,23 @@ +// +// Subdiv2DTest.swift +// +// Created by Giles Payne on 2020/02/10. +// + +import XCTest +import OpenCV + +class Subdiv2DTest: OpenCVTestCase { + + func testGetTriangleList() { + let s2d = Subdiv2D(rect: Rect(x: 0, y: 0, width: 50, height: 50)) + s2d.insert(pt: Point2f(x: 10, y: 10)) + s2d.insert(pt: Point2f(x: 20, y: 10)) + s2d.insert(pt: Point2f(x: 20, y: 20)) + s2d.insert(pt: Point2f(x: 10, y: 20)) + let triangles = NSMutableArray() + s2d.getTriangleList(triangleList: triangles) + XCTAssertEqual(2, triangles.count) + } + +} diff --git a/modules/ml/CMakeLists.txt b/modules/ml/CMakeLists.txt index 1b64cc4f17..e1d5f3100b 100644 --- a/modules/ml/CMakeLists.txt +++ b/modules/ml/CMakeLists.txt @@ -1,2 +1,2 @@ set(the_description "Machine Learning") -ocv_define_module(ml opencv_core WRAP java python) +ocv_define_module(ml opencv_core WRAP java objc python) diff --git a/modules/ml/misc/objc/gen_dict.json b/modules/ml/misc/objc/gen_dict.json new file mode 100644 index 0000000000..1f35051c2d --- /dev/null +++ b/modules/ml/misc/objc/gen_dict.json @@ -0,0 +1,9 @@ +{ + "enum_fix" : { + "EM" : { "Types": "EMTypes" }, + "SVM" : { "Types": "SVMTypes" }, + "KNearest" : { "Types": "KNearestTypes" }, + "DTrees" : { "Flags": "DTreeFlags" }, + "StatModel" : { "Flags": "StatModelFlags" } + } +} diff --git a/modules/objc/CMakeLists.txt b/modules/objc/CMakeLists.txt new file mode 100644 index 0000000000..d4ea6e3563 --- /dev/null +++ b/modules/objc/CMakeLists.txt @@ -0,0 +1,6 @@ +if(OPENCV_INITIAL_PASS AND APPLE_FRAMEWORK AND NOT (BUILD_opencv_objc STREQUAL "OFF")) + # generator for Objective-C source code and documentation signatures + add_subdirectory(generator) +endif() + +#include(${CMAKE_CURRENT_SOURCE_DIR}/common.cmake) diff --git a/modules/objc/common.cmake b/modules/objc/common.cmake new file mode 100644 index 0000000000..ccfc923eaf --- /dev/null +++ b/modules/objc/common.cmake @@ -0,0 +1,11 @@ +ocv_warnings_disable(CMAKE_CXX_FLAGS -Wdeprecated-declarations) + +# get list of modules to wrap +# message(STATUS "Wrapped in Objective-C:") +set(OPENCV_OBJC_MODULES) +foreach(m ${OPENCV_MODULES_BUILD}) + if (";${OPENCV_MODULE_${m}_WRAPPERS};" MATCHES ";objc;" AND HAVE_${m}) + list(APPEND OPENCV_OBJC_MODULES ${m}) + #message(STATUS "\t${m}") + endif() +endforeach() diff --git a/modules/objc/doc/README.md b/modules/objc/doc/README.md new file mode 100644 index 0000000000..640d8eb70b --- /dev/null +++ b/modules/objc/doc/README.md @@ -0,0 +1,15 @@ +## About + +This is the documentation for the Objective-C/Swift OpenCV wrapper + +To get started: add the OpenCV framework to your project and add the following to your source + +###Objective-C + + #import + +###Swift + + import OpenCV + +For details of core OpenCV functionality see: `Core`, `Mat`, `Imgproc` diff --git a/modules/objc/generator/CMakeLists.txt b/modules/objc/generator/CMakeLists.txt new file mode 100644 index 0000000000..be7ab3ff74 --- /dev/null +++ b/modules/objc/generator/CMakeLists.txt @@ -0,0 +1,86 @@ +set(MODULE_NAME "objc") +set(OPENCV_MODULE_IS_PART_OF_WORLD FALSE) +ocv_add_module(${MODULE_NAME} INTERNAL opencv_core opencv_imgproc) + +set(OPENCV_OBJC_SIGNATURES_FILE "${CMAKE_CURRENT_BINARY_DIR}/opencv_objc_signatures.json" CACHE INTERNAL "") +set(OPENCV_OBJC_BINDINGS_DIR "${CMAKE_CURRENT_BINARY_DIR}" CACHE INTERNAL "") + +file(REMOVE_RECURSE "${OPENCV_OBJC_BINDINGS_DIR}/gen") +file(REMOVE "${OPENCV_DEPHELPER}/gen_opencv_objc_source") # force re-run after CMake + +# This file is included from a subdirectory +set(OBJC_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/..") +include(${OBJC_SOURCE_DIR}/common.cmake) + +# common files +file(GLOB_RECURSE deps "${CMAKE_CURRENT_SOURCE_DIR}/templates/*") + +set(__modules_config "") # list of OpenCV modules +foreach(m ${OPENCV_OBJC_MODULES}) + set(module_objc_dir "${OPENCV_MODULE_${m}_LOCATION}/misc/objc") + list(APPEND deps ${OPENCV_MODULE_${m}_HEADERS}) + file(GLOB_RECURSE misc_files "${module_objc_dir}/*") + list(APPEND deps ${misc_files}) + + string(REGEX REPLACE "^opencv_" "" m_ "${m}") + if(__modules_config) + set(__modules_config "${__modules_config},\n") + endif() + file(RELATIVE_PATH rel_path "${OpenCV_SOURCE_DIR}" "${OPENCV_MODULE_${m}_LOCATION}") + set(__modules_config "${__modules_config} { \"name\": \"${m_}\", \"location\": \"${rel_path}\" }") +endforeach(m) + +set(CONFIG_FILE "${CMAKE_CURRENT_BINARY_DIR}/gen_objc.json") +set(__config_str +"{ + \"rootdir\": \"${OpenCV_SOURCE_DIR}\", + \"modules\": [ +${__modules_config} + ] +} +") +if(EXISTS "${CONFIG_FILE}") + file(READ "${CONFIG_FILE}" __content) +else() + set(__content "") +endif() +if(NOT "${__content}" STREQUAL "${__config_str}") + file(WRITE "${CONFIG_FILE}" "${__config_str}") + file(REMOVE "${OPENCV_DEPHELPER}/gen_opencv_objc_source") +endif() +unset(__config_str) + +set(objc_generated_files + # "${OPENCV_OBJC_SIGNATURES_FILE}" + "${OPENCV_DEPHELPER}/gen_opencv_objc_source" +) + +string(REPLACE "opencv_" "" MODULES "${OPENCV_OBJC_MODULES}") + +if(IOS) + set(TARGET "ios") +else() + set(TARGET "osx") +endif() + +add_custom_command( + OUTPUT ${objc_generated_files} + COMMAND ${PYTHON_DEFAULT_EXECUTABLE} "${OBJC_SOURCE_DIR}/generator/gen_objc.py" -p "${OBJC_SOURCE_DIR}/../python/src2/gen2.py" -c "${CONFIG_FILE}" -t "${TARGET}" -f "${FRAMEWORK_NAME}" + COMMAND ${CMAKE_COMMAND} -E touch "${OPENCV_DEPHELPER}/gen_opencv_objc_source" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + DEPENDS "${OBJC_SOURCE_DIR}/generator/gen_objc.py" + "${OBJC_SOURCE_DIR}/../python/src2/gen2.py" + "${OBJC_SOURCE_DIR}/../python/src2/hdr_parser.py" + # don't, result of file(WRITE): "${CMAKE_CURRENT_BINARY_DIR}/gen_objc.json" + ${deps} + # not allowed (file(WRITE) result): "${CONFIG_FILE}" + COMMENT "Generate files for Objective-C bindings" +) + +add_custom_target(gen_opencv_objc_source DEPENDS ${objc_generated_files} + SOURCES "${OBJC_SOURCE_DIR}/generator/gen_objc.py" + "${OBJC_SOURCE_DIR}/generator/templates/cmakelists.template" + "${CMAKE_CURRENT_BINARY_DIR}/gen_objc.json" +) + +add_dependencies(opencv_world gen_opencv_objc_source) diff --git a/modules/objc/generator/gen_objc.py b/modules/objc/generator/gen_objc.py new file mode 100755 index 0000000000..0e64fcab2a --- /dev/null +++ b/modules/objc/generator/gen_objc.py @@ -0,0 +1,1384 @@ +#!/usr/bin/env python + +import sys, re, os.path, errno, fnmatch +import json +import logging +import codecs +from shutil import copyfile +from pprint import pformat +from string import Template +from distutils.dir_util import copy_tree + +if sys.version_info[0] >= 3: + from io import StringIO +else: + import io + class StringIO(io.StringIO): + def write(self, s): + if isinstance(s, str): + s = unicode(s) # noqa: F821 + return super(StringIO, self).write(s) + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) + +# list of modules +config = None +ROOT_DIR = None + +total_files = 0 +updated_files = 0 + +module_imports = [] + +# list of class names, which should be skipped by wrapper generator +# the list is loaded from misc/objc/gen_dict.json defined for the module and its dependencies +class_ignore_list = [] + +# list of constant names, which should be skipped by wrapper generator +# ignored constants can be defined using regular expressions +const_ignore_list = [] + +# list of private constants +const_private_list = [] + +# { Module : { public : [[name, val],...], private : [[]...] } } +missing_consts = {} + +type_dict = { + "" : {"objc_type" : ""}, # c-tor ret_type + "void" : {"objc_type" : "void", "is_primitive" : True}, + "bool" : {"objc_type" : "BOOL", "is_primitive" : True, "to_cpp": "(bool)%(n)s"}, + "char" : {"objc_type" : "char", "is_primitive" : True}, + "int" : {"objc_type" : "int", "is_primitive" : True, "out_type" : "int*", "out_type_ptr": "%(n)s", "out_type_ref": "*(int*)(%(n)s)"}, + "long" : {"objc_type" : "long", "is_primitive" : True}, + "float" : {"objc_type" : "float", "is_primitive" : True, "out_type" : "float*", "out_type_ptr": "%(n)s", "out_type_ref": "*(float*)(%(n)s)"}, + "double" : {"objc_type" : "double", "is_primitive" : True, "out_type" : "double*", "out_type_ptr": "%(n)s", "out_type_ref": "*(double*)(%(n)s)"}, + "size_t" : {"objc_type" : "size_t", "is_primitive" : True}, + "int64" : {"objc_type" : "long", "is_primitive" : True}, + "string" : {"objc_type" : "NSString*", "is_primitive" : True, "from_cpp": "[NSString stringWithUTF8String:%(n)s.c_str()]", "cast_to": "std::string"} +} + +# Defines a rule to add extra prefixes for names from specific namespaces. +# In example, cv::fisheye::stereoRectify from namespace fisheye is wrapped as fisheye_stereoRectify +namespaces_dict = {} + +# { class : [ header ] } +AdditionalImports = {} + +# { class : { func : {declaration, implementation} } } +ManualFuncs = {} + +# { class : { func : { arg_name : {"ctype" : ctype, "attrib" : [attrib]} } } } +func_arg_fix = {} + +# { class : { enum: fixed_enum } } +enum_fix = {} + +# { (class, func) : objc_signature } +method_dict = { + ("Mat", "convertTo") : "-convertTo:rtype:alpha:beta:", + ("Mat", "setTo") : "-setToScalar:mask:", + ("Mat", "zeros") : "+zeros:cols:type:", + ("Mat", "ones") : "+ones:cols:type:", + ("Mat", "dot") : "-dot:" +} + +def read_contents(fname): + with open(fname, 'r') as f: + data = f.read() + return data + +def mkdir_p(path): + ''' mkdir -p ''' + try: + os.makedirs(path) + except OSError as exc: + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: + raise + +T_OBJC_CLASS_HEADER = read_contents(os.path.join(SCRIPT_DIR, 'templates/objc_class_header.template')) +T_OBJC_CLASS_BODY = read_contents(os.path.join(SCRIPT_DIR, 'templates/objc_class_body.template')) +T_OBJC_MODULE_HEADER = read_contents(os.path.join(SCRIPT_DIR, 'templates/objc_module_header.template')) +T_OBJC_MODULE_BODY = read_contents(os.path.join(SCRIPT_DIR, 'templates/objc_module_body.template')) + +class GeneralInfo(): + def __init__(self, type, decl, namespaces): + self.namespace, self.classpath, self.classname, self.name = self.parseName(decl[0], namespaces) + + # parse doxygen comments + self.params={} + + self.deprecated = False + if type == "class": + docstring = "// C++: class " + self.name + "\n" + else: + docstring="" + + if len(decl)>5 and decl[5]: + doc = decl[5] + + if re.search("(@|\\\\)deprecated", doc): + self.deprecated = True + + docstring += sanitize_documentation_string(doc, type) + elif type == "class": + docstring += "/**\n* The " + self.name + " module\n*/\n" + + self.docstring = docstring + + def parseName(self, name, namespaces): + ''' + input: full name and available namespaces + returns: (namespace, classpath, classname, name) + ''' + name = name[name.find(" ")+1:].strip() # remove struct/class/const prefix + spaceName = "" + localName = name # . + for namespace in sorted(namespaces, key=len, reverse=True): + if name.startswith(namespace + "."): + spaceName = namespace + localName = name.replace(namespace + ".", "") + break + pieces = localName.split(".") + if len(pieces) > 2: # ... + return spaceName, ".".join(pieces[:-1]), pieces[-2], pieces[-1] + elif len(pieces) == 2: # . + return spaceName, pieces[0], pieces[0], pieces[1] + elif len(pieces) == 1: # + return spaceName, "", "", pieces[0] + else: + return spaceName, "", "" # error?! + + def fullName(self, isCPP=False): + result = ".".join([self.fullClass(), self.name]) + return result if not isCPP else get_cname(result) + + def fullClass(self, isCPP=False): + result = ".".join([f for f in [self.namespace] + self.classpath.split(".") if len(f)>0]) + return result if not isCPP else get_cname(result) + +class ConstInfo(GeneralInfo): + def __init__(self, decl, addedManually=False, namespaces=[], enumType=None): + GeneralInfo.__init__(self, "const", decl, namespaces) + self.cname = get_cname(self.name) + self.value = decl[1] + self.enumType = enumType + self.addedManually = addedManually + if self.namespace in namespaces_dict: + self.name = '%s_%s' % (namespaces_dict[self.namespace], self.name) + + def __repr__(self): + return Template("CONST $name=$value$manual").substitute(name=self.name, + value=self.value, + manual="(manual)" if self.addedManually else "") + + def isIgnored(self): + for c in const_ignore_list: + if re.match(c, self.name): + return True + return False + +def normalize_field_name(name): + return name.replace(".","_").replace("[","").replace("]","").replace("_getNativeObjAddr()","_nativeObj") + +def normalize_class_name(name): + return re.sub(r"^cv\.", "", name).replace(".", "_") + +def get_cname(name): + return name.replace(".", "::") + +def cast_from(t): + if t in type_dict and "cast_from" in type_dict[t]: + return type_dict[t]["cast_from"] + return t + +def cast_to(t): + if t in type_dict and "cast_to" in type_dict[t]: + return type_dict[t]["cast_to"] + return t + +class ClassPropInfo(): + def __init__(self, decl): # [f_ctype, f_name, '', '/RW'] + self.ctype = decl[0] + self.name = decl[1] + self.rw = "/RW" in decl[3] + + def __repr__(self): + return Template("PROP $ctype $name").substitute(ctype=self.ctype, name=self.name) + +class ClassInfo(GeneralInfo): + def __init__(self, decl, namespaces=[]): # [ 'class/struct cname', ': base', [modlist] ] + GeneralInfo.__init__(self, "class", decl, namespaces) + self.cname = get_cname(self.name) + self.methods = [] + self.methods_suffixes = {} + self.consts = [] # using a list to save the occurrence order + self.private_consts = [] + self.imports = set() + self.props= [] + self.objc_name = self.name + self.smart = None # True if class stores Ptr* instead of T* in nativeObj field + self.additionalImports = None # additional import files + self.enum_declarations = None # Objective-C enum declarations stream + self.method_declarations = None # Objective-C method declarations stream + self.method_implementations = None # Objective-C method implementations stream + self.objc_header_template = None # Objective-C header code + self.objc_body_template = None # Objective-C body code + for m in decl[2]: + if m.startswith("="): + self.objc_name = m[1:] + self.base = '' + self.is_base_class = True + if decl[1]: + self.base = re.sub(r"^.*:", "", decl[1].split(",")[0]).strip().replace(self.objc_name, "") + + def __repr__(self): + return Template("CLASS $namespace::$classpath.$name : $base").substitute(**self.__dict__) + + def getImports(self, module): + return ["#import \"%s.h\"" % c for c in sorted(filter(lambda m: m != self.name, map(lambda m: type_dict[m]["import_module"] if m in type_dict and "import_module" in type_dict[m] else m, self.imports)))] + + def isEnum(self, c): + return c in type_dict and type_dict[c].get("is_enum", False) + + def getForwardDeclarations(self, module): + enum_decl = filter(lambda x:self.isEnum(x) and type_dict[x]["import_module"] != module, self.imports) + class_decl = filter(lambda x: not self.isEnum(x), self.imports) + return ["#import \"%s.h\"" % type_dict[c]["import_module"] for c in enum_decl] + [""] + ["@class %s;" % c for c in sorted(class_decl)] + + def addImports(self, ctype, is_out_type): + if ctype == self.cname: + return + if ctype in type_dict: + objc_import = None + if "v_type" in type_dict[ctype]: + objc_import = type_dict[type_dict[ctype]["v_type"]]["objc_type"] + elif "v_v_type" in type_dict[ctype]: + objc_import = type_dict[type_dict[ctype]["v_v_type"]]["objc_type"] + elif not type_dict[ctype].get("is_primitive", False): + objc_import = type_dict[ctype]["objc_type"] + if objc_import is not None and objc_import not in ["NSNumber*", "NSString*"] and not (objc_import in type_dict and type_dict[objc_import].get("is_primitive", False)): + objc_import = objc_import[:-1] if objc_import[-1] == "*" else objc_import # remove trailing "*" + if objc_import != self.cname: + self.imports.add(objc_import) # remove trailing "*" + + def getAllMethods(self): + result = [] + result.extend([fi for fi in sorted(self.methods) if fi.isconstructor]) + result.extend([fi for fi in sorted(self.methods) if not fi.isconstructor]) + return result + + def addMethod(self, fi): + self.methods.append(fi) + + def getConst(self, name): + for cand in self.consts + self.private_consts: + if cand.name == name: + return cand + return None + + def addConst(self, constinfo): + # choose right list (public or private) + consts = self.consts + for c in const_private_list: + if re.match(c, constinfo.name): + consts = self.private_consts + break + consts.append(constinfo) + + def initCodeStreams(self, Module): + self.additionalImports = StringIO() + self.enum_declarations = StringIO() + self.method_declarations = StringIO() + self.method_implementations = StringIO() + if self.base: + self.objc_header_template = T_OBJC_CLASS_HEADER + self.objc_body_template = T_OBJC_CLASS_BODY + self.is_base_class = False + else: + self.base = "NSObject" + if self.name != Module: + self.objc_header_template = T_OBJC_CLASS_HEADER + self.objc_body_template = T_OBJC_CLASS_BODY + else: + self.objc_header_template = T_OBJC_MODULE_HEADER + self.objc_body_template = T_OBJC_MODULE_BODY + # misc handling + if self.name == Module: + for i in module_imports or []: + self.imports.add(i) + + def cleanupCodeStreams(self): + self.additionalImports.close() + self.enum_declarations.close() + self.method_declarations.close() + self.method_implementations.close() + + def generateObjcHeaderCode(self, m, M, objcM): + return Template(self.objc_header_template + "\n\n").substitute( + module = M, + additionalImports = self.additionalImports.getvalue(), + importBaseClass = '#import "' + self.base + '.h"' if not self.is_base_class else "", + forwardDeclarations = "\n".join(filter(None, self.getForwardDeclarations(objcM))), + enumDeclarations = self.enum_declarations.getvalue(), + nativePointerHandling = Template( +""" +#ifdef __cplusplus +@property(readonly)$cName* nativePtr; +#endif + +- (void)dealloc; + +#ifdef __cplusplus +- (instancetype)initWithNativePtr:($cName*)nativePtr; ++ (instancetype)fromNative:($cName*)nativePtr; +#endif +""" + ).substitute( + cName = self.fullName(isCPP=True) + ) if self.is_base_class else "", + manualMethodDeclations = "", + methodDeclarations = self.method_declarations.getvalue(), + name = self.name, + objcName = self.objc_name, + cName = self.cname, + imports = "\n".join(self.getImports(M)), + docs = self.docstring, + base = self.base) + + def generateObjcBodyCode(self, m, M): + return Template(self.objc_body_template + "\n\n").substitute( + module = M, + nativePointerHandling=Template( +""" +- (void)dealloc { + if (_nativePtr != NULL) { + delete _nativePtr; + } +} + +- (instancetype)initWithNativePtr:($cName*)nativePtr { + self = [super init]; + if (self) { + _nativePtr = nativePtr; + } + return self; +} + ++ (instancetype)fromNative:($cName*)nativePtr { + return [[$objcName alloc] initWithNativePtr:nativePtr]; +} +""" + ).substitute( + cName=self.fullName(isCPP=True), + objcName=self.objc_name + ) if self.is_base_class else "", + manualMethodDeclations = "", + methodImplementations = self.method_implementations.getvalue(), + name = self.name, + objcName = self.objc_name, + cName = self.cname, + imports = "\n".join(self.getImports(M)), + docs = self.docstring, + base = self.base) + +class ArgInfo(): + def __init__(self, arg_tuple): # [ ctype, name, def val, [mod], argno ] + self.pointer = False + ctype = arg_tuple[0] + if ctype.endswith("*"): + ctype = ctype[:-1] + self.pointer = True + self.ctype = ctype + self.name = arg_tuple[1] + self.defval = arg_tuple[2] + self.out = "" + if "/O" in arg_tuple[3]: + self.out = "O" + if "/IO" in arg_tuple[3]: + self.out = "IO" + + def __repr__(self): + return Template("ARG $ctype$p $name=$defval").substitute(ctype=self.ctype, + p=" *" if self.pointer else "", + name=self.name, + defval=self.defval) + +class FuncInfo(GeneralInfo): + def __init__(self, decl, namespaces=[]): # [ funcname, return_ctype, [modifiers], [args] ] + GeneralInfo.__init__(self, "func", decl, namespaces) + self.cname = get_cname(decl[0]) + self.objc_name = self.name + self.swift_name = self.name + self.cv_name = self.fullName(isCPP=True) + self.isconstructor = self.name == self.classname + if "[" in self.name: + self.objc_name = "getelem" + if self.namespace in namespaces_dict: + self.objc_name = '%s_%s' % (namespaces_dict[self.namespace], self.objc_name) + for m in decl[2]: + if m.startswith("="): + self.objc_name = m[1:] + self.static = ["","static"][ "/S" in decl[2] ] + self.ctype = re.sub(r"^CvTermCriteria", "TermCriteria", decl[1] or "") + self.args = [] + func_fix_map = func_arg_fix.get(self.objc_name, {}) + for a in decl[3]: + arg = a[:] + arg_fix_map = func_fix_map.get(arg[1], {}) + arg[0] = arg_fix_map.get('ctype', arg[0]) #fixing arg type + arg[2] = arg_fix_map.get('defval', arg[2]) #fixing arg defval + arg[3] = arg_fix_map.get('attrib', arg[3]) #fixing arg attrib + self.args.append(ArgInfo(arg)) + + if type_complete(self.args, self.ctype): + func_fix_map = func_arg_fix.get(self.signature(self.args), {}) + name_fix_map = func_fix_map.get(self.name, {}) + self.objc_name = name_fix_map.get('name', self.objc_name) + for arg in self.args: + arg_fix_map = func_fix_map.get(arg.name, {}) + arg.ctype = arg_fix_map.get('ctype', arg.ctype) #fixing arg type + arg.defval = arg_fix_map.get('defval', arg.defval) #fixing arg type + arg.name = arg_fix_map.get('name', arg.name) #fixing arg name + + def __repr__(self): + return Template("FUNC <$ctype $namespace.$classpath.$name $args>").substitute(**self.__dict__) + + def __lt__(self, other): + return self.__repr__() < other.__repr__() + + def signature(self, args): + objc_args = build_objc_args(args) + return "(" + type_dict[self.ctype]["objc_type"] + ")" + self.objc_name + " ".join(objc_args) + +def type_complete(args, ctype): + for a in args: + if a.ctype not in type_dict: + if not a.defval and a.ctype.endswith("*"): + a.defval = 0 + if a.defval: + a.ctype = '' + continue + return False + if ctype not in type_dict: + return False + return True + +def build_objc_args(args): + objc_args = [] + for a in args: + if a.ctype not in type_dict: + if not a.defval and a.ctype.endswith("*"): + a.defval = 0 + if a.defval: + a.ctype = '' + continue + if not a.ctype: # hidden + continue + objc_type = type_dict[a.ctype]["objc_type"] + if "v_type" in type_dict[a.ctype]: + if "O" in a.out: + objc_type = "NSMutableArray<" + objc_type + ">*" + else: + objc_type = "NSArray<" + objc_type + ">*" + elif "v_v_type" in type_dict[a.ctype]: + if "O" in a.out: + objc_type = "NSMutableArray*>*" + else: + objc_type = "NSArray*>*" + + if a.out and type_dict[a.ctype].get("out_type", ""): + objc_type = type_dict[a.ctype]["out_type"] + objc_args.append((a.name if len(objc_args) > 0 else '') + ':(' + objc_type + ')' + a.name) + return objc_args + +def build_objc_method_name(args): + objc_method_name = "" + for a in args[1:]: + if a.ctype not in type_dict: + if not a.defval and a.ctype.endswith("*"): + a.defval = 0 + if a.defval: + a.ctype = '' + continue + if not a.ctype: # hidden + continue + objc_method_name += a.name + ":" + return objc_method_name + +def build_swift_signature(args): + swift_signature = "" + for a in args: + if a.ctype not in type_dict: + if not a.defval and a.ctype.endswith("*"): + a.defval = 0 + if a.defval: + a.ctype = '' + continue + if not a.ctype: # hidden + continue + swift_signature += a.name + ":" + return swift_signature + +def add_method_to_dict(class_name, fi): + static = fi.static if fi.classname else True + if not method_dict.has_key((class_name, fi.objc_name)): + objc_method_name = ("+" if static else "-") + fi.objc_name + ":" + build_objc_method_name(fi.args) + method_dict[(class_name, fi.objc_name)] = objc_method_name + +def see_lookup(objc_class, see): + semi_colon = see.find("::") + see_class = see[:semi_colon] if semi_colon > 0 else objc_class + see_method = see[(semi_colon + 2):] if semi_colon != -1 else see + if method_dict.has_key((see_class, see_method)): + method = method_dict[(see_class, see_method)] + if see_class == objc_class: + return method + else: + return ("-" if method[0] == "-" else "") + "[" + see_class + " " + method[1:] + "]" + else: + return see + + +class ObjectiveCWrapperGenerator(object): + def __init__(self): + self.header_files = [] + self.clear() + + def clear(self): + self.namespaces = set(["cv"]) + mat_class_info = ClassInfo([ 'class Mat', '', [], [] ], self.namespaces) + mat_class_info.namespace = "cv" + self.classes = { "Mat" : mat_class_info } + self.classes["Mat"].namespace = "cv" + self.module = "" + self.Module = "" + self.ported_func_list = [] + self.skipped_func_list = [] + self.def_args_hist = {} # { def_args_cnt : funcs_cnt } + + def add_class(self, decl): + classinfo = ClassInfo(decl, namespaces=self.namespaces) + if classinfo.name in class_ignore_list: + logging.info('ignored: %s', classinfo) + return + name = classinfo.name + if self.isWrapped(name) and not classinfo.base: + logging.warning('duplicated: %s', classinfo) + return + self.classes[name] = classinfo + if name in type_dict and not classinfo.base: + logging.warning('duplicated: %s', classinfo) + return + if name != self.Module: + type_dict.setdefault(name, {}).update( + { "objc_type" : classinfo.objc_name + "*", + "from_cpp" : "[" + classinfo.objc_name + " fromNative:%(n)s]", + "to_cpp" : "*(" + classinfo.namespace.replace(".", "::") + "::" + classinfo.objc_name + "*)(%(n)s.nativePtr)" } + ) + + # missing_consts { Module : { public : [[name, val],...], private : [[]...] } } + if name in missing_consts: + if 'public' in missing_consts[name]: + for (n, val) in missing_consts[name]['public']: + classinfo.consts.append( ConstInfo([n, val], addedManually=True) ) + + # class props + for p in decl[3]: + if True: #"vector" not in p[0]: + classinfo.props.append( ClassPropInfo(p) ) + else: + logging.warning("Skipped property: [%s]" % name, p) + + if name != self.Module: + type_dict.setdefault("Ptr_"+name, {}).update( + { "objc_type" : classinfo.objc_name + "*", + "c_type" : name, + "to_cpp": "%(n)s.nativePtr", + "from_cpp_ptr": "[" + name + " fromNativePtr:%(n)s]"} + ) + logging.info('ok: class %s, name: %s, base: %s', classinfo, name, classinfo.base) + + def add_const(self, decl, scope=None, enumType=None): # [ "const cname", val, [], [] ] + constinfo = ConstInfo(decl, namespaces=self.namespaces, enumType=enumType) + if constinfo.isIgnored(): + logging.info('ignored: %s', constinfo) + else: + if not self.isWrapped(constinfo.classname): + logging.info('class not found: %s', constinfo) + constinfo.name = constinfo.classname + '_' + constinfo.name + constinfo.classname = '' + + ci = self.getClass(constinfo.classname) + duplicate = ci.getConst(constinfo.name) + if duplicate: + if duplicate.addedManually: + logging.info('manual: %s', constinfo) + else: + logging.warning('duplicated: %s', constinfo) + else: + ci.addConst(constinfo) + logging.info('ok: %s', constinfo) + + def add_enum(self, decl, scope): # [ "enum cname", "", [], [] ] + enumType = decl[0].rsplit(" ", 1)[1] + if enumType.endswith(""): + enumType = None + else: + ctype = normalize_class_name(enumType) + constinfo = ConstInfo(decl[3][0], namespaces=self.namespaces, enumType=enumType) + objc_type = enumType.rsplit(".", 1)[-1] + if enum_fix.has_key(constinfo.classname): + objc_type = enum_fix[constinfo.classname].get(objc_type, objc_type) + import_module = constinfo.classname if constinfo.classname and constinfo.classname != objc_type else self.Module + type_dict[ctype] = { "cast_from" : "int", + "cast_to" : get_cname(enumType), + "objc_type" : objc_type, + "is_enum" : True, + "import_module" : import_module, + "from_cpp" : "(" + objc_type + ")%(n)s"} + type_dict[objc_type] = { "cast_to" : get_cname(enumType), + "objc_type": objc_type, + "is_enum": True, + "import_module": import_module} + const_decls = decl[3] + + for decl in const_decls: + self.add_const(decl, scope, enumType) + + def add_func(self, decl): + fi = FuncInfo(decl, namespaces=self.namespaces) + classname = fi.classname or self.Module + if classname in class_ignore_list: + logging.info('ignored: %s', fi) + elif classname in ManualFuncs and fi.objc_name in ManualFuncs[classname]: + logging.info('manual: %s', fi) + if ManualFuncs[classname][fi.objc_name].has_key("objc_method_name"): + method_dict[(classname, fi.objc_name)] = ManualFuncs[classname][fi.objc_name]["objc_method_name"] + elif not self.isWrapped(classname): + logging.warning('not found: %s', fi) + else: + self.getClass(classname).addMethod(fi) + logging.info('ok: %s', fi) + # calc args with def val + cnt = len([a for a in fi.args if a.defval]) + self.def_args_hist[cnt] = self.def_args_hist.get(cnt, 0) + 1 + add_method_to_dict(classname, fi) + + def save(self, path, buf): + global total_files, updated_files + total_files += 1 + if os.path.exists(path): + with open(path, "rt") as f: + content = f.read() + if content == buf: + return + with codecs.open(path, "w", "utf-8") as f: + f.write(buf) + updated_files += 1 + + def get_namespace_prefix(self, cname): + namespace = self.classes[cname].namespace if self.classes.has_key(cname) else "cv" + return namespace.replace(".", "::") + "::" + + def gen(self, srcfiles, module, output_path, output_objc_path, common_headers): + self.clear() + self.module = module + self.Module = module.capitalize() + # TODO: support UMat versions of declarations (implement UMat-wrapper for Java) + parser = hdr_parser.CppHeaderParser(generate_umat_decls=False) + + self.add_class( ['class ' + self.Module, '', [], []] ) # [ 'class/struct cname', ':bases', [modlist] [props] ] + + # scan the headers and build more descriptive maps of classes, consts, functions + includes = [] + for hdr in common_headers: + logging.info("\n===== Common header : %s =====", hdr) + includes.append('#include "' + hdr + '"') + for hdr in srcfiles: + decls = parser.parse(hdr) + self.namespaces = parser.namespaces + logging.info("\n\n===== Header: %s =====", hdr) + logging.info("Namespaces: %s", parser.namespaces) + if decls: + includes.append('#include "' + hdr + '"') + else: + logging.info("Ignore header: %s", hdr) + for decl in decls: + logging.info("\n--- Incoming ---\n%s", pformat(decl[:5], 4)) # without docstring + name = decl[0] + if name.startswith("struct") or name.startswith("class"): + self.add_class(decl) + elif name.startswith("const"): + self.add_const(decl) + elif name.startswith("enum"): + # enum + self.add_enum(decl, self.Module) + else: # function + self.add_func(decl) + + logging.info("\n\n===== Generating... =====") + package_path = os.path.join(output_objc_path, module) + mkdir_p(package_path) + for ci in self.classes.values(): + if ci.name == "Mat": + continue + ci.initCodeStreams(self.Module) + self.gen_class(ci) + classObjcHeaderCode = ci.generateObjcHeaderCode(self.module, self.Module, ci.objc_name) + header_file = "%s/%s/%s.h" % (output_objc_path, module, ci.objc_name) + self.save(header_file, classObjcHeaderCode) + self.header_files.append(header_file) + classObjcBodyCode = ci.generateObjcBodyCode(self.module, self.Module) + self.save("%s/%s/%s.mm" % (output_objc_path, module, ci.objc_name), classObjcBodyCode) + ci.cleanupCodeStreams() + self.save(os.path.join(output_path, module+".txt"), self.makeReport()) + + def makeReport(self): + ''' + Returns string with generator report + ''' + report = StringIO() + total_count = len(self.ported_func_list)+ len(self.skipped_func_list) + report.write("PORTED FUNCs LIST (%i of %i):\n\n" % (len(self.ported_func_list), total_count)) + report.write("\n".join(self.ported_func_list)) + report.write("\n\nSKIPPED FUNCs LIST (%i of %i):\n\n" % (len(self.skipped_func_list), total_count)) + report.write("".join(self.skipped_func_list)) + for i in self.def_args_hist.keys(): + report.write("\n%i def args - %i funcs" % (i, self.def_args_hist[i])) + return report.getvalue() + + def fullTypeName(self, t): + if not type_dict[t].get("is_primitive", False) or type_dict[t].has_key("cast_to"): + if type_dict[t].has_key("cast_to"): + return type_dict[t]["cast_to"] + else: + namespace_prefix = self.get_namespace_prefix(t) + return namespace_prefix + t + else: + return t + + def build_objc2cv_prologue(self, prologue, vector_type, vector_full_type, objc_type, vector_name, array_name): + if not (type_dict.has_key(vector_type) and type_dict[vector_type].has_key("to_cpp") and type_dict[vector_type]["to_cpp"] != "%(n)s.nativeRef"): + prologue.append("OBJC2CV(" + vector_full_type + ", " + objc_type[:-1] + ", " + vector_name + ", " + array_name + ");") + else: + conv_macro = "CONV_" + array_name + prologue.append("#define " + conv_macro + "(e) " + type_dict[vector_type]["to_cpp"] % {"n": "e"}) + prologue.append("OBJC2CV_CUSTOM(" + vector_full_type + ", " + objc_type[:-1] + ", " + vector_name + ", " + array_name + ", " + conv_macro + ");") + prologue.append("#undef " + conv_macro) + + def build_cv2objc_epilogue(self, epilogue, vector_type, vector_full_type, objc_type, vector_name, array_name): + if not (type_dict.has_key(vector_type) and type_dict[vector_type].has_key("from_cpp") and type_dict[vector_type]["from_cpp"] != ("[" + objc_type[:-1] + " fromNative:%(n)s]")): + epilogue.append("CV2OBJC(" + vector_full_type + ", " + objc_type[:-1] + ", " + vector_name + ", " + array_name + ");") + else: + unconv_macro = "UNCONV_" + array_name + epilogue.append("#define " + unconv_macro + "(e) " + type_dict[vector_type]["from_cpp"] % {"n": "e"}) + epilogue.append("CV2OBJC_CUSTOM(" + vector_full_type + ", " + objc_type[:-1] + ", " + vector_name + ", " + array_name + ", " + unconv_macro + ");") + epilogue.append("#undef " + unconv_macro) + + def gen_func(self, ci, fi): + logging.info("%s", fi) + method_declarations = ci.method_declarations + method_implementations = ci.method_implementations + + decl_args = [] + for a in fi.args: + s = a.ctype or ' _hidden_ ' + if a.pointer: + s += "*" + elif a.out: + s += "&" + s += " " + a.name + if a.defval: + s += " = " + str(a.defval) + decl_args.append(s) + c_decl = "%s %s %s(%s)" % ( fi.static, fi.ctype, fi.cname, ", ".join(decl_args) ) + + # comment + method_declarations.write( "\n//\n// %s\n//\n" % c_decl ) + method_implementations.write( "\n//\n// %s\n//\n" % c_decl ) + # check if we 'know' all the types + if fi.ctype not in type_dict: # unsupported ret type + msg = "// Return type '%s' is not supported, skipping the function\n\n" % fi.ctype + self.skipped_func_list.append(c_decl + "\n" + msg) + method_declarations.write( " "*4 + msg ) + logging.warning("SKIP:" + c_decl.strip() + "\t due to RET type " + fi.ctype) + return + for a in fi.args: + if a.ctype not in type_dict: + if not a.defval and a.ctype.endswith("*"): + a.defval = 0 + if a.defval: + a.ctype = '' + continue + msg = "// Unknown type '%s' (%s), skipping the function\n\n" % (a.ctype, a.out or "I") + self.skipped_func_list.append(c_decl + "\n" + msg) + method_declarations.write( msg ) + logging.warning("SKIP:" + c_decl.strip() + "\t due to ARG type " + a.ctype + "/" + (a.out or "I")) + return + + self.ported_func_list.append(c_decl) + + # args + args = fi.args[:] # copy + objc_signatures=[] + while True: + # method args + cv_args = [] + prologue = [] + epilogue = [] + if fi.ctype: + ci.addImports(fi.ctype, False) + for a in args: + if not "v_type" in type_dict[a.ctype] and not "v_v_type" in type_dict[a.ctype]: + cast = ("(" + type_dict[a.ctype]["cast_to"] + ")") if "cast_to" in type_dict[a.ctype] else "" + cv_name = type_dict[a.ctype].get("to_cpp", cast + "%(n)s") if a.ctype else a.defval + if a.pointer and not cv_name == "0": + cv_name = "&(" + cv_name + ")" + if "O" in a.out and type_dict[a.ctype].get("out_type", ""): + cv_name = type_dict[a.ctype].get("out_type_ptr" if a.pointer else "out_type_ref", "%(n)s") + cv_args.append(type_dict[a.ctype].get("cv_name", cv_name) % {"n": a.name}) + if not a.ctype: # hidden + continue + ci.addImports(a.ctype, "O" in a.out) + if "v_type" in type_dict[a.ctype]: # pass as vector + vector_cpp_type = type_dict[a.ctype]["v_type"] + objc_type = type_dict[a.ctype]["objc_type"] + has_namespace = vector_cpp_type.find("::") != -1 + ci.addImports(a.ctype, False) + vector_full_cpp_type = self.fullTypeName(vector_cpp_type) if not has_namespace else vector_cpp_type + vector_cpp_name = a.name + "Vector" + cv_args.append(vector_cpp_name) + self.build_objc2cv_prologue(prologue, vector_cpp_type, vector_full_cpp_type, objc_type, vector_cpp_name, a.name) + if "O" in a.out: + self.build_cv2objc_epilogue(epilogue, vector_cpp_type, vector_full_cpp_type, objc_type, vector_cpp_name, a.name) + + if "v_v_type" in type_dict[a.ctype]: # pass as vector of vector + vector_cpp_type = type_dict[a.ctype]["v_v_type"] + objc_type = type_dict[a.ctype]["objc_type"] + ci.addImports(a.ctype, False) + vector_full_cpp_type = self.fullTypeName(vector_cpp_type) + vector_cpp_name = a.name + "Vector2" + cv_args.append(vector_cpp_name) + prologue.append("OBJC2CV2(" + vector_full_cpp_type + ", " + objc_type[:-1] + ", " + vector_cpp_name + ", " + a.name + ");") + if "O" in a.out: + epilogue.append( + "CV2OBJC2(" + vector_full_cpp_type + ", " + objc_type[:-1] + ", " + vector_cpp_name + ", " + a.name + ");") + + # calculate method signature to check for uniqueness + objc_args = build_objc_args(args) + objc_signature = fi.signature(args) + logging.info("Objective-C: " + objc_signature) + + if objc_signature in objc_signatures: + if args: + args.pop() + continue + else: + break + + # doc comment + if fi.docstring: + lines = fi.docstring.splitlines() + toWrite = [] + for index, line in enumerate(lines): + p0 = line.find("@param") + if p0 != -1: + p0 += 7 # len("@param" + 1) + p1 = line.find(' ', p0) + p1 = len(line) if p1 == -1 else p1 + name = line[p0:p1] + for arg in args: + if arg.name == name: + toWrite.append(re.sub('\*\s*@param ', '* @param ', line)) + break + else: + s0 = line.find("@see") + if s0 != -1: + sees = line[(s0 + 5):].split(",") + toWrite.append(line[:(s0 + 5)] + ", ".join(["`" + see_lookup(ci.objc_name, see.strip()) + "`" for see in sees])) + else: + toWrite.append(line) + + for line in toWrite: + method_declarations.write(line + "\n") + + # public wrapper method impl (calling native one above) + # e.g. + # public static void add( Mat src1, Mat src2, Mat dst, Mat mask, int dtype ) + # { add_0( src1.nativeObj, src2.nativeObj, dst.nativeObj, mask.nativeObj, dtype ); } + ret_type = fi.ctype + if fi.ctype.endswith('*'): + ret_type = ret_type[:-1] + ret_val = self.fullTypeName(fi.ctype) + " retVal = " + ret = "return retVal;" + tail = "" + constructor = False + if "v_type" in type_dict[ret_type]: + objc_type = type_dict[ret_type]["objc_type"] + vector_type = type_dict[ret_type]["v_type"] + full_cpp_type = (self.get_namespace_prefix(vector_type) if (vector_type.find("::") == -1) else "") + vector_type + prologue.append("NSMutableArray<" + objc_type + ">* retVal = [NSMutableArray new];") + ret_val = "std::vector<" + full_cpp_type + "> retValVector = " + self.build_cv2objc_epilogue(epilogue, vector_type, full_cpp_type, objc_type, "retValVector", "retVal") + elif "v_v_type" in type_dict[ret_type]: + objc_type = type_dict[ret_type]["objc_type"] + cpp_type = type_dict[ret_type]["v_v_type"] + if cpp_type.find("::") == -1: + cpp_type = self.get_namespace_prefix(cpp_type) + cpp_type + prologue.append("NSMutableArray*>* retVal = [NSMutableArray new];") + ret_val = "std::vector<" + cpp_type + "> retValVector = " + epilogue.append("CV2OBJC2(" + cpp_type + ", " + objc_type[:-1] + ", retValVector, retVal);") + elif ret_type.startswith("Ptr_"): + cpp_type = type_dict[ret_type]["c_type"] + namespace_prefix = self.get_namespace_prefix(cpp_type) + ret_val = namespace_prefix + cpp_type + "* retVal = " + ret = "return [" + type_dict[ret_type]["objc_type"][:-1] + " fromNative:retVal];" + elif ret_type == "void": + ret_val = "" + ret = "" + elif ret_type == "": # c-tor + constructor = True + ret_val = "return [self initWithNativePtr:new " + tail = "]" + ret = "" + elif self.isWrapped(ret_type): # wrapped class + namespace_prefix = self.get_namespace_prefix(ret_type) + ret_val = namespace_prefix + ret_type + "* retVal = new " + namespace_prefix + ret_type + "(" + tail = ")" + ret_type_dict = type_dict[ret_type] + from_cpp = ret_type_dict["from_cpp_ptr"] if ret_type_dict.has_key("from_cpp_ptr") else ret_type_dict["from_cpp"] + ret = "return " + (from_cpp % { "n" : "retVal" }) + ";" + elif "from_cpp" in type_dict[ret_type]: + ret = "return " + (type_dict[ret_type]["from_cpp"] % { "n" : "retVal" }) + ";" + + static = fi.static if fi.classname else True + + objc_ret_type = type_dict[fi.ctype]["objc_type"] if type_dict[fi.ctype]["objc_type"] else "void" if not constructor else "instancetype" + if "v_type" in type_dict[ret_type]: + objc_ret_type = "NSArray<" + objc_ret_type + ">*" + elif "v_v_type" in type_dict[ret_type]: + objc_ret_type = "NSArray*>*" + + prototype = Template("$static ($objc_ret_type)$objc_name$objc_args").substitute( + static = "+" if static else "-", + objc_ret_type = objc_ret_type, + objc_args = " ".join(objc_args), + objc_name = fi.objc_name if not constructor else ("init" + ("With" + (args[0].name[0].upper() + args[0].name[1:]) if len(args) > 0 else "")) + ) + + method_declarations.write( Template( +"""$prototype$swift_name$deprecation_decl; + +""" + ).substitute( + prototype = prototype, + swift_name = " NS_SWIFT_NAME(" + fi.swift_name + "(" + build_swift_signature(args) + "))" if not constructor else "", + deprecation_decl = " DEPRECATED_ATTRIBUTE" if fi.deprecated else "" + ) + ) + + method_implementations.write( Template( +"""$prototype {$prologue + $ret_val$obj_deref$cv_name($cv_args)$tail;$epilogue$ret +} + +""" + ).substitute( + prototype = prototype, + ret = "\n " + ret if ret else "", + ret_val = ret_val, + prologue = "\n " + "\n ".join(prologue) if prologue else "", + epilogue = "\n " + "\n ".join(epilogue) if epilogue else "", + static = "+" if static else "-", + obj_deref = ("MAKE_PTR(" + fi.fullClass(isCPP=True) + ")->" if not ci.is_base_class else "_nativePtr->") if not static and not constructor else "", + cv_name = fi.cv_name if static else fi.fullClass(isCPP=True) if constructor else fi.name, + cv_args = ", ".join(cv_args), + tail = tail + ) + ) + # adding method signature to dictionary + objc_signatures.append(objc_signature) + + # processing args with default values + if args and args[-1].defval: + args.pop() + else: + break + + def gen_class(self, ci): + logging.info("%s", ci) + if ci.name in AdditionalImports: + ci.additionalImports.write("\n".join(["#import %s" % h for h in AdditionalImports[ci.name]])) + + # constants + wrote_consts_pragma = False + consts_map = {c.name: c for c in ci.private_consts} + consts_map.update({c.name: c for c in ci.consts}) + def const_value(v): + if v in consts_map: + target = consts_map[v] + assert target.value != v + return const_value(target.value) + return v + if ci.consts: + enumTypes = set(map(lambda c: c.enumType, ci.consts)) + grouped_consts = {enumType: [c for c in ci.consts if c.enumType == enumType] for enumType in enumTypes} + for typeName, consts in grouped_consts.items(): + logging.info("%s", consts) + if typeName: + typeName = typeName.rsplit(".", 1)[-1] + if enum_fix.has_key(ci.cname): + typeName = enum_fix[ci.cname].get(typeName, typeName) + + ci.enum_declarations.write(""" +// C++: enum {1} +typedef NS_ENUM(int, {2}) {{ + {0}\n}};\n\n""".format(",\n ".join(["%s = %s" % (c.name, c.value) for c in consts]), typeName, typeName) + ) + else: + if not wrote_consts_pragma: + ci.method_declarations.write("#pragma mark - Class Constants\n\n") + wrote_consts_pragma = True + ci.method_declarations.write(""" +{0}\n\n""".format("\n".join(["@property (class, readonly) int %s NS_SWIFT_NAME(%s);" % (c.name, c.name) for c in consts])) + ) + declared_consts = [] + match_alphabet = re.compile("[a-zA-Z]") + for c in consts: + value = str(c.value) + if match_alphabet.search(value): + for declared_const in sorted(declared_consts, key=len, reverse=True): + regex = re.compile("(?" if not ci.is_base_class else "_nativePtr->" + if type_data.has_key("v_type"): + vector_type = type_data["v_type"] + full_cpp_type = (self.get_namespace_prefix(vector_type) if (vector_type.find("::") == -1) else "") + vector_type + ret_val = "std::vector<" + full_cpp_type + "> retValVector = " + ci.method_implementations.write("-(NSArray<" + objc_type + ">*)" + pi.name + "{\n") + ci.method_implementations.write("\tNSMutableArray<" + objc_type + ">* retVal = [NSMutableArray new];\n") + ci.method_implementations.write("\t" + ret_val + ptr_ref + pi.name + ";\n") + epilogue = [] + self.build_cv2objc_epilogue(epilogue, vector_type, full_cpp_type, objc_type, "retValVector", "retVal") + ci.method_implementations.write("\t" + ("\n\t".join(epilogue)) + "\n") + ci.method_implementations.write("\treturn retVal;\n}\n\n") + else: + from_cpp = type_data.get("from_cpp", "%(n)s") + retVal = from_cpp % {"n": (ptr_ref + pi.name)} + ci.method_implementations.write("-(" + objc_type + ")" + pi.name + "{\n\treturn " + retVal + ";\n}\n\n") + if pi.rw: + if type_data.has_key("v_type"): + vector_type = type_data["v_type"] + full_cpp_type = (self.get_namespace_prefix(vector_type) if (vector_type.find("::") == -1) else "") + vector_type + ci.method_implementations.write("-(void)set" + pi.name[0].upper() + pi.name[1:] + ":(NSArray<" + objc_type + ">*)" + pi.name + "{\n") + prologue = [] + self.build_objc2cv_prologue(prologue, vector_type, full_cpp_type, objc_type, "valVector", pi.name) + ci.method_implementations.write("\t" + ("\n\t".join(prologue)) + "\n") + ci.method_implementations.write("\t" + ptr_ref + pi.name + " = valVector;\n}\n\n") + else: + to_cpp = type_data.get("to_cpp", "%(n)s") + val = to_cpp % {"n": pi.name} + ci.method_implementations.write("-(void)set" + pi.name[0].upper() + pi.name[1:] + ":(" + objc_type + ")" + pi.name + " {\n\t" + ptr_ref + pi.name + " = " + val + ";\n}\n\n") + + # manual ports + if ci.name in ManualFuncs: + for func in ManualFuncs[ci.name].keys(): + ci.method_declarations.write( "\n".join(ManualFuncs[ci.name][func]["declaration"]) ) + ci.method_implementations.write( "\n".join(ManualFuncs[ci.name][func]["implementation"]) ) + + def getClass(self, classname): + return self.classes[classname or self.Module] + + def isWrapped(self, classname): + name = classname or self.Module + return name in self.classes + + def isSmartClass(self, ci): + ''' + Check if class stores Ptr* instead of T* in nativeObj field + ''' + if ci.smart != None: + return ci.smart + + # if parents are smart (we hope) then children are! + # if not we believe the class is smart if it has "create" method + ci.smart = False + if ci.base or ci.name == 'Algorithm': + ci.smart = True + else: + for fi in ci.methods: + if fi.name == "create": + ci.smart = True + break + + return ci.smart + + def smartWrap(self, ci, fullname): + ''' + Wraps fullname with Ptr<> if needed + ''' + if self.isSmartClass(ci): + return "Ptr<" + fullname + ">" + return fullname + + def finalize(self, output_objc_path): + opencv_header_file = os.path.join(output_objc_path, framework_name + ".h") + self.save(opencv_header_file, '\n'.join(['#import "%s"' % os.path.basename(f) for f in self.header_files if os.path.basename(f) != "CVObjcUtil.h"])) + cmakelist_template = read_contents(os.path.join(SCRIPT_DIR, 'templates/cmakelists.template')) + cmakelist = Template(cmakelist_template).substitute(modules = ";".join(modules), framework = framework_name) + self.save(os.path.join(dstdir, "CMakeLists.txt"), cmakelist) + mkdir_p("./framework_build") + mkdir_p("./test_build") + mkdir_p("./doc_build") + copyfile(os.path.join(SCRIPT_DIR, '../doc/README.md'), "./doc_build/README.md") + if framework_name != "OpenCV": + for dirname, dirs, files in os.walk(os.path.join(testdir, "test")): + for filename in files: + filepath = os.path.join(dirname, filename) + with open(filepath) as file: + body = file.read() + body = body.replace("import OpenCV", "import " + framework_name) + body = body.replace("#import ", "#import <" + framework_name + "/" + framework_name + ".h>") + with open(filepath, "w") as file: + file.write(body) + + +def copy_objc_files(objc_files_dir, objc_base_path, module_path, include = False): + global total_files, updated_files + objc_files = [] + re_filter = re.compile(r'^.+\.(h|m|mm|swift)$') + for root, dirnames, filenames in os.walk(objc_files_dir): + objc_files += [os.path.join(root, filename) for filename in filenames if re_filter.match(filename)] + objc_files = [f.replace('\\', '/') for f in objc_files] + + re_prefix = re.compile(r'^.+/(.+)\.(h|m|mm|swift)$') + for objc_file in objc_files: + src = objc_file + m = re_prefix.match(objc_file) + target_fname = (m.group(1) + '.' + m.group(2)) if m else os.path.basename(objc_file) + dest = os.path.join(objc_base_path, os.path.join(module_path, target_fname)) + mkdir_p(os.path.dirname(dest)) + total_files += 1 + if include and m.group(2) == 'h': + generator.header_files.append(dest) + if (not os.path.exists(dest)) or (os.stat(src).st_mtime - os.stat(dest).st_mtime > 1): + copyfile(src, dest) + updated_files += 1 + +def unescape(str): + return str.replace("<", "<").replace(">", ">").replace("&", "&") + +def escape_underscore(str): + return str.replace('_', '\\_') + +def escape_texttt(str): + return re.sub(re.compile('texttt{(.*?)\}', re.DOTALL), lambda x: 'texttt{' + escape_underscore(x.group(1)) + '}', str) + +def get_macros(tex): + out = "" + if re.search("\\\\fork\s*{", tex): + out += "\\newcommand{\\fork}[4]{ \\left\\{ \\begin{array}{l l} #1 & \\text{#2}\\\\\\\\ #3 & \\text{#4}\\\\\\\\ \\end{array} \\right.} " + if re.search("\\\\vecthreethree\s*{", tex): + out += "\\newcommand{\\vecthreethree}[9]{ \\begin{bmatrix} #1 & #2 & #3\\\\\\\\ #4 & #5 & #6\\\\\\\\ #7 & #8 & #9 \\end{bmatrix} } " + return out + +def fix_tex(tex): + macros = get_macros(tex) + fix_escaping = escape_texttt(unescape(tex)) + return macros + fix_escaping + +def sanitize_documentation_string(doc, type): + if type == "class": + doc = doc.replace("@param ", "") + + doc = re.sub(re.compile('`\\$\\$(.*?)\\$\\$`', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc) + doc = re.sub(re.compile('\\\\f\\{align\\*\\}\\{?(.*?)\\\\f\\}', re.DOTALL), lambda x: '`$$\\begin{aligned} ' + fix_tex(x.group(1)) + ' \\end{aligned}$$`', doc) + doc = re.sub(re.compile('\\\\f\\{equation\\*\\}\\{(.*?)\\\\f\\}', re.DOTALL), lambda x: '`$$\\begin{aligned} ' + fix_tex(x.group(1)) + ' \\end{aligned}$$`', doc) + doc = re.sub(re.compile('\\\\f\\$(.*?)\\\\f\\$', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc) + doc = re.sub(re.compile('\\\\f\\[(.*?)\\\\f\\]', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc) + doc = re.sub(re.compile('\\\\f\\{(.*?)\\\\f\\}', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc) + + doc = doc.replace("@anchor", "") \ + .replace("@brief ", "").replace("\\brief ", "") \ + .replace("@cite", "CITE:") \ + .replace("@code{.cpp}", "") \ + .replace("@code{.txt}", "") \ + .replace("@code", "") \ + .replace("@copydoc", "") \ + .replace("@copybrief", "") \ + .replace("@date", "") \ + .replace("@defgroup", "") \ + .replace("@details ", "") \ + .replace("@endcode", "") \ + .replace("@endinternal", "") \ + .replace("@file", "") \ + .replace("@include", "INCLUDE:") \ + .replace("@ingroup", "") \ + .replace("@internal", "") \ + .replace("@overload", "") \ + .replace("@param[in]", "@param") \ + .replace("@param[out]", "@param") \ + .replace("@ref", "REF:") \ + .replace("@returns", "@return") \ + .replace("@sa ", "@see ") \ + .replace("@snippet", "SNIPPET:") \ + .replace("@todo", "TODO:") \ + + lines = doc.splitlines() + + in_code = False + for i,line in enumerate(lines): + if line.find("") != -1: + in_code = False + lines[i] = line.replace("", "") + if in_code: + lines[i] = unescape(line) + if line.find("") != -1: + in_code = True + lines[i] = line.replace("", "") + + lines = list(map(lambda x: x[x.find('*'):].strip() if x.lstrip().startswith("*") else x, lines)) + lines = list(map(lambda x: "* " + x[1:].strip() if x.startswith("*") and x != "*" else x, lines)) + lines = list(map(lambda x: x if x.startswith("*") else "* " + x if x and x != "*" else "*", lines)) + + hasValues = False + for line in lines: + if line != "*": + hasValues = True + break + return "/**\n " + "\n ".join(lines) + "\n */" if hasValues else "" + +if __name__ == "__main__": + # initialize logger + logging.basicConfig(filename='gen_objc.log', format=None, filemode='w', level=logging.INFO) + handler = logging.StreamHandler() + handler.setLevel(logging.WARNING) + logging.getLogger().addHandler(handler) + + # parse command line parameters + import argparse + arg_parser = argparse.ArgumentParser(description='OpenCV Objective-C Wrapper Generator') + arg_parser.add_argument('-p', '--parser', required=True, help='OpenCV header parser') + arg_parser.add_argument('-c', '--config', required=True, help='OpenCV modules config') + arg_parser.add_argument('-t', '--target', required=True, help='Target (either ios or osx)') + arg_parser.add_argument('-f', '--framework', required=True, help='Framework name') + + args=arg_parser.parse_args() + + # import header parser + hdr_parser_path = os.path.abspath(args.parser) + if hdr_parser_path.endswith(".py"): + hdr_parser_path = os.path.dirname(hdr_parser_path) + sys.path.append(hdr_parser_path) + import hdr_parser + + with open(args.config) as f: + config = json.load(f) + + ROOT_DIR = config['rootdir']; assert os.path.exists(ROOT_DIR) + + dstdir = "./gen" + testdir = "./test" + objc_base_path = os.path.join(dstdir, 'objc'); mkdir_p(objc_base_path) + objc_test_base_path = testdir; mkdir_p(objc_test_base_path) + copy_objc_files(os.path.join(SCRIPT_DIR, '../test/test'), objc_test_base_path, 'test', False) + copy_objc_files(os.path.join(SCRIPT_DIR, '../test/dummy'), objc_test_base_path, 'dummy', False) + copyfile(os.path.join(SCRIPT_DIR, '../test/cmakelists.template'), os.path.join(objc_test_base_path, 'CMakeLists.txt')) + + # launch Objective-C Wrapper generator + generator = ObjectiveCWrapperGenerator() + + gen_dict_files = [] + modules = [] + framework_name = args.framework + + print("Objective-C: Processing OpenCV modules: %d" % len(config['modules'])) + for e in config['modules']: + (module, module_location) = (e['name'], os.path.join(ROOT_DIR, e['location'])) + logging.info("\n=== MODULE: %s (%s) ===\n" % (module, module_location)) + modules.append(module) + + module_imports = [] + srcfiles = [] + common_headers = [] + + misc_location = os.path.join(module_location, 'misc/objc') + + srcfiles_fname = os.path.join(misc_location, 'filelist') + if os.path.exists(srcfiles_fname): + with open(srcfiles_fname) as f: + srcfiles = [os.path.join(module_location, str(l).strip()) for l in f.readlines() if str(l).strip()] + else: + re_bad = re.compile(r'(private|.inl.hpp$|_inl.hpp$|.details.hpp$|_winrt.hpp$|/cuda/|/legacy/)') + # .h files before .hpp + h_files = [] + hpp_files = [] + for root, dirnames, filenames in os.walk(os.path.join(module_location, 'include')): + h_files += [os.path.join(root, filename) for filename in fnmatch.filter(filenames, '*.h')] + hpp_files += [os.path.join(root, filename) for filename in fnmatch.filter(filenames, '*.hpp')] + srcfiles = h_files + hpp_files + srcfiles = [f for f in srcfiles if not re_bad.search(f.replace('\\', '/'))] + logging.info("\nFiles (%d):\n%s", len(srcfiles), pformat(srcfiles)) + + common_headers_fname = os.path.join(misc_location, 'filelist_common') + if os.path.exists(common_headers_fname): + with open(common_headers_fname) as f: + common_headers = [os.path.join(module_location, str(l).strip()) for l in f.readlines() if str(l).strip()] + logging.info("\nCommon headers (%d):\n%s", len(common_headers), pformat(common_headers)) + + gendict_fname = os.path.join(misc_location, 'gen_dict.json') + if os.path.exists(gendict_fname): + with open(gendict_fname) as f: + gen_type_dict = json.load(f) + class_ignore_list += gen_type_dict.get("class_ignore_list", []) + const_ignore_list += gen_type_dict.get("const_ignore_list", []) + const_private_list += gen_type_dict.get("const_private_list", []) + missing_consts.update(gen_type_dict.get("missing_consts", {})) + type_dict.update(gen_type_dict.get("type_dict", {})) + AdditionalImports.update(gen_type_dict.get("AdditionalImports", {})) + ManualFuncs.update(gen_type_dict.get("ManualFuncs", {})) + func_arg_fix.update(gen_type_dict.get("func_arg_fix", {})) + enum_fix.update(gen_type_dict.get("enum_fix", {})) + namespaces_dict.update(gen_type_dict.get("namespaces_dict", {})) + module_imports += gen_type_dict.get("module_imports", []) + + objc_files_dir = os.path.join(misc_location, 'common') + if os.path.exists(objc_files_dir): + copy_objc_files(objc_files_dir, objc_base_path, module, True) + if args.target == 'ios': + ios_files_dir = os.path.join(misc_location, 'ios') + if os.path.exists(ios_files_dir): + copy_objc_files(ios_files_dir, objc_base_path, module, True) + + objc_test_files_dir = os.path.join(misc_location, 'test') + if os.path.exists(objc_test_files_dir): + copy_objc_files(objc_test_files_dir, objc_test_base_path, 'test', False) + objc_test_resources_dir = os.path.join(objc_test_files_dir, 'resources') + if os.path.exists(objc_test_resources_dir): + copy_tree(objc_test_resources_dir, os.path.join(objc_test_base_path, 'test', 'resources')) + + if len(srcfiles) > 0: + generator.gen(srcfiles, module, dstdir, objc_base_path, common_headers) + else: + logging.info("No generated code for module: %s", module) + generator.finalize(objc_base_path) + + print('Generated files: %d (updated %d)' % (total_files, updated_files)) diff --git a/modules/objc/generator/templates/cmakelists.template b/modules/objc/generator/templates/cmakelists.template new file mode 100644 index 0000000000..d6fab2cede --- /dev/null +++ b/modules/objc/generator/templates/cmakelists.template @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 3.15) + +project($framework) + +set(MODULES "$modules") + +# Enable C++11 +set (CMAKE_CXX_STANDARD 11) +set (CMAKE_CXX_STANDARD_REQUIRED TRUE) + +set (OBJC_COMPILE_FLAGS "-fobjc-arc -fobjc-weak -fvisibility=hidden -D__OPENCV_BUILD=1") +set (SUPPRESS_WARNINGS_FLAGS "-Wno-incomplete-umbrella") +set (CMAKE_CXX_FLAGS "$${CMAKE_CXX_FLAGS} $${OBJC_COMPILE_FLAGS} $${SUPPRESS_WARNINGS_FLAGS}") + +# grab the files +file(GLOB_RECURSE objc_sources "objc/*\.h" "objc/*\.m" "objc/*\.mm" "objc/*\.swift") +file(GLOB_RECURSE objc_headers "*\.h") + +add_library(opencv_objc_framework STATIC $${objc_sources}) + +set_target_properties(opencv_objc_framework PROPERTIES LINKER_LANGUAGE CXX) + +target_include_directories(opencv_objc_framework PRIVATE "$${BUILD_ROOT}") +target_include_directories(opencv_objc_framework PRIVATE "$${BUILD_ROOT}/install/include") +target_include_directories(opencv_objc_framework PRIVATE "$${BUILD_ROOT}/install/include/opencv2") +foreach(m $${MODULES}) + target_include_directories(opencv_objc_framework PRIVATE "$${BUILD_ROOT}/modules/objc/gen/objc/$${m}") +endforeach() + +install(TARGETS opencv_objc_framework LIBRARY DESTINATION lib) + +enable_language(Swift) + +# Additional target properties +set_target_properties(opencv_objc_framework PROPERTIES + OUTPUT_NAME "$framework" + ARCHIVE_OUTPUT_DIRECTORY "$${BUILD_ROOT}/lib" + XCODE_ATTRIBUTE_SWIFT_VERSION 5.0 + XCODE_ATTRIBUTE_OTHER_SWIFT_FLAGS "-Xcc $${SUPPRESS_WARNINGS_FLAGS}" + FRAMEWORK TRUE + MACOSX_FRAMEWORK_IDENTIFIER org.opencv.$framework + PUBLIC_HEADER "$${objc_headers}" + DEFINE_SYMBOL CVAPI_EXPORTS + ) + +find_program(JAZZY jazzy) + +if(JAZZY) + add_custom_command( + OUTPUT "$${BUILD_ROOT}/modules/objc/doc_build/doc/index.html" + COMMAND $${JAZZY} --objc --author OpenCV --author_url http://opencv.org --github_url https://github.com/opencv/opencv --umbrella-header "$${BUILD_ROOT}/lib/$${CMAKE_BUILD_TYPE}/$framework.framework/Headers/$framework.h" --framework-root "$${BUILD_ROOT}/lib/$${CMAKE_BUILD_TYPE}/$framework.framework" --module $framework --sdk iphonesimulator --undocumented-text \"\" + WORKING_DIRECTORY "$${BUILD_ROOT}/modules/objc/doc_build" + COMMENT "Generating Documentation" + ) + add_custom_target(opencv_objc_doc ALL DEPENDS "$${BUILD_ROOT}/modules/objc/doc_build/doc/index.html") + add_dependencies(opencv_objc_doc opencv_objc_framework) +else() + message("jazzy not found - documentation will not be generated!") +endif() diff --git a/modules/objc/generator/templates/objc_class_body.template b/modules/objc/generator/templates/objc_class_body.template new file mode 100644 index 0000000000..7db209da1b --- /dev/null +++ b/modules/objc/generator/templates/objc_class_body.template @@ -0,0 +1,16 @@ +// +// This file is auto-generated. Please don't modify it! +// + +#import "$objcName.h" +#import "CVObjcUtil.h" + +$imports + +@implementation $objcName + +$nativePointerHandling + +$methodImplementations + +@end diff --git a/modules/objc/generator/templates/objc_class_header.template b/modules/objc/generator/templates/objc_class_header.template new file mode 100644 index 0000000000..fae6b48dd6 --- /dev/null +++ b/modules/objc/generator/templates/objc_class_header.template @@ -0,0 +1,29 @@ +// +// This file is auto-generated. Please don't modify it! +// +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +$additionalImports +#endif + +#import +$importBaseClass + +$forwardDeclarations + +$enumDeclarations + +NS_ASSUME_NONNULL_BEGIN + +$docs +@interface $objcName : $base + +$nativePointerHandling + +$methodDeclarations + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/objc/generator/templates/objc_module_body.template b/modules/objc/generator/templates/objc_module_body.template new file mode 100644 index 0000000000..a11d2404f3 --- /dev/null +++ b/modules/objc/generator/templates/objc_module_body.template @@ -0,0 +1,14 @@ +// +// This file is auto-generated. Please don't modify it! +// + +#import "$module.h" +#import "CVObjcUtil.h" + +$imports + +@implementation $module + +$methodImplementations + +@end diff --git a/modules/objc/generator/templates/objc_module_header.template b/modules/objc/generator/templates/objc_module_header.template new file mode 100644 index 0000000000..9c7f14bdf1 --- /dev/null +++ b/modules/objc/generator/templates/objc_module_header.template @@ -0,0 +1,26 @@ +// +// This file is auto-generated. Please don't modify it! +// +#pragma once + +#ifdef __cplusplus +#import "opencv.hpp" +$additionalImports +#endif + +#import + +$forwardDeclarations + +$enumDeclarations + +NS_ASSUME_NONNULL_BEGIN + +$docs +@interface $module : $base + +$methodDeclarations + +@end + +NS_ASSUME_NONNULL_END diff --git a/modules/objc/test/cmakelists.template b/modules/objc/test/cmakelists.template new file mode 100644 index 0000000000..48578645b8 --- /dev/null +++ b/modules/objc/test/cmakelists.template @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.15) + +project(OpenCVTest) + +enable_testing() + +find_package(XCTest REQUIRED) + +# Enable C++11 +set (CMAKE_CXX_STANDARD 11) +set (CMAKE_CXX_STANDARD_REQUIRED TRUE) + +set (OBJC_COMPILE_FLAGS "-fobjc-arc -fobjc-weak -fvisibility=hidden") +set (SUPPRESS_WARNINGS_FLAGS "-Wno-incomplete-umbrella") +set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OBJC_COMPILE_FLAGS} ${SUPPRESS_WARNINGS_FLAGS}") +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OBJC_ARC_COMPILE_FLAGS} ${SUPPRESS_WARNINGS_FLAGS}") + +# grab the files +file(GLOB_RECURSE test_sources "test/*\.h" "test/*\.m" "test/*\.mm" "test/*\.swift") + +add_library(OpenCVTest STATIC dummy/dummy.mm) + +enable_language(Swift) + +# XCTest for Framework +xctest_add_bundle(OpenCVTestTests OpenCVTest ${test_sources} test/resources ${FRAMEWORK_DIR}/${FRAMEWORK_NAME}.framework) +set_target_properties(OpenCVTestTests PROPERTIES + XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS "${FRAMEWORK_DIR}" + XCODE_ATTRIBUTE_OTHER_SWIFT_FLAGS "-Xcc ${SUPPRESS_WARNINGS_FLAGS}" + ) + +# link necessary Frameworks +target_link_libraries(OpenCVTestTests PRIVATE "-framework Accelerate") +target_link_libraries(OpenCVTestTests PRIVATE "-framework AVFoundation") +target_link_libraries(OpenCVTestTests PRIVATE "-framework CoreMedia") +if (NOT DEFINED IOS_ARCH) + target_link_libraries(OpenCVTestTests PRIVATE "-framework OpenCL") +endif() + +# its OK to ignore stuff we don't know about since there will be no tests relying on it +set_target_properties(OpenCVTestTests PROPERTIES LINK_FLAGS -Wl,-undefined,dynamic_lookup) + +set_source_files_properties(test/resources PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + +xctest_add_test(XCTest.OpenCVTest OpenCVTestTests) diff --git a/modules/objc/test/dummy/dummy.mm b/modules/objc/test/dummy/dummy.mm new file mode 100644 index 0000000000..a35e92fe25 --- /dev/null +++ b/modules/objc/test/dummy/dummy.mm @@ -0,0 +1,8 @@ +// +// dummy.mm +// +// Created by Giles Payne on 2019/10/13. +// + +void dummy() { +} diff --git a/modules/objc/test/test/OpenCVTestCase.swift b/modules/objc/test/test/OpenCVTestCase.swift new file mode 100644 index 0000000000..8712fd18aa --- /dev/null +++ b/modules/objc/test/test/OpenCVTestCase.swift @@ -0,0 +1,226 @@ +// +// OpenCVTestCase.swift +// +// Created by Giles Payne on 2020/01/19. +// + +import XCTest +import OpenCV + +enum OpenCVTestError: Error { + case unsupportedOperationError(String) +} + +open class OpenCVTestCase: XCTestCase { + + //change to 'true' to unblock fail on fail("Not yet implemented") + static let passNYI = true + + static let isTestCaseEnabled = true + + static let XFEATURES2D = "xfeatures2d" + static let DEFAULT_FACTORY = "create" + + static let matSize: Int32 = 10 + static let EPS = 0.001 + static let FEPS:Float = 0.001 + static let weakEPS = 0.5 + static let weakFEPS:Float = 0.5 + + var dst: Mat = Mat() + var truth: Mat? = nil + + let colorBlack = Scalar(0) + let colorWhite = Scalar(255, 255, 255) + + // Naming notation: _[depth]_[dimensions]_value + // examples: gray0 - single channel 8U 2d Mat filled with 0 + // grayRnd - single channel 8U 2d Mat filled with random numbers + // gray0_32f_1d + + let gray0 = Mat(rows:matSize, cols:matSize, type:CvType.CV_8U, scalar:Scalar(0)) + let gray1 = Mat(rows:matSize, cols:matSize, type: CvType.CV_8U, scalar: Scalar(1)) + let gray2 = Mat(rows:matSize, cols:matSize, type: CvType.CV_8U, scalar: Scalar(2)) + let gray3 = Mat(rows:matSize, cols:matSize, type: CvType.CV_8U, scalar: Scalar(3)) + let gray9 = Mat(rows:matSize, cols:matSize, type: CvType.CV_8U, scalar: Scalar(9)) + let gray127 = Mat(rows:matSize, cols:matSize, type: CvType.CV_8U, scalar: Scalar(127)) + let gray128 = Mat(rows:matSize, cols:matSize, type: CvType.CV_8U, scalar: Scalar(128)) + let gray255 = Mat(rows:matSize, cols:matSize, type: CvType.CV_8U, scalar: Scalar(255)) + let grayRnd = Mat(rows:matSize, cols:matSize, type: CvType.CV_8U) + + let gray_16u_256 = Mat(rows: matSize, cols: matSize, type: CvType.CV_16U, scalar: Scalar(256)) + let gray_16s_1024 = Mat(rows: matSize, cols: matSize, type: CvType.CV_16S, scalar: Scalar(1024)) + + let gray0_32f = Mat(rows: matSize, cols: matSize, type: CvType.CV_32F, scalar: Scalar(0.0)) + let gray1_32f = Mat(rows: matSize, cols: matSize, type: CvType.CV_32F, scalar: Scalar(1.0)) + let gray3_32f = Mat(rows: matSize, cols: matSize, type: CvType.CV_32F, scalar: Scalar(3.0)) + let gray9_32f = Mat(rows: matSize, cols: matSize, type: CvType.CV_32F, scalar: Scalar(9.0)) + let gray255_32f = Mat(rows: matSize, cols: matSize, type: CvType.CV_32F, scalar: Scalar(255.0)) + let grayE_32f = Mat.eye(rows: matSize, cols: matSize, type: CvType.CV_32FC1) + let grayRnd_32f = Mat(rows: matSize, cols: matSize, type: CvType.CV_32F) + + let gray0_64f = Mat(rows: matSize, cols: matSize, type: CvType.CV_64F, scalar: Scalar(0.0)) + let gray0_32f_1d = Mat(rows: 1, cols: matSize, type: CvType.CV_32F, scalar: Scalar(0.0)) + let gray0_64f_1d = Mat(rows: 1, cols: matSize, type: CvType.CV_64F, scalar: Scalar(0.0)) + + let rgba0 = Mat(rows: matSize, cols: matSize, type: CvType.CV_8UC4, scalar: Scalar.all(0)) + let rgba128 = Mat(rows: matSize, cols: matSize, type: CvType.CV_8UC4, scalar: Scalar.all(128)) + + let rgbLena: Mat = { + return Imgcodecs.imread(filename: Bundle(for: OpenCVTestCase.self).path(forResource:"lena", ofType:"png", inDirectory:"resources")!) + }() + + let grayChess: Mat = { + return Imgcodecs.imread(filename: Bundle(for: OpenCVTestCase.self).path(forResource:"chessboard", ofType:"jpg", inDirectory:"resources")!, flags: ImreadModes.IMREAD_GRAYSCALE.rawValue) + }() + + let gray255_32f_3d = Mat(sizes: [matSize, matSize, matSize] as [NSNumber], type: CvType.CV_32F, scalar: Scalar(255.0)) + + let v1 = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + let v2 = Mat(rows: 1, cols: 3, type: CvType.CV_32F) + + override open func setUp() { + //Core.setErrorVerbosity(false) + Core.randu(dst: grayRnd, low: 0, high: 255) + Core.randu(dst: grayRnd_32f, low:0, high: 255) + do { + try v1.put(row: 0,col: 0, data: [1.0, 3.0, 2.0]) + try v2.put(row: 0,col: 0, data: [2.0, 1.0, 3.0]) + } catch { + print("Fatal error in start-up") + } + } + + override open func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func assertMatEqual(_ expected:Mat, _ actual:Mat, file: StaticString = #file, line: UInt = #line) throws { + try compareMats(expected, actual, true, file:file, line:line) + } + + func assertMatNotEqual(_ expected:Mat, _ actual:Mat, file: StaticString = #file, line: UInt = #line) throws { + try compareMats(expected, actual, false, file:file, line:line) + } + + func assertMatEqual(_ expected:Mat, _ actual:Mat, _ eps:Double, file: StaticString = #file, line: UInt = #line) throws { + try compareMats(expected, actual, eps, true, file:file, line:line); + } + + func assertMatNotEqual(_ expected:Mat, _ actual:Mat, _ eps:Double, file: StaticString = #file, line: UInt = #line) throws { + try compareMats(expected, actual, eps, false, file:file, line:line); + } + + func assertSizeEquals(_ expected:Size,_ actual: Size, file: StaticString = #file, line: UInt = #line) { + let msg = "expected:<\(expected)> but was:<\(actual)>" + XCTAssertEqual(expected.width, actual.width, msg, file:file, line:line) + XCTAssertEqual(expected.height, actual.height, msg, file:file, line:line) + } + + func assertSize2fEquals(_ expected:Size2f,_ actual: Size2f,_ eps: Float, file: StaticString = #file, line: UInt = #line) { + let msg = "expected:<\(expected)> but was:<\(actual)>" + XCTAssertEqual(expected.width, actual.width, accuracy:eps, msg, file:file, line:line) + XCTAssertEqual(expected.height, actual.height, accuracy:eps, msg, file:file, line:line) + } + + func assertSize2dEquals(_ expected:Size2d,_ actual: Size2d,_ eps: Double, file: StaticString = #file, line: UInt = #line) { + let msg = "expected:<\(expected)> but was:<\(actual)>" + XCTAssertEqual(expected.width, actual.width, accuracy:eps, msg, file:file, line:line) + XCTAssertEqual(expected.height, actual.height, accuracy:eps, msg, file:file, line:line) + } + + func assertPointEquals(_ expected:Point, _ actual: Point, file: StaticString = #file, line: UInt = #line) { + let msg = "expected:<\(expected)> but was:<\(actual)>" + XCTAssertEqual(expected.x, actual.x, msg, file:file, line:line) + XCTAssertEqual(expected.y, actual.y, msg, file:file, line:line) + } + + func assertPoint2dEquals(_ expected:Point2d, _ actual: Point2d, _ eps: Double, file: StaticString = #file, line: UInt = #line) { + let msg = "expected:<\(expected)> but was:<\(actual)>" + XCTAssertEqual(expected.x, actual.x, accuracy:eps, msg, file:file, line:line) + XCTAssertEqual(expected.y, actual.y, accuracy:eps, msg, file:file, line:line) + } + + func assertPoint2fEquals(_ expected:Point2f, _ actual: Point2f, _ eps: Float, file: StaticString = #file, line: UInt = #line) { + let msg = "expected:<\(expected)> but was:<\(actual)>" + XCTAssertEqual(expected.x, actual.x, accuracy:eps, msg, file:file, line:line) + XCTAssertEqual(expected.y, actual.y, accuracy:eps, msg, file:file, line:line) + } + + func assertScalarEqual(_ expected:Scalar, _ actual:Scalar, _ eps: Double, file: StaticString = #file, line: UInt = #line) { + let msg = "expected:<\(expected)> but was:<\(actual)>" + XCTAssertEqual(expected.val[0] as! Double, actual.val[0] as! Double, accuracy:eps, msg, file:file, line:line) + XCTAssertEqual(expected.val[1] as! Double, actual.val[1] as! Double, accuracy:eps, msg, file:file, line:line) + XCTAssertEqual(expected.val[2] as! Double, actual.val[2] as! Double, accuracy:eps, msg, file:file, line:line) + XCTAssertEqual(expected.val[3] as! Double, actual.val[3] as! Double, accuracy:eps, msg, file:file, line:line) + } + + func assertArrayEquals(_ expected:[NSNumber], _ actual:[NSNumber], _ eps: Double, file: StaticString = #file, line: UInt = #line) { + XCTAssertEqual(expected.count, actual.count, "Arrays have different sizes.", file:file, line:line) + + for i in 0.. Bool { + if expected.dims() != actual.dims() { + return false + } + if expected.dims() > 2 { + return (0.. Mat { + return Mat(rows:OpenCVTestCase.matSize, cols:OpenCVTestCase.matSize, type:type, scalar:Scalar(vals: vals)) + } + + func makeMask(_ mat:Mat, vals:[Double] = []) -> Mat { + mat.submat(rowStart: 0, rowEnd: mat.rows(), colStart: 0, colEnd: mat.cols() / 2).setTo(scalar: Scalar(vals: vals as [NSNumber])) + return mat + } +} diff --git a/modules/objdetect/CMakeLists.txt b/modules/objdetect/CMakeLists.txt index 414e578099..3fa0c5d33b 100644 --- a/modules/objdetect/CMakeLists.txt +++ b/modules/objdetect/CMakeLists.txt @@ -1,5 +1,5 @@ set(the_description "Object Detection") -ocv_define_module(objdetect opencv_core opencv_imgproc opencv_calib3d WRAP java python js) +ocv_define_module(objdetect opencv_core opencv_imgproc opencv_calib3d WRAP java objc python js) if(HAVE_QUIRC) get_property(QUIRC_INCLUDE GLOBAL PROPERTY QUIRC_INCLUDE_DIR) diff --git a/modules/photo/CMakeLists.txt b/modules/photo/CMakeLists.txt index 15de722f15..34e3741601 100644 --- a/modules/photo/CMakeLists.txt +++ b/modules/photo/CMakeLists.txt @@ -4,4 +4,4 @@ if(HAVE_CUDA) ocv_warnings_disable(CMAKE_CXX_FLAGS -Wundef -Wmissing-declarations -Wshadow) endif() -ocv_define_module(photo opencv_imgproc OPTIONAL opencv_cudaarithm opencv_cudaimgproc WRAP java python js) +ocv_define_module(photo opencv_imgproc OPTIONAL opencv_cudaarithm opencv_cudaimgproc WRAP java objc python js) diff --git a/modules/photo/misc/objc/gen_dict.json b/modules/photo/misc/objc/gen_dict.json new file mode 100644 index 0000000000..bcec59f0e1 --- /dev/null +++ b/modules/photo/misc/objc/gen_dict.json @@ -0,0 +1,6 @@ +{ + "func_arg_fix" : { + "(void)fastNlMeansDenoising:(Mat*)src dst:(Mat*)dst h:(FloatVector*)h templateWindowSize:(int)templateWindowSize searchWindowSize:(int)searchWindowSize normType:(int)normType" : { "h" : { "name" : "hVector" } }, + "(void)fastNlMeansDenoisingMulti:(NSArray*)srcImgs dst:(Mat*)dst imgToDenoiseIndex:(int)imgToDenoiseIndex temporalWindowSize:(int)temporalWindowSize h:(FloatVector*)h templateWindowSize:(int)templateWindowSize searchWindowSize:(int)searchWindowSize normType:(int)normType" : { "h" : { "name" : "hVector" } } + } +} diff --git a/modules/video/CMakeLists.txt b/modules/video/CMakeLists.txt index 015c95ca96..e25f0b7e0e 100644 --- a/modules/video/CMakeLists.txt +++ b/modules/video/CMakeLists.txt @@ -1,2 +1,2 @@ set(the_description "Video Analysis") -ocv_define_module(video opencv_imgproc OPTIONAL opencv_calib3d WRAP java python js) +ocv_define_module(video opencv_imgproc OPTIONAL opencv_calib3d WRAP java objc python js) diff --git a/modules/videoio/CMakeLists.txt b/modules/videoio/CMakeLists.txt index bf82bb0eb1..12ff992294 100644 --- a/modules/videoio/CMakeLists.txt +++ b/modules/videoio/CMakeLists.txt @@ -10,7 +10,7 @@ if(NOT TARGET opencv_videoio_plugins) add_custom_target(opencv_videoio_plugins ALL) endif() -ocv_add_module(videoio opencv_imgproc opencv_imgcodecs WRAP java python) +ocv_add_module(videoio opencv_imgproc opencv_imgcodecs WRAP java objc python) set(videoio_hdrs ${CMAKE_CURRENT_LIST_DIR}/src/precomp.hpp) diff --git a/modules/videoio/misc/objc/gen_dict.json b/modules/videoio/misc/objc/gen_dict.json new file mode 100644 index 0000000000..902856b0fe --- /dev/null +++ b/modules/videoio/misc/objc/gen_dict.json @@ -0,0 +1,17 @@ +{ + "AdditionalImports" : { + "Videoio" : + [ "\"videoio/registry.hpp\"" ] + }, + "ManualFuncs" : { + "VideoCapture" : { + "release" : {"declaration" : [""], "implementation" : [""] } + }, + "VideoWriter" : { + "release" : {"declaration" : [""], "implementation" : [""] } + } + }, + "func_arg_fix" : { + "(BOOL)open:(int)index apiPreference:(int)apiPreference" : { "open" : {"name" : "openWithIndex"} } + } +} diff --git a/modules/videoio/misc/objc/ios/CvAbstractCamera2.mm b/modules/videoio/misc/objc/ios/CvAbstractCamera2.mm new file mode 100644 index 0000000000..0aebc13e82 --- /dev/null +++ b/modules/videoio/misc/objc/ios/CvAbstractCamera2.mm @@ -0,0 +1,445 @@ +// +// CvAbstractCamera2.mm +// +// Created by Giles Payne on 2020/04/01. +// + +#import "CvCamera2.h" + +#pragma mark - Private Interface + +@interface CvAbstractCamera2 () + +@property (nonatomic, strong) AVCaptureVideoPreviewLayer* captureVideoPreviewLayer; + +- (void)deviceOrientationDidChange:(NSNotification*)notification; +- (void)startCaptureSession; + +- (void)setDesiredCameraPosition:(AVCaptureDevicePosition)desiredPosition; + +- (void)updateSize; + +@end + + +#pragma mark - Implementation + + +@implementation CvAbstractCamera2 + +#pragma mark - Constructors + +- (id)init; +{ + self = [super init]; + if (self) { + // react to device orientation notifications + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(deviceOrientationDidChange:) + name:UIDeviceOrientationDidChangeNotification + object:nil]; + [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; + self.currentDeviceOrientation = [[UIDevice currentDevice] orientation]; + + + // check if camera available + self.cameraAvailable = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]; + NSLog(@"camera available: %@", (self.cameraAvailable ? @"YES" : @"NO") ); + + _running = NO; + + // set camera default configuration + self.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront; + self.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationLandscapeLeft; + self.defaultFPS = 15; + self.defaultAVCaptureSessionPreset = AVCaptureSessionPreset352x288; + + self.parentView = nil; + self.useAVCaptureVideoPreviewLayer = NO; + } + return self; +} + + + +- (id)initWithParentView:(UIView*)parent; +{ + self = [super init]; + if (self) { + // react to device orientation notifications + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(deviceOrientationDidChange:) + name:UIDeviceOrientationDidChangeNotification + object:nil]; + [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; + self.currentDeviceOrientation = [[UIDevice currentDevice] orientation]; + + + // check if camera available + self.cameraAvailable = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]; + NSLog(@"camera available: %@", (self.cameraAvailable ? @"YES" : @"NO") ); + + _running = NO; + + // set camera default configuration + self.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront; + self.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationLandscapeLeft; + self.defaultFPS = 15; + self.defaultAVCaptureSessionPreset = AVCaptureSessionPreset640x480; + + self.parentView = parent; + self.useAVCaptureVideoPreviewLayer = YES; + } + return self; +} + +- (void)dealloc; +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; +} + +#pragma mark - Public interface + + +- (void)start; +{ + if (![NSThread isMainThread]) { + NSLog(@"[Camera] Warning: Call start only from main thread"); + [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO]; + return; + } + + if (self.running == YES) { + return; + } + _running = YES; + + // TODO: update image size data before actually starting (needed for recording) + [self updateSize]; + + if (self.cameraAvailable) { + [self startCaptureSession]; + } +} + + +- (void)pause; +{ + _running = NO; + [self.captureSession stopRunning]; +} + + + +- (void)stop; +{ + _running = NO; + + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; + if (self.captureSession) { + for (AVCaptureInput *input in self.captureSession.inputs) { + [self.captureSession removeInput:input]; + } + + for (AVCaptureOutput *output in self.captureSession.outputs) { + [self.captureSession removeOutput:output]; + } + + [self.captureSession stopRunning]; + } + + _captureSessionLoaded = NO; +} + + + +// use front/back camera +- (void)switchCameras; +{ + BOOL was_running = self.running; + if (was_running) { + [self stop]; + } + if (self.defaultAVCaptureDevicePosition == AVCaptureDevicePositionFront) { + self.defaultAVCaptureDevicePosition = AVCaptureDevicePositionBack; + } else { + self.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront; + } + if (was_running) { + [self start]; + } +} + + + +#pragma mark - Device Orientation Changes + + +- (void)deviceOrientationDidChange:(NSNotification*)notification +{ + (void)notification; + UIDeviceOrientation orientation = [UIDevice currentDevice].orientation; + + switch (orientation) + { + case UIDeviceOrientationPortrait: + case UIDeviceOrientationPortraitUpsideDown: + case UIDeviceOrientationLandscapeLeft: + case UIDeviceOrientationLandscapeRight: + self.currentDeviceOrientation = orientation; + break; + + case UIDeviceOrientationFaceUp: + case UIDeviceOrientationFaceDown: + default: + break; + } + NSLog(@"deviceOrientationDidChange: %d", (int)orientation); + + [self updateOrientation]; +} + + + +#pragma mark - Private Interface + +- (void)createCaptureSession; +{ + // set a av capture session preset + self.captureSession = [[AVCaptureSession alloc] init]; + if ([self.captureSession canSetSessionPreset:self.defaultAVCaptureSessionPreset]) { + [self.captureSession setSessionPreset:self.defaultAVCaptureSessionPreset]; + } else if ([self.captureSession canSetSessionPreset:AVCaptureSessionPresetLow]) { + [self.captureSession setSessionPreset:AVCaptureSessionPresetLow]; + } else { + NSLog(@"[Camera] Error: could not set session preset"); + } +} + +- (void)createCaptureDevice; +{ + // setup the device + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + [self setDesiredCameraPosition:self.defaultAVCaptureDevicePosition]; + NSLog(@"[Camera] device connected? %@", device.connected ? @"YES" : @"NO"); + NSLog(@"[Camera] device position %@", (device.position == AVCaptureDevicePositionBack) ? @"back" : @"front"); +} + + +- (void)createVideoPreviewLayer; +{ + self.captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession]; + + if ([self.captureVideoPreviewLayer.connection isVideoOrientationSupported]) + { + [self.captureVideoPreviewLayer.connection setVideoOrientation:self.defaultAVCaptureVideoOrientation]; + } + + if (self.parentView != nil) { + self.captureVideoPreviewLayer.frame = self.parentView.bounds; + self.captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; + [self.parentView.layer addSublayer:self.captureVideoPreviewLayer]; + } + NSLog(@"[Camera] created AVCaptureVideoPreviewLayer"); +} + +- (void)setDesiredCameraPosition:(AVCaptureDevicePosition)desiredPosition; +{ + for (AVCaptureDevice *device in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) { + if ([device position] == desiredPosition) { + [self.captureSession beginConfiguration]; + + NSError* error = nil; + AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; + if (!input) { + NSLog(@"error creating input %@", [error description]); + } + + // support for autofocus + if ([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) { + error = nil; + if ([device lockForConfiguration:&error]) { + device.focusMode = AVCaptureFocusModeContinuousAutoFocus; + [device unlockForConfiguration]; + } else { + NSLog(@"unable to lock device for autofocus configuration %@", [error description]); + } + } + [self.captureSession addInput:input]; + + for (AVCaptureInput *oldInput in self.captureSession.inputs) { + [self.captureSession removeInput:oldInput]; + } + [self.captureSession addInput:input]; + [self.captureSession commitConfiguration]; + + break; + } + } +} + + + +- (void)startCaptureSession +{ + if (!self.cameraAvailable) { + return; + } + + if (self.captureSessionLoaded == NO) { + [self createCaptureSession]; + [self createCaptureDevice]; + [self createCaptureOutput]; + + // setup preview layer + if (self.useAVCaptureVideoPreviewLayer) { + [self createVideoPreviewLayer]; + } else { + [self createCustomVideoPreview]; + } + + _captureSessionLoaded = YES; + } + + [self.captureSession startRunning]; +} + + +- (void)createCaptureOutput; +{ + [NSException raise:NSInternalInconsistencyException + format:@"You must override %s in a subclass", __FUNCTION__]; +} + +- (void)createCustomVideoPreview; +{ + [NSException raise:NSInternalInconsistencyException + format:@"You must override %s in a subclass", __FUNCTION__]; +} + +- (void)updateOrientation; +{ + // nothing to do here +} + + +- (void)updateSize; +{ + if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPresetPhoto]) { + //TODO: find the correct resolution + self.imageWidth = 640; + self.imageHeight = 480; + } else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPresetHigh]) { + //TODO: find the correct resolution + self.imageWidth = 640; + self.imageHeight = 480; + } else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPresetMedium]) { + //TODO: find the correct resolution + self.imageWidth = 640; + self.imageHeight = 480; + } else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPresetLow]) { + //TODO: find the correct resolution + self.imageWidth = 640; + self.imageHeight = 480; + } else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPreset352x288]) { + self.imageWidth = 352; + self.imageHeight = 288; + } else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPreset640x480]) { + self.imageWidth = 640; + self.imageHeight = 480; + } else if ([self.defaultAVCaptureSessionPreset isEqualToString:AVCaptureSessionPreset1280x720]) { + self.imageWidth = 1280; + self.imageHeight = 720; + } else { + self.imageWidth = 640; + self.imageHeight = 480; + } +} + +- (void)lockFocus; +{ + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if ([device isFocusModeSupported:AVCaptureFocusModeLocked]) { + NSError *error = nil; + if ([device lockForConfiguration:&error]) { + device.focusMode = AVCaptureFocusModeLocked; + [device unlockForConfiguration]; + } else { + NSLog(@"unable to lock device for locked focus configuration %@", [error description]); + } + } +} + +- (void) unlockFocus; +{ + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if ([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) { + NSError *error = nil; + if ([device lockForConfiguration:&error]) { + device.focusMode = AVCaptureFocusModeContinuousAutoFocus; + [device unlockForConfiguration]; + } else { + NSLog(@"unable to lock device for autofocus configuration %@", [error description]); + } + } +} + +- (void)lockExposure; +{ + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if ([device isExposureModeSupported:AVCaptureExposureModeLocked]) { + NSError *error = nil; + if ([device lockForConfiguration:&error]) { + device.exposureMode = AVCaptureExposureModeLocked; + [device unlockForConfiguration]; + } else { + NSLog(@"unable to lock device for locked exposure configuration %@", [error description]); + } + } +} + +- (void) unlockExposure; +{ + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if ([device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) { + NSError *error = nil; + if ([device lockForConfiguration:&error]) { + device.exposureMode = AVCaptureExposureModeContinuousAutoExposure; + [device unlockForConfiguration]; + } else { + NSLog(@"unable to lock device for autoexposure configuration %@", [error description]); + } + } +} + +- (void)lockBalance; +{ + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if ([device isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeLocked]) { + NSError *error = nil; + if ([device lockForConfiguration:&error]) { + device.whiteBalanceMode = AVCaptureWhiteBalanceModeLocked; + [device unlockForConfiguration]; + } else { + NSLog(@"unable to lock device for locked white balance configuration %@", [error description]); + } + } +} + +- (void) unlockBalance; +{ + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if ([device isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance]) { + NSError *error = nil; + if ([device lockForConfiguration:&error]) { + device.whiteBalanceMode = AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance; + [device unlockForConfiguration]; + } else { + NSLog(@"unable to lock device for auto white balance configuration %@", [error description]); + } + } +} + +@end diff --git a/modules/videoio/misc/objc/ios/CvCamera2.h b/modules/videoio/misc/objc/ios/CvCamera2.h new file mode 100644 index 0000000000..0127116f86 --- /dev/null +++ b/modules/videoio/misc/objc/ios/CvCamera2.h @@ -0,0 +1,84 @@ +// +// CvCamera2.h +// +// Created by Giles Payne on 2020/03/11. +// + +#import +#import +#import +#import + +@class Mat; + +@class CvAbstractCamera2; + +@interface CvAbstractCamera2 : NSObject + +@property UIDeviceOrientation currentDeviceOrientation; +@property BOOL cameraAvailable; +@property (nonatomic, strong) AVCaptureSession* captureSession; +@property (nonatomic, strong) AVCaptureConnection* videoCaptureConnection; + +@property (nonatomic, readonly) BOOL running; +@property (nonatomic, readonly) BOOL captureSessionLoaded; + +@property (nonatomic, assign) int defaultFPS; +@property (nonatomic, readonly) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer; +@property (nonatomic, assign) AVCaptureDevicePosition defaultAVCaptureDevicePosition; +@property (nonatomic, assign) AVCaptureVideoOrientation defaultAVCaptureVideoOrientation; +@property (nonatomic, assign) BOOL useAVCaptureVideoPreviewLayer; +@property (nonatomic, strong) NSString *const defaultAVCaptureSessionPreset; +@property (nonatomic, assign) int imageWidth; +@property (nonatomic, assign) int imageHeight; +@property (nonatomic, strong) UIView* parentView; + +- (void)start; +- (void)stop; +- (void)switchCameras; +- (id)initWithParentView:(UIView*)parent; +- (void)createCaptureOutput; +- (void)createVideoPreviewLayer; +- (void)updateOrientation; +- (void)lockFocus; +- (void)unlockFocus; +- (void)lockExposure; +- (void)unlockExposure; +- (void)lockBalance; +- (void)unlockBalance; +@end + +///////////////////////////////// CvVideoCamera /////////////////////////////////////////// +@class CvVideoCamera2; + +@protocol CvVideoCameraDelegate2 +- (void)processImage:(Mat*)image; +@end + +@interface CvVideoCamera2 : CvAbstractCamera2 +@property (nonatomic, weak) id delegate; +@property (nonatomic, assign) BOOL grayscaleMode; +@property (nonatomic, assign) BOOL recordVideo; +@property (nonatomic, assign) BOOL rotateVideo; +@property (nonatomic, strong) AVAssetWriterInput* recordAssetWriterInput; +@property (nonatomic, strong) AVAssetWriterInputPixelBufferAdaptor* recordPixelBufferAdaptor; +@property (nonatomic, strong) AVAssetWriter* recordAssetWriter; +- (void)adjustLayoutToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation; +- (void)layoutPreviewLayer; +- (void)saveVideo; +- (NSURL *)videoFileURL; +- (NSString *)videoFileString; +@end + +///////////////////////////////// CvPhotoCamera /////////////////////////////////////////// +@class CvPhotoCamera2; + +@protocol CvPhotoCameraDelegate2 +- (void)photoCamera:(CvPhotoCamera2*)photoCamera capturedImage:(UIImage*)image; +- (void)photoCameraCancel:(CvPhotoCamera2*)photoCamera; +@end + +@interface CvPhotoCamera2 : CvAbstractCamera2 +@property (nonatomic, weak) id delegate; +- (void)takePicture; +@end diff --git a/modules/videoio/misc/objc/ios/CvPhotoCamera2.m b/modules/videoio/misc/objc/ios/CvPhotoCamera2.m new file mode 100644 index 0000000000..460cce6d32 --- /dev/null +++ b/modules/videoio/misc/objc/ios/CvPhotoCamera2.m @@ -0,0 +1,138 @@ +// +// CvPhotoCamera2.mm +// +// Created by Giles Payne on 2020/04/01. +// + +#import "CvCamera2.h" + +#pragma mark - Private Interface + +@interface CvPhotoCamera2 () +{ + id _delegate; +} + +@property (nonatomic, strong) AVCaptureStillImageOutput* stillImageOutput; + +@end + + +#pragma mark - Implementation + +@implementation CvPhotoCamera2 + + +#pragma mark Public + +- (void)setDelegate:(id)newDelegate { + _delegate = newDelegate; +} + +- (id)delegate { + return _delegate; +} + +#pragma mark - Public interface + +- (void)takePicture +{ + if (self.cameraAvailable == NO) { + return; + } + self.cameraAvailable = NO; + + [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:self.videoCaptureConnection + completionHandler: + ^(CMSampleBufferRef imageSampleBuffer, NSError *error) + { + if (error == nil && imageSampleBuffer != NULL) + { + // TODO check + // NSNumber* imageOrientation = [UIImage cgImageOrientationForUIDeviceOrientation:currentDeviceOrientation]; + // CMSetAttachment(imageSampleBuffer, kCGImagePropertyOrientation, imageOrientation, 1); + + NSData *jpegData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self.captureSession stopRunning]; + + // Make sure we create objects on the main thread in the main context + UIImage* newImage = [UIImage imageWithData:jpegData]; + + //UIImageOrientation orientation = [newImage imageOrientation]; + + // TODO: only apply rotation, don't scale, since we can set this directly in the camera + /* + switch (orientation) { + case UIImageOrientationUp: + case UIImageOrientationDown: + newImage = [newImage imageWithAppliedRotationAndMaxSize:CGSizeMake(640.0, 480.0)]; + break; + case UIImageOrientationLeft: + case UIImageOrientationRight: + newImage = [newImage imageWithMaxSize:CGSizeMake(640.0, 480.0)]; + default: + break; + } + */ + + // We have captured the image, we can allow the user to take another picture + self.cameraAvailable = YES; + + NSLog(@"CvPhotoCamera2 captured image"); + [self.delegate photoCamera:self capturedImage:newImage]; + + [self.captureSession startRunning]; + }); + } + }]; + + +} + +- (void)stop; +{ + [super stop]; + self.stillImageOutput = nil; +} + + +#pragma mark - Private Interface + + +- (void)createStillImageOutput; +{ + // setup still image output with jpeg codec + self.stillImageOutput = [[AVCaptureStillImageOutput alloc] init]; + NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys:AVVideoCodecJPEG, AVVideoCodecKey, nil]; + [self.stillImageOutput setOutputSettings:outputSettings]; + [self.captureSession addOutput:self.stillImageOutput]; + + for (AVCaptureConnection *connection in self.stillImageOutput.connections) { + for (AVCaptureInputPort *port in [connection inputPorts]) { + if ([port.mediaType isEqual:AVMediaTypeVideo]) { + self.videoCaptureConnection = connection; + break; + } + } + if (self.videoCaptureConnection) { + break; + } + } + NSLog(@"[Camera] still image output created"); +} + + +- (void)createCaptureOutput; +{ + [self createStillImageOutput]; +} + +- (void)createCustomVideoPreview; +{ + //do nothing, always use AVCaptureVideoPreviewLayer +} + + +@end diff --git a/modules/videoio/misc/objc/ios/CvVideoCamera2.mm b/modules/videoio/misc/objc/ios/CvVideoCamera2.mm new file mode 100644 index 0000000000..7f4abdb578 --- /dev/null +++ b/modules/videoio/misc/objc/ios/CvVideoCamera2.mm @@ -0,0 +1,575 @@ +// +// CvVideoCamera2.mm +// +// Created by Giles Payne on 2020/03/11. +// + +#import "Mat.h" +#import "CvCamera2.h" +#import + +static CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;} + +#pragma mark - Private Interface + +@interface CvVideoCamera2 () { + int recordingCountDown; +} + +- (void)createVideoDataOutput; +- (void)createVideoFileOutput; + +@property (nonatomic, strong) CALayer *customPreviewLayer; +@property (nonatomic, strong) AVCaptureVideoDataOutput *videoDataOutput; + +@end + + +#pragma mark - Implementation + +@implementation CvVideoCamera2 +{ + id _delegate; + dispatch_queue_t videoDataOutputQueue; + CMTime lastSampleTime; +} + +- (void)setDelegate:(id)newDelegate { + _delegate = newDelegate; +} + +- (id)delegate { + return _delegate; +} + +#pragma mark - Constructors + +- (id)initWithParentView:(UIView*)parent { + self = [super initWithParentView:parent]; + if (self) { + parent.contentMode = UIViewContentModeScaleAspectFill; + self.useAVCaptureVideoPreviewLayer = NO; + self.recordVideo = NO; + self.rotateVideo = NO; + self.defaultAVCaptureDevicePosition = AVCaptureDevicePositionBack; + self.defaultAVCaptureSessionPreset = AVCaptureSessionPresetHigh; + self.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait; + self.defaultFPS = 30; + self.grayscaleMode = NO; + } + return self; +} + +#pragma mark - Public interface + + +- (void)start { + if (self.running == YES) { + return; + } + + recordingCountDown = 10; + [super start]; + + if (self.recordVideo == YES) { + NSError* error = nil; + if ([[NSFileManager defaultManager] fileExistsAtPath:[self videoFileString]]) { + [[NSFileManager defaultManager] removeItemAtPath:[self videoFileString] error:&error]; + } + if (error == nil) { + NSLog(@"[Camera] Delete file %@", [self videoFileString]); + } + } +} + +- (void)stop { + if (self.running == YES) { + [super stop]; + + if (self.recordVideo == YES) { + if (self.recordAssetWriter) { + if (self.recordAssetWriter.status == AVAssetWriterStatusWriting) { + [self.recordAssetWriter finishWritingWithCompletionHandler:^void() { + NSLog(@"[Camera] recording stopped"); + }]; + } else { + NSLog(@"[Camera] Recording Error: asset writer status is not writing"); + } + } + } + + if (self.customPreviewLayer) { + [self.customPreviewLayer removeFromSuperlayer]; + self.customPreviewLayer = nil; + } + } +} + +// TODO fix +- (void)adjustLayoutToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + NSLog(@"layout preview layer"); + if (self.parentView != nil) { + + CALayer* layer = self.customPreviewLayer; + CGRect bounds = self.customPreviewLayer.bounds; + int rotation_angle = 0; + bool flip_bounds = false; + + switch (interfaceOrientation) { + case UIInterfaceOrientationPortrait: + NSLog(@"to Portrait"); + rotation_angle = 270; + break; + case UIInterfaceOrientationPortraitUpsideDown: + rotation_angle = 90; + NSLog(@"to UpsideDown"); + break; + case UIInterfaceOrientationLandscapeLeft: + rotation_angle = 0; + NSLog(@"to LandscapeLeft"); + break; + case UIInterfaceOrientationLandscapeRight: + rotation_angle = 180; + NSLog(@"to LandscapeRight"); + break; + default: + break; // leave the layer in its last known orientation + } + + switch (self.defaultAVCaptureVideoOrientation) { + case AVCaptureVideoOrientationLandscapeRight: + rotation_angle += 180; + break; + case AVCaptureVideoOrientationPortraitUpsideDown: + rotation_angle += 270; + break; + case AVCaptureVideoOrientationPortrait: + rotation_angle += 90; + case AVCaptureVideoOrientationLandscapeLeft: + break; + default: + break; + } + rotation_angle = rotation_angle % 360; + + if (rotation_angle == 90 || rotation_angle == 270) { + flip_bounds = true; + } + + if (flip_bounds) { + NSLog(@"flip bounds"); + bounds = CGRectMake(0, 0, bounds.size.height, bounds.size.width); + } + + layer.position = CGPointMake(self.parentView.frame.size.width/2., self.parentView.frame.size.height/2.); + self.customPreviewLayer.bounds = CGRectMake(0, 0, self.parentView.frame.size.width, self.parentView.frame.size.height); + + layer.affineTransform = CGAffineTransformMakeRotation( DegreesToRadians(rotation_angle) ); + layer.bounds = bounds; + } + +} + +// TODO fix +- (void)layoutPreviewLayer { + NSLog(@"layout preview layer"); + if (self.parentView != nil) { + + CALayer* layer = self.customPreviewLayer; + CGRect bounds = self.customPreviewLayer.bounds; + int rotation_angle = 0; + bool flip_bounds = false; + + switch (self.currentDeviceOrientation) { + case UIDeviceOrientationPortrait: + rotation_angle = 270; + break; + case UIDeviceOrientationPortraitUpsideDown: + rotation_angle = 90; + break; + case UIDeviceOrientationLandscapeLeft: + NSLog(@"left"); + rotation_angle = 180; + break; + case UIDeviceOrientationLandscapeRight: + NSLog(@"right"); + rotation_angle = 0; + break; + case UIDeviceOrientationFaceUp: + case UIDeviceOrientationFaceDown: + default: + break; // leave the layer in its last known orientation + } + + switch (self.defaultAVCaptureVideoOrientation) { + case AVCaptureVideoOrientationLandscapeRight: + rotation_angle += 180; + break; + case AVCaptureVideoOrientationPortraitUpsideDown: + rotation_angle += 270; + break; + case AVCaptureVideoOrientationPortrait: + rotation_angle += 90; + case AVCaptureVideoOrientationLandscapeLeft: + break; + default: + break; + } + rotation_angle = rotation_angle % 360; + + if (rotation_angle == 90 || rotation_angle == 270) { + flip_bounds = true; + } + + if (flip_bounds) { + NSLog(@"flip bounds"); + bounds = CGRectMake(0, 0, bounds.size.height, bounds.size.width); + } + + layer.position = CGPointMake(self.parentView.frame.size.width/2., self.parentView.frame.size.height/2.); + layer.affineTransform = CGAffineTransformMakeRotation( DegreesToRadians(rotation_angle) ); + layer.bounds = bounds; + } + +} + +#pragma mark - Private Interface + +- (void)createVideoDataOutput { + // Make a video data output + self.videoDataOutput = [AVCaptureVideoDataOutput new]; + + // In grayscale mode we want YUV (YpCbCr 4:2:0) so we can directly access the graylevel intensity values (Y component) + // In color mode we, BGRA format is used + OSType format = self.grayscaleMode ? kCVPixelFormatType_420YpCbCr8BiPlanarFullRange : kCVPixelFormatType_32BGRA; + + self.videoDataOutput.videoSettings = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:format] + forKey:(id)kCVPixelBufferPixelFormatTypeKey]; + + // discard if the data output queue is blocked (as we process the still image) + [self.videoDataOutput setAlwaysDiscardsLateVideoFrames:YES]; + + if ( [self.captureSession canAddOutput:self.videoDataOutput] ) { + [self.captureSession addOutput:self.videoDataOutput]; + } + [[self.videoDataOutput connectionWithMediaType:AVMediaTypeVideo] setEnabled:YES]; + + + // set default FPS + AVCaptureDeviceInput *currentInput = [self.captureSession.inputs objectAtIndex:0]; + AVCaptureDevice *device = currentInput.device; + + NSError *error = nil; + [device lockForConfiguration:&error]; + + float maxRate = ((AVFrameRateRange*) [device.activeFormat.videoSupportedFrameRateRanges objectAtIndex:0]).maxFrameRate; + if (maxRate > self.defaultFPS - 1 && error == nil) { + [device setActiveVideoMinFrameDuration:CMTimeMake(1, self.defaultFPS)]; + [device setActiveVideoMaxFrameDuration:CMTimeMake(1, self.defaultFPS)]; + NSLog(@"[Camera] FPS set to %d", self.defaultFPS); + } else { + NSLog(@"[Camera] unable to set defaultFPS at %d FPS, max is %f FPS", self.defaultFPS, maxRate); + } + + if (error != nil) { + NSLog(@"[Camera] unable to set defaultFPS: %@", error); + } + + [device unlockForConfiguration]; + + // set video mirroring for front camera (more intuitive) + if ([self.videoDataOutput connectionWithMediaType:AVMediaTypeVideo].supportsVideoMirroring) { + if (self.defaultAVCaptureDevicePosition == AVCaptureDevicePositionFront) { + [self.videoDataOutput connectionWithMediaType:AVMediaTypeVideo].videoMirrored = YES; + } else { + [self.videoDataOutput connectionWithMediaType:AVMediaTypeVideo].videoMirrored = NO; + } + } + + // set default video orientation + if ([self.videoDataOutput connectionWithMediaType:AVMediaTypeVideo].supportsVideoOrientation) { + [self.videoDataOutput connectionWithMediaType:AVMediaTypeVideo].videoOrientation = self.defaultAVCaptureVideoOrientation; + } + + + // create a custom preview layer + self.customPreviewLayer = [CALayer layer]; + self.customPreviewLayer.bounds = CGRectMake(0, 0, self.parentView.frame.size.width, self.parentView.frame.size.height); + self.customPreviewLayer.position = CGPointMake(self.parentView.frame.size.width/2., self.parentView.frame.size.height/2.); + [self updateOrientation]; + + // create a serial dispatch queue used for the sample buffer delegate as well as when a still image is captured + // a serial dispatch queue must be used to guarantee that video frames will be delivered in order + // see the header doc for setSampleBufferDelegate:queue: for more information + videoDataOutputQueue = dispatch_queue_create("VideoDataOutputQueue", DISPATCH_QUEUE_SERIAL); + [self.videoDataOutput setSampleBufferDelegate:self queue:videoDataOutputQueue]; + + + NSLog(@"[Camera] created AVCaptureVideoDataOutput"); +} + +- (void)createVideoFileOutput { + /* Video File Output in H.264, via AVAsserWriter */ + NSLog(@"Create Video with dimensions %dx%d", self.imageWidth, self.imageHeight); + + NSDictionary *outputSettings + = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:self.imageWidth], AVVideoWidthKey, + [NSNumber numberWithInt:self.imageHeight], AVVideoHeightKey, + AVVideoCodecH264, AVVideoCodecKey, + nil + ]; + + + self.recordAssetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:outputSettings]; + + + int pixelBufferFormat = (self.grayscaleMode == YES) ? kCVPixelFormatType_420YpCbCr8BiPlanarFullRange : kCVPixelFormatType_32BGRA; + + self.recordPixelBufferAdaptor = + [[AVAssetWriterInputPixelBufferAdaptor alloc] + initWithAssetWriterInput:self.recordAssetWriterInput + sourcePixelBufferAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:pixelBufferFormat], kCVPixelBufferPixelFormatTypeKey, nil]]; + + NSError* error = nil; + NSLog(@"Create AVAssetWriter with url: %@", [self videoFileURL]); + self.recordAssetWriter = [AVAssetWriter assetWriterWithURL:[self videoFileURL] + fileType:AVFileTypeMPEG4 + error:&error]; + if (error != nil) { + NSLog(@"[Camera] Unable to create AVAssetWriter: %@", error); + } + + [self.recordAssetWriter addInput:self.recordAssetWriterInput]; + self.recordAssetWriterInput.expectsMediaDataInRealTime = YES; + + NSLog(@"[Camera] created AVAssetWriter"); +} + +- (void)createCaptureOutput { + [self createVideoDataOutput]; + if (self.recordVideo == YES) { + [self createVideoFileOutput]; + } +} + +- (void)createCustomVideoPreview { + [self.parentView.layer addSublayer:self.customPreviewLayer]; +} + +- (CVPixelBufferRef) pixelBufferFromCGImage: (CGImageRef) image { + + CGSize frameSize = CGSizeMake(CGImageGetWidth(image), CGImageGetHeight(image)); + NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:NO], kCVPixelBufferCGImageCompatibilityKey, + [NSNumber numberWithBool:NO], kCVPixelBufferCGBitmapContextCompatibilityKey, + nil]; + CVPixelBufferRef pxbuffer = NULL; + CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, frameSize.width, + frameSize.height, kCVPixelFormatType_32ARGB, (CFDictionaryRef) CFBridgingRetain(options), + &pxbuffer); + NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL); + + CVPixelBufferLockBaseAddress(pxbuffer, 0); + void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer); + + + CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(pxdata, frameSize.width, + frameSize.height, 8, 4*frameSize.width, rgbColorSpace, + kCGImageAlphaPremultipliedFirst); + + CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), + CGImageGetHeight(image)), image); + CGColorSpaceRelease(rgbColorSpace); + CGContextRelease(context); + + CVPixelBufferUnlockBaseAddress(pxbuffer, 0); + + return pxbuffer; +} + +#pragma mark - Protocol AVCaptureVideoDataOutputSampleBufferDelegate + +- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection +{ + (void)captureOutput; + (void)connection; + auto strongDelegate = self.delegate; + if (strongDelegate) { + + // convert from Core Media to Core Video + CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + CVPixelBufferLockBaseAddress(imageBuffer, 0); + + void* bufferAddress; + size_t width; + size_t height; + size_t bytesPerRow; + + CGColorSpaceRef colorSpace; + CGContextRef context; + + int format_opencv; + + OSType format = CVPixelBufferGetPixelFormatType(imageBuffer); + if (format == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) { + + format_opencv = CV_8UC1; + + bufferAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0); + width = CVPixelBufferGetWidthOfPlane(imageBuffer, 0); + height = CVPixelBufferGetHeightOfPlane(imageBuffer, 0); + bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0); + + } else { // expect kCVPixelFormatType_32BGRA + + format_opencv = CV_8UC4; + + bufferAddress = CVPixelBufferGetBaseAddress(imageBuffer); + width = CVPixelBufferGetWidth(imageBuffer); + height = CVPixelBufferGetHeight(imageBuffer); + bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); + + } + + // delegate image processing to the delegate + cv::Mat image((int)height, (int)width, format_opencv, bufferAddress, bytesPerRow); + + CGImage* dstImage; + + if ([strongDelegate respondsToSelector:@selector(processImage:)]) { + [strongDelegate processImage:[Mat fromNative:image]]; + } + + // check if matrix data pointer or dimensions were changed by the delegate + bool iOSimage = false; + if (height == (size_t)image.rows && width == (size_t)image.cols && format_opencv == image.type() && bufferAddress == image.data && bytesPerRow == image.step) { + iOSimage = true; + } + + + // (create color space, create graphics context, render buffer) + CGBitmapInfo bitmapInfo; + + // basically we decide if it's a grayscale, rgb or rgba image + if (image.channels() == 1) { + colorSpace = CGColorSpaceCreateDeviceGray(); + bitmapInfo = kCGImageAlphaNone; + } else if (image.channels() == 3) { + colorSpace = CGColorSpaceCreateDeviceRGB(); + bitmapInfo = kCGImageAlphaNone; + if (iOSimage) { + bitmapInfo |= kCGBitmapByteOrder32Little; + } else { + bitmapInfo |= kCGBitmapByteOrder32Big; + } + } else { + colorSpace = CGColorSpaceCreateDeviceRGB(); + bitmapInfo = kCGImageAlphaPremultipliedFirst; + if (iOSimage) { + bitmapInfo |= kCGBitmapByteOrder32Little; + } else { + bitmapInfo |= kCGBitmapByteOrder32Big; + } + } + + if (iOSimage) { + context = CGBitmapContextCreate(bufferAddress, width, height, 8, bytesPerRow, colorSpace, bitmapInfo); + dstImage = CGBitmapContextCreateImage(context); + CGContextRelease(context); + } else { + + NSData *data = [NSData dataWithBytes:image.data length:image.elemSize()*image.total()]; + CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data); + + // Creating CGImage from cv::Mat + dstImage = CGImageCreate(image.cols, // width + image.rows, // height + 8, // bits per component + 8 * image.elemSize(), // bits per pixel + image.step, // bytesPerRow + colorSpace, // colorspace + bitmapInfo, // bitmap info + provider, // CGDataProviderRef + NULL, // decode + false, // should interpolate + kCGRenderingIntentDefault // intent + ); + + CGDataProviderRelease(provider); + } + + + // render buffer + dispatch_sync(dispatch_get_main_queue(), ^{ + self.customPreviewLayer.contents = (__bridge id)dstImage; + }); + + + recordingCountDown--; + if (self.recordVideo == YES && recordingCountDown < 0) { + lastSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); +// CMTimeShow(lastSampleTime); + if (self.recordAssetWriter.status != AVAssetWriterStatusWriting) { + [self.recordAssetWriter startWriting]; + [self.recordAssetWriter startSessionAtSourceTime:lastSampleTime]; + if (self.recordAssetWriter.status != AVAssetWriterStatusWriting) { + NSLog(@"[Camera] Recording Error: asset writer status is not writing: %@", self.recordAssetWriter.error); + return; + } else { + NSLog(@"[Camera] Video recording started"); + } + } + + if (self.recordAssetWriterInput.readyForMoreMediaData) { + CVImageBufferRef pixelBuffer = [self pixelBufferFromCGImage:dstImage]; + if (! [self.recordPixelBufferAdaptor appendPixelBuffer:pixelBuffer + withPresentationTime:lastSampleTime] ) { + NSLog(@"Video Writing Error"); + } + if (pixelBuffer != nullptr) + CVPixelBufferRelease(pixelBuffer); + } + + } + + // cleanup + CGImageRelease(dstImage); + CGColorSpaceRelease(colorSpace); + CVPixelBufferUnlockBaseAddress(imageBuffer, 0); + } +} + +- (void)updateOrientation { + if (self.rotateVideo == YES) + { + NSLog(@"rotate.."); + self.customPreviewLayer.bounds = CGRectMake(0, 0, self.parentView.frame.size.width, self.parentView.frame.size.height); + [self layoutPreviewLayer]; + } +} + +- (void)saveVideo { + if (self.recordVideo == NO) { + return; + } + + UISaveVideoAtPathToSavedPhotosAlbum([self videoFileString], nil, nil, NULL); +} + +- (NSURL *)videoFileURL { + NSString *outputPath = [[NSString alloc] initWithFormat:@"%@%@", NSTemporaryDirectory(), @"output.mov"]; + NSURL *outputURL = [NSURL fileURLWithPath:outputPath]; + NSFileManager *fileManager = [NSFileManager defaultManager]; + if ([fileManager fileExistsAtPath:outputPath]) { + NSLog(@"file exists"); + } + return outputURL; +} + +- (NSString *)videoFileString { + NSString *outputPath = [[NSString alloc] initWithFormat:@"%@%@", NSTemporaryDirectory(), @"output.mov"]; + return outputPath; +} + +@end diff --git a/platforms/ios/build_framework.py b/platforms/ios/build_framework.py index 9135ecf7b2..d0abf7038a 100755 --- a/platforms/ios/build_framework.py +++ b/platforms/ios/build_framework.py @@ -17,19 +17,20 @@ Script will create , if it's missing, and a few its subdirectories: [cmake-generated build tree for an iOS device target] iPhoneSimulator-*/ [cmake-generated build tree for iOS simulator] - opencv2.framework/ + {framework_name}.framework/ [the framework content] The script should handle minor OpenCV updates efficiently - it does not recompile the library from scratch each time. -However, opencv2.framework directory is erased and recreated on each run. +However, {framework_name}.framework directory is erased and recreated on each run. -Adding --dynamic parameter will build opencv2.framework as App Store dynamic framework. Only iOS 8+ versions are supported. +Adding --dynamic parameter will build {framework_name}.framework as App Store dynamic framework. Only iOS 8+ versions are supported. """ from __future__ import print_function import glob, re, os, os.path, shutil, string, sys, argparse, traceback, multiprocessing from subprocess import check_call, check_output, CalledProcessError +from distutils.dir_util import copy_tree IPHONEOS_DEPLOYMENT_TARGET='8.0' # default, can be changed via command line options or environment variable @@ -49,7 +50,7 @@ def getXCodeMajor(): raise Exception("Failed to parse Xcode version") class Builder: - def __init__(self, opencv, contrib, dynamic, bitcodedisabled, exclude, disable, enablenonfree, targets, debug, debug_info): + def __init__(self, opencv, contrib, dynamic, bitcodedisabled, exclude, disable, enablenonfree, targets, debug, debug_info, framework_name): self.opencv = os.path.abspath(opencv) self.contrib = None if contrib: @@ -61,11 +62,13 @@ class Builder: self.dynamic = dynamic self.bitcodedisabled = bitcodedisabled self.exclude = exclude + self.build_objc_wrapper = not "objc" in self.exclude self.disable = disable self.enablenonfree = enablenonfree self.targets = targets self.debug = debug self.debug_info = debug_info + self.framework_name = framework_name def getBD(self, parent, t): @@ -114,6 +117,10 @@ class Builder: if self.dynamic == False: self.mergeLibs(mainBD) self.makeFramework(outdir, dirs) + if self.build_objc_wrapper: + print("To run tests call:") + print(sys.argv[0].replace("build_framework", "run_tests") + " --framework_dir=" + outdir + " --framework_name=" + self.framework_name + " " + dirs[0] + "/modules/objc/test") + self.copy_samples(outdir) def build(self, outdir): try: @@ -140,7 +147,8 @@ class Builder: "-DCMAKE_INSTALL_PREFIX=install", "-DCMAKE_BUILD_TYPE=%s" % self.getConfiguration(), "-DOPENCV_INCLUDE_INSTALL_PATH=include", - "-DOPENCV_3P_LIB_INSTALL_PATH=lib/3rdparty" + "-DOPENCV_3P_LIB_INSTALL_PATH=lib/3rdparty", + "-DFRAMEWORK_NAME=%s" % self.framework_name, ] + ([ "-DBUILD_SHARED_LIBS=ON", "-DCMAKE_MACOSX_BUNDLE=ON", @@ -197,15 +205,26 @@ class Builder: def getInfoPlist(self, builddirs): return os.path.join(builddirs[0], "ios", "Info.plist") - def buildOne(self, arch, target, builddir, cmakeargs = []): - # Run cmake + def makeCMakeCmd(self, arch, target, dir, cmakeargs = []): toolchain = self.getToolchain(arch, target) cmakecmd = self.getCMakeArgs(arch, target) + \ (["-DCMAKE_TOOLCHAIN_FILE=%s" % toolchain] if toolchain is not None else []) if target.lower().startswith("iphoneos"): cmakecmd.append("-DCPU_BASELINE=DETECT") - cmakecmd.append(self.opencv) + cmakecmd.append(dir) cmakecmd.extend(cmakeargs) + return cmakecmd + + def buildOne(self, arch, target, builddir, cmakeargs = []): + # Run cmake + #toolchain = self.getToolchain(arch, target) + #cmakecmd = self.getCMakeArgs(arch, target) + \ + # (["-DCMAKE_TOOLCHAIN_FILE=%s" % toolchain] if toolchain is not None else []) + #if target.lower().startswith("iphoneos"): + # cmakecmd.append("-DCPU_BASELINE=DETECT") + #cmakecmd.append(self.opencv) + #cmakecmd.extend(cmakeargs) + cmakecmd = self.makeCMakeCmd(arch, target, self.opencv, cmakeargs) execute(cmakecmd, cwd = builddir) # Clean and build @@ -215,16 +234,27 @@ class Builder: buildcmd = self.getBuildCommand(arch, target) execute(buildcmd + ["-target", "ALL_BUILD", "build"], cwd = builddir) execute(["cmake", "-DBUILD_TYPE=%s" % self.getConfiguration(), "-P", "cmake_install.cmake"], cwd = builddir) + if self.build_objc_wrapper: + cmakecmd = self.makeCMakeCmd(arch, target, builddir + "/modules/objc/gen", cmakeargs) + cmakecmd.append("-DBUILD_ROOT=%s" % builddir) + cmakecmd.append("-DCMAKE_INSTALL_NAME_TOOL=install_name_tool") + cmakecmd.append("--no-warn-unused-cli") + execute(cmakecmd, cwd = builddir + "/modules/objc/framework_build") + + execute(buildcmd + ["-target", "ALL_BUILD", "build"], cwd = builddir + "/modules/objc/framework_build") + execute(["cmake", "-DBUILD_TYPE=%s" % self.getConfiguration(), "-DCMAKE_INSTALL_PREFIX=%s" % (builddir + "/install"), "-P", "cmake_install.cmake"], cwd = builddir + "/modules/objc/framework_build") def mergeLibs(self, builddir): res = os.path.join(builddir, "lib", self.getConfiguration(), "libopencv_merged.a") libs = glob.glob(os.path.join(builddir, "install", "lib", "*.a")) + module = [os.path.join(builddir, "install", "lib", self.framework_name + ".framework", self.framework_name)] if self.build_objc_wrapper else [] + libs3 = glob.glob(os.path.join(builddir, "install", "lib", "3rdparty", "*.a")) - print("Merging libraries:\n\t%s" % "\n\t".join(libs + libs3), file=sys.stderr) - execute(["libtool", "-static", "-o", res] + libs + libs3) + print("Merging libraries:\n\t%s" % "\n\t".join(libs + libs3 + module), file=sys.stderr) + execute(["libtool", "-static", "-o", res] + libs + libs3 + module) def makeFramework(self, outdir, builddirs): - name = "opencv2" + name = self.framework_name # set the current dir to the dst root framework_dir = os.path.join(outdir, "%s.framework" % name) @@ -234,13 +264,41 @@ class Builder: if self.dynamic: dstdir = framework_dir - libname = "opencv2.framework/opencv2" + libname = name + ".framework/" + name else: dstdir = os.path.join(framework_dir, "Versions", "A") libname = "libopencv_merged.a" # copy headers from one of build folders shutil.copytree(os.path.join(builddirs[0], "install", "include", "opencv2"), os.path.join(dstdir, "Headers")) + if name != "opencv2": + for dirname, dirs, files in os.walk(os.path.join(dstdir, "Headers")): + for filename in files: + filepath = os.path.join(dirname, filename) + with open(filepath) as file: + body = file.read() + body = body.replace("include \"opencv2/", "include \"" + name + "/") + body = body.replace("include ", "#import <" + self.framework_name + "/" + self.framework_name + ".h>") + body = body.replace("OpenCV.framework", self.framework_name + ".framework") + body = body.replace("../../OpenCV/**", "../../" + self.framework_name + "/**") + with open(filepath, "w") as file: + file.write(body) + if __name__ == "__main__": folder = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "../..")) @@ -304,6 +389,8 @@ if __name__ == "__main__": parser.add_argument('--enable_nonfree', default=False, dest='enablenonfree', action='store_true', help='enable non-free modules (disabled by default)') parser.add_argument('--debug', default=False, dest='debug', action='store_true', help='Build "Debug" binaries (disabled by default)') parser.add_argument('--debug_info', default=False, dest='debug_info', action='store_true', help='Build with debug information (useful for Release mode: BUILD_WITH_DEBUG_INFO=ON)') + parser.add_argument('--framework_name', default='opencv2', dest='framework_name', action='store_true', help='Name of OpenCV framework (default: opencv2, will change to OpenCV in future version)') + parser.add_argument('--legacy_build', default=False, dest='legacy_build', action='store_true', help='Build legacy opencv2 framework (default: False, equivalent to "--framework_name=opencv2 --without=objc")') args = parser.parse_args() os.environ['IPHONEOS_DEPLOYMENT_TARGET'] = args.iphoneos_deployment_target @@ -312,6 +399,10 @@ if __name__ == "__main__": print('Using iPhoneOS ARCHS=' + str(iphoneos_archs)) iphonesimulator_archs = args.iphonesimulator_archs.split(',') print('Using iPhoneSimulator ARCHS=' + str(iphonesimulator_archs)) + if args.legacy_build: + args.framework_name = "opencv2" + if not "objc" in args.without: + args.without.append("objc") b = iOSBuilder(args.opencv, args.contrib, args.dynamic, args.bitcodedisabled, args.without, args.disable, args.enablenonfree, [ @@ -320,5 +411,5 @@ if __name__ == "__main__": [ (iphoneos_archs, "iPhoneOS"), (iphonesimulator_archs, "iPhoneSimulator"), - ], args.debug, args.debug_info) + ], args.debug, args.debug_info, args.framework_name) b.build(args.out) diff --git a/platforms/ios/run_tests.py b/platforms/ios/run_tests.py new file mode 100755 index 0000000000..de302c2bfe --- /dev/null +++ b/platforms/ios/run_tests.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +""" +This script runs OpenCV.framework tests for iOS. +""" + +from __future__ import print_function +import glob, re, os, os.path, shutil, string, sys, argparse, traceback, multiprocessing +from subprocess import check_call, check_output, CalledProcessError + +IPHONEOS_DEPLOYMENT_TARGET='8.0' # default, can be changed via command line options or environment variable + +def execute(cmd, cwd = None): + print("Executing: %s in %s" % (cmd, cwd), file=sys.stderr) + print('Executing: ' + ' '.join(cmd)) + retcode = check_call(cmd, cwd = cwd) + if retcode != 0: + raise Exception("Child returned:", retcode) + +class TestRunner: + def __init__(self, script_dir, tests_dir, build_dir, framework_dir, framework_name, arch, target, platform): + self.script_dir = script_dir + self.tests_dir = tests_dir + self.build_dir = build_dir + self.framework_dir = framework_dir + self.framework_name = framework_name + self.arch = arch + self.target = target + self.platform = platform + + def _run(self): + if not os.path.isdir(self.build_dir): + os.makedirs(self.build_dir) + + self.runTest() + + def run(self): + try: + self._run() + except Exception as e: + print("="*60, file=sys.stderr) + print("ERROR: %s" % e, file=sys.stderr) + print("="*60, file=sys.stderr) + traceback.print_exc(file=sys.stderr) + sys.exit(1) + + def getToolchain(self): + return None + + def getCMakeArgs(self): + args = [ + "cmake", + "-GXcode", + "-DFRAMEWORK_DIR=%s" % self.framework_dir, + "-DFRAMEWORK_NAME=%s" % self.framework_name, + ] + return args + + def makeCMakeCmd(self): + toolchain = self.getToolchain() + cmakecmd = self.getCMakeArgs() + \ + (["-DCMAKE_TOOLCHAIN_FILE=%s" % toolchain] if toolchain is not None else []) + \ + ["-DCMAKE_INSTALL_NAME_TOOL=install_name_tool"] + cmakecmd.append(self.tests_dir) + return cmakecmd + + def runTest(self): + cmakecmd = self.makeCMakeCmd() + execute(cmakecmd, cwd = self.build_dir) + buildcmd = self.getTestCommand() + execute(buildcmd, cwd = self.build_dir) + + def getTestCommand(self): + testcmd = [ + "xcodebuild", + "test", + "-project", "OpenCVTest.xcodeproj", + "-scheme", "OpenCVTestTests", + "-destination", "platform=%s" % self.platform + ] + return testcmd + +class iOSTestRunner(TestRunner): + + def getToolchain(self): + toolchain = os.path.join(self.script_dir, "cmake", "Toolchains", "Toolchain-%s_Xcode.cmake" % self.target) + return toolchain + + def getCMakeArgs(self): + args = TestRunner.getCMakeArgs(self) + args = args + [ + "-DIOS_ARCH=%s" % self.arch, + "-DIPHONEOS_DEPLOYMENT_TARGET=%s" % os.environ['IPHONEOS_DEPLOYMENT_TARGET'], + ] + return args + +if __name__ == "__main__": + script_dir = os.path.abspath(os.path.dirname(sys.argv[0])) + parser = argparse.ArgumentParser(description='The script builds OpenCV.framework for iOS.') + parser.add_argument('tests_dir', metavar='TEST_DIR', help='folder where test files are located') + parser.add_argument('--build_dir', default=None, help='folder where test will be built (default is "../test_build" relative to tests_dir)') + parser.add_argument('--framework_dir', default=None, help='folder where OpenCV framework is located') + parser.add_argument('--framework_name', default='opencv2', help='Name of OpenCV framework (default: opencv2, will change to OpenCV in future version)') + parser.add_argument('--iphoneos_deployment_target', default=os.environ.get('IPHONEOS_DEPLOYMENT_TARGET', IPHONEOS_DEPLOYMENT_TARGET), help='specify IPHONEOS_DEPLOYMENT_TARGET') + parser.add_argument('--platform', default='iOS Simulator,name=iPhone 11', help='xcodebuild platform parameter (default is iOS 11 simulator)') + args = parser.parse_args() + + os.environ['IPHONEOS_DEPLOYMENT_TARGET'] = args.iphoneos_deployment_target + print('Using IPHONEOS_DEPLOYMENT_TARGET=' + os.environ['IPHONEOS_DEPLOYMENT_TARGET']) + arch = "x86_64" + target = "iPhoneSimulator" + print('Using iPhoneSimulator ARCH=' + arch) + + r = iOSTestRunner(script_dir, args.tests_dir, args.build_dir if args.build_dir else os.path.join(args.tests_dir, "../test_build"), args.framework_dir, args.framework_name, arch, target, args.platform) + r.run() diff --git a/platforms/osx/build_framework.py b/platforms/osx/build_framework.py index 1c2b3cdb92..73ab6c39e8 100755 --- a/platforms/osx/build_framework.py +++ b/platforms/osx/build_framework.py @@ -45,14 +45,20 @@ if __name__ == "__main__": parser.add_argument('--macosx_deployment_target', default=os.environ.get('MACOSX_DEPLOYMENT_TARGET', MACOSX_DEPLOYMENT_TARGET), help='specify MACOSX_DEPLOYMENT_TARGET') parser.add_argument('--debug', action='store_true', help='Build "Debug" binaries (CMAKE_BUILD_TYPE=Debug)') parser.add_argument('--debug_info', action='store_true', help='Build with debug information (useful for Release mode: BUILD_WITH_DEBUG_INFO=ON)') + parser.add_argument('--framework_name', default='opencv2', dest='framework_name', action='store_true', help='Name of OpenCV framework (default: opencv2, will change to OpenCV in future version)') + parser.add_argument('--legacy_build', default=False, dest='legacy_build', action='store_true', help='Build legacy framework (default: False, equivalent to "--framework_name=opencv2 --without=objc")') args = parser.parse_args() os.environ['MACOSX_DEPLOYMENT_TARGET'] = args.macosx_deployment_target print('Using MACOSX_DEPLOYMENT_TARGET=' + os.environ['MACOSX_DEPLOYMENT_TARGET']) + if args.legacy_build: + args.framework_name = "opencv2" + if not "objc" in args.without: + args.without.append("objc") b = OSXBuilder(args.opencv, args.contrib, False, False, args.without, args.disable, args.enablenonfree, [ (["x86_64"], "MacOSX") - ], args.debug, args.debug_info) + ], args.debug, args.debug_info, args.framework_name) b.build(args.out) diff --git a/platforms/osx/run_tests.py b/platforms/osx/run_tests.py new file mode 100755 index 0000000000..afdcf8426a --- /dev/null +++ b/platforms/osx/run_tests.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +""" +This script runs OpenCV.framework tests for OSX. +""" + +from __future__ import print_function +import os, os.path, sys, argparse, traceback, multiprocessing + +# import common code +sys.path.insert(0, os.path.abspath(os.path.abspath(os.path.dirname(__file__))+'/../ios')) +from run_tests import TestRunner + +MACOSX_DEPLOYMENT_TARGET='10.12' # default, can be changed via command line options or environment variable + +class OSXTestRunner(TestRunner): + + def getToolchain(self): + return None + + def getCMakeArgs(self): + args = TestRunner.getCMakeArgs(self) + args = args + [ + '-DMACOSX_DEPLOYMENT_TARGET=%s' % os.environ['MACOSX_DEPLOYMENT_TARGET'] + ] + return args + + +if __name__ == "__main__": + script_dir = os.path.abspath(os.path.dirname(sys.argv[0])) + parser = argparse.ArgumentParser(description='The script builds OpenCV.framework for OSX.') + parser.add_argument('tests_dir', metavar='TEST_DIR', help='folder where test files are located') + parser.add_argument('--build_dir', default=None, help='folder where test will be built (default is "../test_build" relative to tests_dir)') + parser.add_argument('--framework_dir', default=None, help='folder where OpenCV framework is located') + parser.add_argument('--framework_name', default='opencv2', help='Name of OpenCV framework (default: opencv2, will change to OpenCV in future version)') + parser.add_argument('--macosx_deployment_target', default=os.environ.get('MACOSX_DEPLOYMENT_TARGET', MACOSX_DEPLOYMENT_TARGET), help='specify MACOSX_DEPLOYMENT_TARGET') + parser.add_argument('--platform', default='macOS,arch=x86_64', help='xcodebuild platform parameter (default is macOS)') + + args = parser.parse_args() + os.environ['MACOSX_DEPLOYMENT_TARGET'] = args.macosx_deployment_target + arch = "x86_64" + target = "macOS" + + r = OSXTestRunner(script_dir, args.tests_dir, args.build_dir if args.build_dir else os.path.join(args.tests_dir, "../test_build"), args.framework_dir, args.framework_name, arch, target, args.platform) + r.run() diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection.xcodeproj/project.pbxproj b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..be696a84b6 --- /dev/null +++ b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection.xcodeproj/project.pbxproj @@ -0,0 +1,397 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 5B14CF802452E926003438BE /* OpenCV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B14CF7F2452E926003438BE /* OpenCV.framework */; }; + 5B8C4C2D243814E9003D9176 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B8C4C2C243814A0003D9176 /* libc++.tbd */; }; + 5B8C4C31243849E9003D9176 /* ColorBlobDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B8C4C30243849E9003D9176 /* ColorBlobDetector.swift */; }; + 5BBDB48A240D380F00BB10FE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBDB489240D380F00BB10FE /* AppDelegate.swift */; }; + 5BBDB48E240D380F00BB10FE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBDB48D240D380F00BB10FE /* ViewController.swift */; }; + 5BBDB491240D380F00BB10FE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5BBDB48F240D380F00BB10FE /* Main.storyboard */; }; + 5BBDB493240D381000BB10FE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5BBDB492240D381000BB10FE /* Assets.xcassets */; }; + 5BBDB496240D381000BB10FE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5BBDB494240D381000BB10FE /* LaunchScreen.storyboard */; }; + 5BBDB517241923C800BB10FE /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BBDB516241923C800BB10FE /* AVFoundation.framework */; }; + 5BBDB5192419241200BB10FE /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BBDB5182419241200BB10FE /* CoreMedia.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 5B14CF7F2452E926003438BE /* OpenCV.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenCV.framework; path = ../../../OpenCV.framework; sourceTree = ""; }; + 5B8C4C2C243814A0003D9176 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; + 5B8C4C30243849E9003D9176 /* ColorBlobDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorBlobDetector.swift; sourceTree = ""; }; + 5BBDB486240D380F00BB10FE /* ColorBlobDetection.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ColorBlobDetection.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 5BBDB489240D380F00BB10FE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 5BBDB48D240D380F00BB10FE /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 5BBDB490240D380F00BB10FE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 5BBDB492240D381000BB10FE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 5BBDB495240D381000BB10FE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 5BBDB497240D381000BB10FE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 5BBDB516241923C800BB10FE /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; + 5BBDB5182419241200BB10FE /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 5BBDB483240D380F00BB10FE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5BBDB5192419241200BB10FE /* CoreMedia.framework in Frameworks */, + 5B8C4C2D243814E9003D9176 /* libc++.tbd in Frameworks */, + 5BBDB517241923C800BB10FE /* AVFoundation.framework in Frameworks */, + 5B14CF802452E926003438BE /* OpenCV.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 5BBDB47D240D380F00BB10FE = { + isa = PBXGroup; + children = ( + 5BBDB488240D380F00BB10FE /* ColorBlobDetection */, + 5BBDB487240D380F00BB10FE /* Products */, + 5BBDB515241923C800BB10FE /* Frameworks */, + ); + sourceTree = ""; + }; + 5BBDB487240D380F00BB10FE /* Products */ = { + isa = PBXGroup; + children = ( + 5BBDB486240D380F00BB10FE /* ColorBlobDetection.app */, + ); + name = Products; + sourceTree = ""; + }; + 5BBDB488240D380F00BB10FE /* ColorBlobDetection */ = { + isa = PBXGroup; + children = ( + 5B14CF7F2452E926003438BE /* OpenCV.framework */, + 5BBDB489240D380F00BB10FE /* AppDelegate.swift */, + 5BBDB48D240D380F00BB10FE /* ViewController.swift */, + 5BBDB48F240D380F00BB10FE /* Main.storyboard */, + 5BBDB492240D381000BB10FE /* Assets.xcassets */, + 5BBDB494240D381000BB10FE /* LaunchScreen.storyboard */, + 5BBDB497240D381000BB10FE /* Info.plist */, + 5B8C4C30243849E9003D9176 /* ColorBlobDetector.swift */, + ); + path = ColorBlobDetection; + sourceTree = ""; + }; + 5BBDB515241923C800BB10FE /* Frameworks */ = { + isa = PBXGroup; + children = ( + 5B8C4C2C243814A0003D9176 /* libc++.tbd */, + 5BBDB5182419241200BB10FE /* CoreMedia.framework */, + 5BBDB516241923C800BB10FE /* AVFoundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 5BBDB485240D380F00BB10FE /* ColorBlobDetection */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5BBDB4B0240D381000BB10FE /* Build configuration list for PBXNativeTarget "ColorBlobDetection" */; + buildPhases = ( + 5BBDB482240D380F00BB10FE /* Sources */, + 5BBDB483240D380F00BB10FE /* Frameworks */, + 5BBDB484240D380F00BB10FE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ColorBlobDetection; + productName = ColorBlobDetection; + productReference = 5BBDB486240D380F00BB10FE /* ColorBlobDetection.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 5BBDB47E240D380F00BB10FE /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1130; + LastUpgradeCheck = 1130; + ORGANIZATIONNAME = OpenCV; + TargetAttributes = { + 5BBDB485240D380F00BB10FE = { + CreatedOnToolsVersion = 11.3.1; + LastSwiftMigration = 1130; + }; + }; + }; + buildConfigurationList = 5BBDB481240D380F00BB10FE /* Build configuration list for PBXProject "ColorBlobDetection" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 5BBDB47D240D380F00BB10FE; + productRefGroup = 5BBDB487240D380F00BB10FE /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 5BBDB485240D380F00BB10FE /* ColorBlobDetection */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 5BBDB484240D380F00BB10FE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5BBDB496240D381000BB10FE /* LaunchScreen.storyboard in Resources */, + 5BBDB493240D381000BB10FE /* Assets.xcassets in Resources */, + 5BBDB491240D380F00BB10FE /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 5BBDB482240D380F00BB10FE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5BBDB48E240D380F00BB10FE /* ViewController.swift in Sources */, + 5B8C4C31243849E9003D9176 /* ColorBlobDetector.swift in Sources */, + 5BBDB48A240D380F00BB10FE /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 5BBDB48F240D380F00BB10FE /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 5BBDB490240D380F00BB10FE /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 5BBDB494240D381000BB10FE /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 5BBDB495240D381000BB10FE /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 5BBDB4AE240D381000BB10FE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = ../..; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = "../../OpenCV/**"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = ""; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 5BBDB4AF240D381000BB10FE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ../..; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = "../../OpenCV/**"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_LDFLAGS = ""; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 5BBDB4B1240D381000BB10FE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = NO; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/../..", + ); + INFOPLIST_FILE = ColorBlobDetection/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_SWIFT_FLAGS = "-Xcc -Wno-incomplete-umbrella"; + PRODUCT_BUNDLE_IDENTIFIER = org.opencv.ColorBlobDetection; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 5BBDB4B2240D381000BB10FE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = NO; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/../..", + ); + INFOPLIST_FILE = ColorBlobDetection/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_SWIFT_FLAGS = "-Xcc -Wno-incomplete-umbrella"; + PRODUCT_BUNDLE_IDENTIFIER = org.opencv.ColorBlobDetection; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 5BBDB481240D380F00BB10FE /* Build configuration list for PBXProject "ColorBlobDetection" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5BBDB4AE240D381000BB10FE /* Debug */, + 5BBDB4AF240D381000BB10FE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5BBDB4B0240D381000BB10FE /* Build configuration list for PBXNativeTarget "ColorBlobDetection" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5BBDB4B1240D381000BB10FE /* Debug */, + 5BBDB4B2240D381000BB10FE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 5BBDB47E240D380F00BB10FE /* Project object */; +} diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..3d4c1e5525 --- /dev/null +++ b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/AppDelegate.swift b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/AppDelegate.swift new file mode 100644 index 0000000000..9965cc5086 --- /dev/null +++ b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/AppDelegate.swift @@ -0,0 +1,19 @@ +// +// AppDelegate.swift +// +// Created by Giles Payne on 2020/03/02. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + +} diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/100.png b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 0000000000000000000000000000000000000000..1e700dd332bad2da46295b370fede6503a7ecb9e GIT binary patch literal 3758 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 literal 0 HcmV?d00001 diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/152.png b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000000000000000000000000000000000000..99e2a23b4ab7d17d2eba771d9f9c075fb4ec41e7 GIT binary patch literal 5685 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 bw(kCabO&)RM*kNv00000NkvXXu0mjf5R;PL literal 0 HcmV?d00001 diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/167.png b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000000000000000000000000000000000000..ea93ffc144b373370f1069f8795ec9b18eebe3ed GIT binary patch literal 6547 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 Y1EVtSC%*^BkN^Mx07*qoM6N<$g0_4l)c^nh literal 0 HcmV?d00001 diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/29.png b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000000000000000000000000000000000000..cee7ce05b534fbbcfae1ffe06e7537221c973842 GIT binary patch literal 946 zcmV;j15NyiP)GK^%H3CpAc>!lbeFbIMmFtV{;YJ4)CAd{W~VM?^X zAashtN=M25K-mOM4}nYt{m~wZ9(t<%#Xk4kbI&<*@7&W&8_vTyYpw5F|MuGZi2tw* zcM-xM_8^d+N$e@$gy8F@P-?1Z7)9FCL;rW|=LD}b!D~e75DsB)?F%R!Qj8Dv)9XuW z*ogFDKcGTxZxDPkpMHN*X%e`%267DPE9~{a=88-@pco%rFsWe(DK$!G*;@r~v_Ck6 zG3@8d8nVM;NQaS5mH?(?-(K+n0@6P<$Ce9z>qR_odJRoTg%oR~s|DZwU!=w!5d2c4 z7uF)3t&t83PA!Nu8Cm)fxT*y3x$IjrSt}E%p2H>ijO;ZFu4x7w!i(5X6(G`tVtk~C zRQeRioXD0vDi~iJY1$IfdpXG8*w+ibFQs<`>2Ur&jlEfLeo>@(8t+6pjP^yQ-0z0im~nTe^V?9f^t$%tJUTvBzc`=(VbzS4o_a zn21r&7BvkHFBtFa1!_EmINePig2!yyv-hm6o6CA3c(2XV8JT!9MvVw_qtIu-k{alD zz{NrzMfE;4B%c9pD;lm6t=CATX?0OazCpoZuW{x{r&GNPF_yiXb%-yzXThr_zA4Kj z_Qj}tTX~yWI9ccC(H@YjcY(W0gpVO!ZPJCc)FzWDTa{A?{Dj2*s$uh}GY5Ln*LSZE>=cO%l!s4XuIftPK1 zT|K95c7l$#N>(k_ia^+rH`WCpJ+sF95e+WfelP?E?d(5iKw>gRJ_e3}{%lkI3$zVn U-e7r0!vFvP07*qoM6N<$g0OkAu>b%7 literal 0 HcmV?d00001 diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/40.png b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000000000000000000000000000000000000..0340a78dee95e6509123ed50b25fb5cdc17a31c0 GIT binary patch literal 1266 zcmV9-!cl^rk`)CS7DK6p zme_*`t+5AbRw6yIG9tvHB9e&mL;D}+%$>(Qci%hb+(~J}`Lg%gYyHkzd#(KtBW#9q zfJuZf340Q75a|tKd=JyimfuBVH(mpU{pn0yRfGTeylRDI-v9tFtdu00a?3Su&m15N`THnD)m+|P>|h^ zJ+X=xs{v-&37rYR1K7)fH}kO&W??T){zyeHmIiF$y?xlz1-pj=8M=_xCLl8}o+kLG zOe97QXfGB#lrm;1tA$x@tZ()Sg5M4o;8el3Bwz^L$xz#41mFLk0nZbBdbj{vg*eOj z#$s+y7QFwT0%poA{Q(?PP`n3wrr^s$l3auIS_0x%*(a8P%VbI?Ne&po3hX-*$}Kze z_Zy}lCD$d=1B&tPLjwqtk=|`kE-h1x-E|(6UIC^TWDjDG6Z~*U0Bd3X5V&ZFfnQ{w z)KE!aBGN~7{n9GMcw1Sx$|24H;R2*rfzi34g!cwQ9l|Q?`8By7#W<%5Tw8!O$gxOY z0N=@;(9Ed&y(q0$j9pdX+6Jsa9uIU0vU6e6<`7mZ#y&5$(JEze8~R5)B@JlC2f}RN zRO2zgSl~#Z4UxD3@y>`XmsHRQXu5^&0FG;+;}gphOWMd%>uTV4&k8pi}rbCoF${XK+cyah2@dSy_3ft)xTUR#D?epHyew#U< zo*YotcHrXSjk3e?B_-qt!OLy#Ey_CKMac(%V+*pc+nOfKQlYwibTIyUEE0QwuWQ>| zK)f|PppTOH3CL!HO5O@wo-jBSQpYfllK2@ox-h3V1Kb0QD-3*N zx!6~lRq{sQ`bI!C-MJ*EPTx7;UkUQvmd{TTW$Oa%O^j`_oWD4P;HPbF`L6(zS3~17 z5Z^X{m>EKw8jRzGsPEtqf>+oyoV;x=P46ZxLX5df=;1_?JQEdfZs@nNyT{oHz)neaMTzw>D!(Gs z)urID+IU{spA}jc6_e{{P=~LR$c|5?JlVH@>%RW^sRQd0k<(|d_s!Za<3|zCm-`u? zDY3H2+hV_py*7&|l(6P^64Mz7HyNK?x3cidh$qT{6{Xw}0v$F}YHv1MZQcg5DzX#u cr literal 0 HcmV?d00001 diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/50.png b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/50.png new file mode 100644 index 0000000000000000000000000000000000000000..d143fdcce436930623e21d5c7da1cb09efe53632 GIT binary patch literal 1667 zcmV-}27LL6P)BzUI7YEh>I8nyvuB3%sZQ6P5}_WgnNG;A66 zrGoEE^7SEa8;D^H(#ybr7L8enJznrfi~JR~wtyJ+L|P8)(~c3qv$A7MwY=H`62fEH z(^}Tvo6(*nnAfVTmVp?iAwAZr;uOALv5yvf-iplDfmDywaRo|OBfSF*!X5?eo=Y#y zQfY}E~NQ6 zyvGUNC<~^gqva4D!M-3>nKWNXa!H;cmc9dqq|(?^1dICzi1ZS0N`@gZU{~Y<385SN zh7A6z1b6lqkhg(xn+Zs_;Ku)fl-U7G*&b;wM!o@tWq7n7Jrcq#*w;Ds_98);k1T5tl=t2xTzEj>i=TU`|bMhn!{L`iZ{DR-9wK}HX6TD(T1vke zXMe}ow+q%3DXoE@AXwTAZ8J0-1JaDL%}J9F#9SB%qm4%b2lg^#wa}`B<5VLdaU+OA$ib-mTq*=V zV{??vSkPC+o91&Qme^bhdY~@r*D;p|f$`OdtQ$VlHp%Y!D6t?()Rhm!TS%3LZFI$t zW(MfKjWRYen{lGyax31VuPyIeOjb`J`4!-#-oM%$3d$J;d4^O!_p+G;)k?a0w9G+5 zuhk~!P1|LB0b*g(3{_>*CF+hvjsFE(6VqjRXNk4ImJQ!s+kh`BK!U?O;~C`);xo&` z%kg-7iCCYddeLSS=)*>tA$Yz`>d}*!nOfC2o{Csj21p3* zw#iE*Z`wMxUZzJ#=>=$wZoSQZ5lENFnKM-l??NmnV{{s=F+ae%aBmT)%kRQB8+*IZFY-L}Z?E?9;2*?=3yLmw5$}}atKgZ`I zYHuj+QuZ-z7s&buB=*)LQiBNHl_<%61~5ZK<#}P-G=Ad=h!<-f8Lga6vE#t5GThFJ zk*96W&iK4s!QoNIol9T;8jnZ3o(j}@9ymQSfblRy&A3V$&3y2T-bxZ>Oz}NZBDM^v zD_)B69+|F&O0t1^jq&w}B^!S?Lj&Ooe@{iuD7F ztSQRgfiTm!p`=b#xx)Bi#8V{=s*!uD6o&~MUckRWVqubSZ`z=uXs$Ih{<}da_7L%@ zEdGgXt4exApClwNQ5^_$V0nB9Otv|orYHW}D5m0dcI6Ix9<)A7(SQ8LTTcP0azFq8 N002ovPDHLkV1iQ`8&?1T literal 0 HcmV?d00001 diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/57.png b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000000000000000000000000000000000000..55d5723144f6a7984b843d5595099075be6c70ac GIT binary patch literal 2034 zcmV5f>02xEG~#a1XEPdmI@UJn^bK-Ku}9b(MlTEA~7|VSfaEBLM8B8 z3SG3spjbpf!(v3;q_wBuXpJ(Q|%L^yDA8zKHGv_}u z&zZA~m_jn_56nlJ54;vQ5O^i+ z)eU(2W*Iq1!-+^MfcN%qjW!YEGQp0%WitXJ1!qYB zr1bA>Y-%4Zc%WpQLs*Xel?>l)vd=8(nJSRa0{dika+=_ol5q_{ZkGMwlAfso`6%#~ z49M3B{$4W9A&hl0mTFdMjbcn0%|JBlalI#-ZP?2NmzFHElGJ)bb>)Q;Z8Ift3CPys;p?Rd)Xgc$d4i7SU*LKA6no6M3m||R9w+NPg4$Q6@zf1PXrGru$9~xw5H|s9*-Pnib?n=W+ zNOxBnYM(7gS3FQBp7|~gvcLOs8S2UGr$~Pq-n@;{h@ihl&jsa!`!kbcG!}vhs zJArh4Y(qT%LK)rfJb932Mj%{hd>ZL{tEZjDDLMWYDrX)28aXU~Ey&<7W?BuKe&{|c zU+yR?^98`epnJ=_6JFpYA#i1ks*JoULCV?&WNR~31{g0ytZQyuFJ}n8(dHb9O);wX z-ppS@7fG#-*b{b&o?0>S1Wf`d7fKrSlig!^=AZy2rwYP;6MJLM1l?BF=a);QTGNqT zvipJlIsU%~q^#sfK2TOXnZtM<8Z74v!8h2Pzn9iKZJkoK#h;ZXT|2kf`p_hh!Qt!1 zgI2`uwmi_{fIlel!^-1t=7BQFviFgA9C+2<@;1vst*S(OC(Wrs1Glt|Pe62sW#tX% za7e9dsBVGox78aDcWDm6%Wdkae7kIp13lIP85}m5Ox-hY^39g#HAdCL=mjb`Dx32_ zn<_q#10+&!rIsXC$EZ&WbF652(-PfpoVNcDKnlcX2Ew_KxDxS5Nw{iUa=IptFUbDc z)}-n|2wrQGuA3a$=8yz(snE*oil9rQ;_^X&)XTWI6Rj`FoCEr9#ZK-kk31q5lr#EkvWV{HmwzQGKVWaU!GvD?#QSqYj@esVq zrnbc(u{K849_Kytc#*`KjD6b@$UA^sZS!`^XJkQ|lZBF}d}#*cim3SIF5{X60!M-# z%FQkLVc>|&x4mA$VYSIuz=f`jil3@gWc?MGUT|PD5vdE-A-_!`3jo;M4&3#xD%~OY zN}JDQ=QT@GFfdmrIe{z*t&hYFh*a@zlbNCRMg8W2_+yqQ^)V&?w!w3kdu^8XCec1# zB0W+(sbsg!!L8U;R`Pl$lmt5CdqBebo5%=V6%{L1+{+}s+C&HK`tA6lq&KrosFkjb zXCW5|6`e~Htra}~F+L9Q`^jTIMk2Kcs{1GdPB?MU%*R0+Cz9siaFcO*VLVb2x;!dg zQm@pDCDL1-k&?t%jA}KS_EZMU;iA=e$vTieEqTOv4w~Gy>(H`sivPu)58?KX2j zyLv&k9m#w#vNx;G8lTncQS0n#)oFgz#E~>uh$rpz)^AVxIJsIk8lcZ~DtF27hUDoI zw*y1Hqq8{`T6g~hRo7kHK$F*dYeNgW6zYb-6KNk70xN9lUuV@DwAE%AXi^+oUqLtf z2f|Y0Vt=uH?)AM$+(;ouY_@1#BJsIWLfp~yyu@`ewg6T>=Pvg`Ov>qI2f}RQRNI^e z900A}#{)v!Bk>1_t<3=R!{zxQFyC&Ss1#qP-_xDnQ+6w+{z-R@wMzT{0W&$u*W#zf QHvj+t07*qoM6N<$f)-QbI{*Lx literal 0 HcmV?d00001 diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/58.png b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000000000000000000000000000000000000..e2653bcfdec2a44ee06769b43623c5d175d78cdf GIT binary patch literal 2073 zcmV+!2RP)QTbMDOCFZE64ulsJ#S>NT1 zm}D{>N(f7_j{;r_ybSm!@D#8M`&+;|!84QTiY80BVII=Cz~UM<9>ZP_d|vQ$4gS7a z8V71PfDl$-pVzlH@Cf!*f~y9QO^P%M)UZF&?ZE4Ya#Y%&qBwIX9bLkW0W~}q>3hK3 zMjVtrtD?B55nVuYKtuQ%_E}9iR_d`Y6nw4;os9rBoP%^t6ApFh*o{3`uyt77LjyI; zK)M^)Z&+t4pa01>gejlGc3^W1Mg}G=x>386S`IiKC@?lhOi#{ zjD~>T100>uwN#Khv?|4io6!)~#xN~0%8$xEC^rj+HzIAz?9oMn@8)z(&K`vekgm)) zxL9y&@?g#nSd4q$Ac(=nKs4Np!r@52$XNb8_Hlx^2PcqWA<`Wg9eeg3jm}wmoS1B< z>EGB>1^*q-<`_}6BC8zTBKyP~Q|kLrGg<--;cDy;XPnp}`;cxP!!)G7W&zmC1z*nc zB^ZVW+6n{j2llD7V;?VwS5dE}yAtWLgpc>gK6V(8QFdsc?O9DlNO#FzQ01tj57J}6 zfjxQb69u>YUjS|I)gJ+|q}fUKOu>UGM-8VU-7tYiTCJj3H7v-+X0!y_nwdWXFB)}B z`l5;=w|`4^OYsU?G*0$f6~#GCfNT_K53oD2-y(RTj|k>sr_kchisChXsKM>w(d_s?E-+e+`3yMvMpX~t*wMLyTcpk|rSbptHzXN_kdw)g4bICh^VvC`%k(BJFp@lg`@ z+A(?8qup{7ftnRUm)AEi#wm1Zp(0g7%Bb}UiPE;jrA49Ix`Hm8<{nq%OipNu!w z3DkU0Xwcw!r}1>u-$^^-ObA|O^NDfJJ!3Ny)HA*795FAFI&)r|Lt(5GI|kIeR;_&z zJJNlV@kbC>_bpfo_(KxcPFQZ@#-dO;ODU3kA26qj=TA2CM!=2&l@$Z$I^F4BHXpP+ zGv`q6?>W3vY5xfW3WclQpvR!OGLMmH=&q>MK zybp9!H&2a0iX!*hnk~!%F&a##?ET94;t4ZnUX6(P5c z+x=WHX;W2nXSTnk?wICAH?V^`Y?%Krk?ZC(?D2;lh%J+|Dj=OoAbH$K{^qU|<^gF0FnU5DC{5*?XJ zx3U-_#(?1@QFg9VwB2|P;vc=3}Rkfa|m8JLauS8pyqPb#0)bSKUOgBuZ) zxTYw|SK#C7y5`_^g&~$V8>8NPvs=C2E-^sWFX`q(c6>XmvhLhnGg>UlmtcE0fwUgT z(M7gQ;!91~1)j8Hey!0iUauM^_4YD{?3$t z>9_*1y9R%T#mgm@0<&xA+G6u{S}U<9)#<&&_j$(z!qKLz^o=x_rT`BM{idLZRlIf8 zCul-JbKF z20kdk@c?D?|F`W*qsor!oIQ(ub#tRBv{&qY*D0fz=o+vcLN_NTF)^^YZc=e1L^1>+$i7{ z*fqexjW{|Epc?~hH~{Hqje#8=v4%l62RMXf*q>=6vh7OjGX-hp3`~sxHq1s^H?TL| z6>OA!Y!mvM0WAFz_)rrLb=6^CAh>H#8v_G2OhfuZO-JsL-NEKk^n5Ef&Xk0ARv!G@x(48!1nrC$Rd%sOGu5!{yJ zaR_%|pPvtpu2YQtBr`kIHUao0;4K+Pw_r~b{3i<-W>h(Ko_-{I=4%#V!z84C6r!?F zFOd1y1MZU6k*qiihZ%*_Bu{_D8e&xys`)Kc_3lKAxM8JtlwFJ zA2tMdo9w;n=o+sG?~C+&0r1&^@6~bA(qDkwlzq*o^lnK1D}Z@O_6a4xhNF;vRp5I> z_Aw=0*(EWEaFVt3dtiDY%EesRa5YkXbJOeLSPJ1L?D@rkv(FN&?!;%9gS4cEjuQlF zC-0jE+O(<&!+A(6YIv|!_B-484ErPfy@vc#vJa}kmt|=J@II}zH>EJG&(Tm0J%${@ zjo6>hd#Xi>@v7Gxa4X7RK>BvZna5>M>-N22Po!-b`B$;03;z0g0i1U0w8N${Y=;o~ zbV2sMZ1@t=bsgoU+Z5xRLBS^Zfv5Da6NZl?{iK6a56C{L(WSvM|* zH?@{VTJJAmPizwGxBzZN`WU1Q*fRtV*9F{?O=t2&vJdY?t}bt*Q+lLP!(JNd9rZ+p zAc#E)^}yUaVDpAnGc4U&A|iiV>cN>^!CDWnnH>m6m}<8@vdysUxBA25j~ex}#>BbCiEVcSgmF26Xz( z5*uxngL>>D`eKQ>b}B3?^`P7QYyXcqROsFg2|z6j#z!Mc_u7muA1U#h7Q-KH<*%Nj zH2IAhvR%p8d>E=G!44W7VKZ6i*;>dtFbUlq70>Bc9{e7OO(W%PE{9RcNN%v^Netxx zEwY&mJF7CI0L%K_Abnlq!w{(;&0_H@--_gig^DG`C+ zF7Zqq+cr}Z;LC+>C?0zQo8YVMi4FSrcq9QXl}^@Mp4|_evWzE5WV??Nx5ilI^F<25D{RioFm5UW7Fre+KUcKY z7lRK`;d*Iv4QOE<^&xn%O>S_u6#*Nk9k^D17juES938_B8&5&i4xh5!84}qhxXnT+ zv|w7wL-0K|*^*KMyd8LJW&|I#{E=EC>~N#W7u2nh{ax=T@mzMp>;bS;J(xK$qwmQC z_y<7t8G-RZh^I=RW!(pSG{d*q@_{42m;54->-lcC)03#Ggqxvaww7dnoX+7Zg|5x1 z5?UD*vky*!!`F=$6!ff)if6R}-Uwu8ki?Q0b!kp_2wrY;Vb;F5E&)Edb^pzdUmBFC z_|=X~=EBn2tyU~P4UuNl2=WyY-zv@?adV97(0}sYA&@q!VmUb7nyOq{)}z4DC8NMr zp|M1czIU z=k}vCBeTKy6vV3~y^>Q)wb3+IVr`7t@s6{-h^IhUUAjG$93S!hHscfPcPBhS;-{pa z{WL@CY>m1Jj;%`@38mNPpB&KU7UL^Wy+>&~ZB@6PbaeGlp6%%A7_eie^bRqTBApV6 zl!))qEAVPZCN83oHg?zR2_dk+=F7d>2GRz#Xn4)G%a>X$Qv=~L<8;>LUd#^bU1z+K zp=sFw`J~iED5btFL$6A#iZLZI>Y4`EZKfr#pAb3~NV#yzebZ0sQsVz-p(i47Jz`Xq zzX8N86+SKm&b6!GLcOhJKP4Xj1YVT5KSoxS^KJM)w$|ZZsf8+o00000NkvXXu0mjf D4#5I> literal 0 HcmV?d00001 diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/72.png b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/72.png new file mode 100644 index 0000000000000000000000000000000000000000..d33f50de3477c2b87796b9bc94921876e75e3fc6 GIT binary patch literal 2676 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 g`QwrORm?8@55c0Zz%;Pu*^KO) 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 literal 0 HcmV?d00001 diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/Contents.json b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..859fae7f90 --- /dev/null +++ b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,158 @@ +{ + "images" : [ + { + "filename" : "40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "57.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "57x57" + }, + { + "filename" : "114.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "57x57" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "50.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "50x50" + }, + { + "filename" : "100.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "50x50" + }, + { + "filename" : "72.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "72x72" + }, + { + "filename" : "144.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "72x72" + }, + { + "filename" : "76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "appstore.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/appstore.png b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/AppIcon.appiconset/appstore.png new file mode 100644 index 0000000000000000000000000000000000000000..772f3ff1857cb29cb74ff53b2bb34138bb02e311 GIT binary patch literal 62918 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 literal 0 HcmV?d00001 diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/Contents.json b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Base.lproj/LaunchScreen.storyboard b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..865e9329f3 --- /dev/null +++ b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Base.lproj/Main.storyboard b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Base.lproj/Main.storyboard new file mode 100644 index 0000000000..071ca8c392 --- /dev/null +++ b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Base.lproj/Main.storyboard @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/ColorBlobDetector.swift b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/ColorBlobDetector.swift new file mode 100644 index 0000000000..82c34ab58d --- /dev/null +++ b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/ColorBlobDetector.swift @@ -0,0 +1,75 @@ +// +// ColorBlobDetector.swift +// +// Created by Giles Payne on 2020/04/04. +// + +import OpenCV + +public class ColorBlobDetector { + // Lower and Upper bounds for range checking in HSV color space + var lowerBound = Scalar(0.0) + var upperBound = Scalar(0.0) + // Minimum contour area in percent for contours filtering + static let minContourArea = 0.1 + // Color radius for range checking in HSV color space + var colorRadius = Scalar(25.0, 50.0, 50.0, 0.0) + let spectrum = Mat() + let contours = NSMutableArray() + + // Cache + let pyrDownMat = Mat() + let hsvMat = Mat() + let mask = Mat() + let dilatedMask = Mat() + let hierarchy = Mat() + + func setHsvColor(hsvColor:Scalar) { + let minH = (hsvColor.val[0].doubleValue >= colorRadius.val[0].doubleValue) ? hsvColor.val[0].doubleValue - colorRadius.val[0].doubleValue : 0 + let maxH = (hsvColor.val[0].doubleValue + colorRadius.val[0].doubleValue <= 255) ? hsvColor.val[0].doubleValue + colorRadius.val[0].doubleValue : 255 + + lowerBound = Scalar(minH, hsvColor.val[1].doubleValue - colorRadius.val[1].doubleValue, hsvColor.val[2].doubleValue - colorRadius.val[2].doubleValue, 0) + upperBound = Scalar(maxH, hsvColor.val[1].doubleValue + colorRadius.val[1].doubleValue, hsvColor.val[2].doubleValue + colorRadius.val[2].doubleValue, 255) + + let spectrumHsv = Mat(rows: 1, cols: (Int32)(maxH-minH), type:CvType.CV_8UC3); + + for j:Int32 in 0.. ColorBlobDetector.minContourArea * maxArea) { + Core.multiply(src1: contourMat, srcScalar: Scalar(4.0,4.0), dst: contourMat) + contours.add(NSMutableArray(array: contourMat.toArray())) + } + } + } +} diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Info.plist b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Info.plist new file mode 100644 index 0000000000..19b473c26f --- /dev/null +++ b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/Info.plist @@ -0,0 +1,49 @@ + + + + + NSCameraUsageDescription + Please allow access to the camera for image processing + UIUserInterfaceStyle + Light + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/ViewController.swift b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/ViewController.swift new file mode 100644 index 0000000000..4d2975dd56 --- /dev/null +++ b/samples/swift/ios/ColorBlobDetection/ColorBlobDetection/ViewController.swift @@ -0,0 +1,124 @@ +// +// ViewController.swift +// +// Created by Giles Payne on 2020/03/02. +// + +import UIKit +import OpenCV + +class ViewController: UIViewController, CvVideoCameraDelegate2 { + + var isColorSelected = false + var rgba: Mat? = nil + let detector = ColorBlobDetector() + let spectrum = Mat() + var blobColorRgba = Scalar(255.0) + var blobColorHsv = Scalar(255.0) + let SPECTRUM_SIZE = Size(width: 200, height: 64) + let CONTOUR_COLOR = Scalar(255.0, 0.0, 0.0, 255.0) + var cameraHolderWidth: CGFloat = 0 + var cameraHolderHeight: CGFloat = 0 + + func processImage(_ image: Mat!) { + rgba = image + if isColorSelected { + detector.process(rgbaImage: image) + let contours = detector.contours + NSLog("Contours count: \(contours.count))") + Imgproc.drawContours(image: image, contours: contours as! [[Point]], contourIdx: -1, color: CONTOUR_COLOR) + + let colorLabel = image.submat(rowStart: 4, rowEnd: 68, colStart: 4, colEnd: 68) + colorLabel.setTo(scalar: blobColorRgba) + + let spectrumLabel = image.submat(rowStart: 4, rowEnd: 4 + spectrum.rows(), colStart: 70, colEnd: 70 + spectrum.cols()) + spectrum.copy(to: spectrumLabel) + } + } + + var camera: CvVideoCamera2? = nil + + @IBOutlet weak var cameraHolder: UIView! + override func viewDidLoad() { + super.viewDidLoad() + camera = CvVideoCamera2(parentView: cameraHolder) + camera?.rotateVideo = true + camera?.delegate = self + camera?.start() + } + + override func viewDidLayoutSubviews() { + if UIDevice.current.orientation.isLandscape { + cameraHolderWidth = cameraHolder.bounds.height + cameraHolderHeight = cameraHolder.bounds.width + } else { + cameraHolderWidth = cameraHolder.bounds.width + cameraHolderHeight = cameraHolder.bounds.height + } + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + if let aRgba = rgba, touches.count == 1 { + let touch = touches.first! + let cols = CGFloat(aRgba.cols()) + let rows = CGFloat(aRgba.rows()) + + let orientation = UIDevice.current.orientation + var x = touch.location(in: cameraHolder).x + var y = touch.location(in: cameraHolder).y + if orientation == .landscapeLeft { + let tempX = x + x = cameraHolder.bounds.height - y + y = tempX + } else if orientation == .landscapeRight { + let tempY = y + y = cameraHolder.bounds.width - x + x = tempY + } + + x = x * (cols / cameraHolderWidth) + y = y * (rows / cameraHolderHeight) + + if ((x < 0) || (y < 0) || (x > cols) || (y > rows)) { + return + } + + let touchedRect = Rect() + + touchedRect.x = (x>4) ? Int32(x)-4 : 0; + touchedRect.y = (y>4) ? Int32(y)-4 : 0; + + touchedRect.width = (x+4 < cols) ? Int32(x) + 4 - touchedRect.x : Int32(cols) - touchedRect.x; + touchedRect.height = (y+4 < rows) ? Int32(y) + 4 - touchedRect.y : Int32(rows) - touchedRect.y; + + let touchedRegionRgba = aRgba.submat(roi: touchedRect) + + let touchedRegionHsv = Mat() + Imgproc.cvtColor(src: touchedRegionRgba, dst: touchedRegionHsv, code: .COLOR_RGB2HSV_FULL) + + // Calculate average color of touched region + blobColorHsv = Core.sum(src: touchedRegionHsv) + let pointCount = touchedRect.width*touchedRect.height + blobColorHsv = blobColorHsv.mul(Scalar.all(1.0/Double(pointCount))) + + blobColorRgba = converScalarHsv2Rgba(hsvColor: blobColorHsv) + + NSLog("Touched rgba color: (\(blobColorRgba.val[0]), \(blobColorRgba.val[1]), \( blobColorRgba.val[2]), \(blobColorRgba.val[3])") + + detector.setHsvColor(hsvColor: blobColorHsv) + + Imgproc.resize(src: detector.spectrum, dst: spectrum, dsize: SPECTRUM_SIZE, fx: 0, fy: 0, interpolation: InterpolationFlags.INTER_LINEAR_EXACT.rawValue) + + isColorSelected = true + } + } + + func converScalarHsv2Rgba(hsvColor:Scalar) -> Scalar { + let pointMatRgba = Mat() + let pointMatHsv = Mat(rows: 1, cols: 1, type: CvType.CV_8UC3, scalar: hsvColor) + Imgproc.cvtColor(src: pointMatHsv, dst: pointMatRgba, code: .COLOR_HSV2RGB_FULL, dstCn: 4) + let elementData = pointMatRgba.get(row: 0, col: 0) + return Scalar(vals: elementData as [NSNumber]) + } + +} diff --git a/samples/swift/ios/FaceDetection/FaceDetection.xcodeproj/project.pbxproj b/samples/swift/ios/FaceDetection/FaceDetection.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..bc07ef8910 --- /dev/null +++ b/samples/swift/ios/FaceDetection/FaceDetection.xcodeproj/project.pbxproj @@ -0,0 +1,407 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 5B14CF822452EED3003438BE /* OpenCV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B14CF812452EED3003438BE /* OpenCV.framework */; }; + 5B79BD652439FF020062DD99 /* DetectionBasedTracker.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B79BD642439FF020062DD99 /* DetectionBasedTracker.mm */; }; + 5B8C4C2D243814E9003D9176 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B8C4C2C243814A0003D9176 /* libc++.tbd */; }; + 5B8C4C2F2438383D003D9176 /* lbpcascade_frontalface.xml in Resources */ = {isa = PBXBuildFile; fileRef = 5B8C4C2E2438383D003D9176 /* lbpcascade_frontalface.xml */; }; + 5BBDB48A240D380F00BB10FE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBDB489240D380F00BB10FE /* AppDelegate.swift */; }; + 5BBDB48E240D380F00BB10FE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBDB48D240D380F00BB10FE /* ViewController.swift */; }; + 5BBDB491240D380F00BB10FE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5BBDB48F240D380F00BB10FE /* Main.storyboard */; }; + 5BBDB493240D381000BB10FE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5BBDB492240D381000BB10FE /* Assets.xcassets */; }; + 5BBDB496240D381000BB10FE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5BBDB494240D381000BB10FE /* LaunchScreen.storyboard */; }; + 5BBDB517241923C800BB10FE /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BBDB516241923C800BB10FE /* AVFoundation.framework */; }; + 5BBDB5192419241200BB10FE /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BBDB5182419241200BB10FE /* CoreMedia.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 5B14CF812452EED3003438BE /* OpenCV.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenCV.framework; path = ../../../OpenCV.framework; sourceTree = ""; }; + 5B79BD622439FEB60062DD99 /* DetectionBasedTracker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DetectionBasedTracker.h; sourceTree = ""; }; + 5B79BD632439FF020062DD99 /* FaceDetection-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FaceDetection-Bridging-Header.h"; sourceTree = ""; }; + 5B79BD642439FF020062DD99 /* DetectionBasedTracker.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = DetectionBasedTracker.mm; sourceTree = ""; }; + 5B8C4C2C243814A0003D9176 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; + 5B8C4C2E2438383D003D9176 /* lbpcascade_frontalface.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = lbpcascade_frontalface.xml; sourceTree = ""; }; + 5BBDB486240D380F00BB10FE /* FaceDetection.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FaceDetection.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 5BBDB489240D380F00BB10FE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 5BBDB48D240D380F00BB10FE /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 5BBDB490240D380F00BB10FE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 5BBDB492240D381000BB10FE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 5BBDB495240D381000BB10FE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 5BBDB497240D381000BB10FE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 5BBDB516241923C800BB10FE /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; + 5BBDB5182419241200BB10FE /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 5BBDB483240D380F00BB10FE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5BBDB5192419241200BB10FE /* CoreMedia.framework in Frameworks */, + 5B8C4C2D243814E9003D9176 /* libc++.tbd in Frameworks */, + 5BBDB517241923C800BB10FE /* AVFoundation.framework in Frameworks */, + 5B14CF822452EED3003438BE /* OpenCV.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 5BBDB47D240D380F00BB10FE = { + isa = PBXGroup; + children = ( + 5BBDB488240D380F00BB10FE /* FaceDetection */, + 5BBDB487240D380F00BB10FE /* Products */, + 5BBDB515241923C800BB10FE /* Frameworks */, + ); + sourceTree = ""; + }; + 5BBDB487240D380F00BB10FE /* Products */ = { + isa = PBXGroup; + children = ( + 5BBDB486240D380F00BB10FE /* FaceDetection.app */, + ); + name = Products; + sourceTree = ""; + }; + 5BBDB488240D380F00BB10FE /* FaceDetection */ = { + isa = PBXGroup; + children = ( + 5B14CF812452EED3003438BE /* OpenCV.framework */, + 5B8C4C2E2438383D003D9176 /* lbpcascade_frontalface.xml */, + 5BBDB489240D380F00BB10FE /* AppDelegate.swift */, + 5BBDB48D240D380F00BB10FE /* ViewController.swift */, + 5BBDB48F240D380F00BB10FE /* Main.storyboard */, + 5BBDB492240D381000BB10FE /* Assets.xcassets */, + 5BBDB494240D381000BB10FE /* LaunchScreen.storyboard */, + 5BBDB497240D381000BB10FE /* Info.plist */, + 5B79BD622439FEB60062DD99 /* DetectionBasedTracker.h */, + 5B79BD642439FF020062DD99 /* DetectionBasedTracker.mm */, + 5B79BD632439FF020062DD99 /* FaceDetection-Bridging-Header.h */, + ); + path = FaceDetection; + sourceTree = ""; + }; + 5BBDB515241923C800BB10FE /* Frameworks */ = { + isa = PBXGroup; + children = ( + 5B8C4C2C243814A0003D9176 /* libc++.tbd */, + 5BBDB5182419241200BB10FE /* CoreMedia.framework */, + 5BBDB516241923C800BB10FE /* AVFoundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 5BBDB485240D380F00BB10FE /* FaceDetection */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5BBDB4B0240D381000BB10FE /* Build configuration list for PBXNativeTarget "FaceDetection" */; + buildPhases = ( + 5BBDB482240D380F00BB10FE /* Sources */, + 5BBDB483240D380F00BB10FE /* Frameworks */, + 5BBDB484240D380F00BB10FE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = FaceDetection; + productName = FaceDetection; + productReference = 5BBDB486240D380F00BB10FE /* FaceDetection.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 5BBDB47E240D380F00BB10FE /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1130; + LastUpgradeCheck = 1130; + ORGANIZATIONNAME = OpenCV; + TargetAttributes = { + 5BBDB485240D380F00BB10FE = { + CreatedOnToolsVersion = 11.3.1; + LastSwiftMigration = 1130; + }; + }; + }; + buildConfigurationList = 5BBDB481240D380F00BB10FE /* Build configuration list for PBXProject "FaceDetection" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 5BBDB47D240D380F00BB10FE; + productRefGroup = 5BBDB487240D380F00BB10FE /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 5BBDB485240D380F00BB10FE /* FaceDetection */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 5BBDB484240D380F00BB10FE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5BBDB496240D381000BB10FE /* LaunchScreen.storyboard in Resources */, + 5BBDB493240D381000BB10FE /* Assets.xcassets in Resources */, + 5BBDB491240D380F00BB10FE /* Main.storyboard in Resources */, + 5B8C4C2F2438383D003D9176 /* lbpcascade_frontalface.xml in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 5BBDB482240D380F00BB10FE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5B79BD652439FF020062DD99 /* DetectionBasedTracker.mm in Sources */, + 5BBDB48E240D380F00BB10FE /* ViewController.swift in Sources */, + 5BBDB48A240D380F00BB10FE /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 5BBDB48F240D380F00BB10FE /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 5BBDB490240D380F00BB10FE /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 5BBDB494240D381000BB10FE /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 5BBDB495240D381000BB10FE /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 5BBDB4AE240D381000BB10FE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = ../..; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = "../../OpenCV/**"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = ""; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OBJC_BRIDGING_HEADER = "FaceDetection/FaceDetection-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 5BBDB4AF240D381000BB10FE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ../..; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = "../../OpenCV/**"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_LDFLAGS = ""; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OBJC_BRIDGING_HEADER = "FaceDetection/FaceDetection-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 5BBDB4B1240D381000BB10FE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = NO; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/../..", + ); + HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/../../OpenCV.framework/Headers"; + INFOPLIST_FILE = FaceDetection/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_SWIFT_FLAGS = "-Xcc -Wno-incomplete-umbrella"; + PRODUCT_BUNDLE_IDENTIFIER = org.opencv.FaceDetection; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "FaceDetection/FaceDetection-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 5BBDB4B2240D381000BB10FE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = NO; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/../..", + ); + HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/../../OpenCV.framework/Headers"; + INFOPLIST_FILE = FaceDetection/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_SWIFT_FLAGS = "-Xcc -Wno-incomplete-umbrella"; + PRODUCT_BUNDLE_IDENTIFIER = org.opencv.FaceDetection; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "FaceDetection/FaceDetection-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 5BBDB481240D380F00BB10FE /* Build configuration list for PBXProject "FaceDetection" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5BBDB4AE240D381000BB10FE /* Debug */, + 5BBDB4AF240D381000BB10FE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5BBDB4B0240D381000BB10FE /* Build configuration list for PBXNativeTarget "FaceDetection" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5BBDB4B1240D381000BB10FE /* Debug */, + 5BBDB4B2240D381000BB10FE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 5BBDB47E240D380F00BB10FE /* Project object */; +} diff --git a/samples/swift/ios/FaceDetection/FaceDetection.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/samples/swift/ios/FaceDetection/FaceDetection.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..3d4c1e5525 --- /dev/null +++ b/samples/swift/ios/FaceDetection/FaceDetection.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/samples/swift/ios/FaceDetection/FaceDetection/AppDelegate.swift b/samples/swift/ios/FaceDetection/FaceDetection/AppDelegate.swift new file mode 100644 index 0000000000..9965cc5086 --- /dev/null +++ b/samples/swift/ios/FaceDetection/FaceDetection/AppDelegate.swift @@ -0,0 +1,19 @@ +// +// AppDelegate.swift +// +// Created by Giles Payne on 2020/03/02. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + +} diff --git a/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/100.png b/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 0000000000000000000000000000000000000000..1e700dd332bad2da46295b370fede6503a7ecb9e GIT binary patch literal 3758 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 literal 0 HcmV?d00001 diff --git a/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/152.png b/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000000000000000000000000000000000000..99e2a23b4ab7d17d2eba771d9f9c075fb4ec41e7 GIT binary patch literal 5685 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 bw(kCabO&)RM*kNv00000NkvXXu0mjf5R;PL literal 0 HcmV?d00001 diff --git a/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/167.png b/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000000000000000000000000000000000000..ea93ffc144b373370f1069f8795ec9b18eebe3ed GIT binary patch literal 6547 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 Y1EVtSC%*^BkN^Mx07*qoM6N<$g0_4l)c^nh literal 0 HcmV?d00001 diff --git a/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/29.png b/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000000000000000000000000000000000000..cee7ce05b534fbbcfae1ffe06e7537221c973842 GIT binary patch literal 946 zcmV;j15NyiP)GK^%H3CpAc>!lbeFbIMmFtV{;YJ4)CAd{W~VM?^X zAashtN=M25K-mOM4}nYt{m~wZ9(t<%#Xk4kbI&<*@7&W&8_vTyYpw5F|MuGZi2tw* zcM-xM_8^d+N$e@$gy8F@P-?1Z7)9FCL;rW|=LD}b!D~e75DsB)?F%R!Qj8Dv)9XuW z*ogFDKcGTxZxDPkpMHN*X%e`%267DPE9~{a=88-@pco%rFsWe(DK$!G*;@r~v_Ck6 zG3@8d8nVM;NQaS5mH?(?-(K+n0@6P<$Ce9z>qR_odJRoTg%oR~s|DZwU!=w!5d2c4 z7uF)3t&t83PA!Nu8Cm)fxT*y3x$IjrSt}E%p2H>ijO;ZFu4x7w!i(5X6(G`tVtk~C zRQeRioXD0vDi~iJY1$IfdpXG8*w+ibFQs<`>2Ur&jlEfLeo>@(8t+6pjP^yQ-0z0im~nTe^V?9f^t$%tJUTvBzc`=(VbzS4o_a zn21r&7BvkHFBtFa1!_EmINePig2!yyv-hm6o6CA3c(2XV8JT!9MvVw_qtIu-k{alD zz{NrzMfE;4B%c9pD;lm6t=CATX?0OazCpoZuW{x{r&GNPF_yiXb%-yzXThr_zA4Kj z_Qj}tTX~yWI9ccC(H@YjcY(W0gpVO!ZPJCc)FzWDTa{A?{Dj2*s$uh}GY5Ln*LSZE>=cO%l!s4XuIftPK1 zT|K95c7l$#N>(k_ia^+rH`WCpJ+sF95e+WfelP?E?d(5iKw>gRJ_e3}{%lkI3$zVn U-e7r0!vFvP07*qoM6N<$g0OkAu>b%7 literal 0 HcmV?d00001 diff --git a/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/40.png b/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000000000000000000000000000000000000..0340a78dee95e6509123ed50b25fb5cdc17a31c0 GIT binary patch literal 1266 zcmV9-!cl^rk`)CS7DK6p zme_*`t+5AbRw6yIG9tvHB9e&mL;D}+%$>(Qci%hb+(~J}`Lg%gYyHkzd#(KtBW#9q zfJuZf340Q75a|tKd=JyimfuBVH(mpU{pn0yRfGTeylRDI-v9tFtdu00a?3Su&m15N`THnD)m+|P>|h^ zJ+X=xs{v-&37rYR1K7)fH}kO&W??T){zyeHmIiF$y?xlz1-pj=8M=_xCLl8}o+kLG zOe97QXfGB#lrm;1tA$x@tZ()Sg5M4o;8el3Bwz^L$xz#41mFLk0nZbBdbj{vg*eOj z#$s+y7QFwT0%poA{Q(?PP`n3wrr^s$l3auIS_0x%*(a8P%VbI?Ne&po3hX-*$}Kze z_Zy}lCD$d=1B&tPLjwqtk=|`kE-h1x-E|(6UIC^TWDjDG6Z~*U0Bd3X5V&ZFfnQ{w z)KE!aBGN~7{n9GMcw1Sx$|24H;R2*rfzi34g!cwQ9l|Q?`8By7#W<%5Tw8!O$gxOY z0N=@;(9Ed&y(q0$j9pdX+6Jsa9uIU0vU6e6<`7mZ#y&5$(JEze8~R5)B@JlC2f}RN zRO2zgSl~#Z4UxD3@y>`XmsHRQXu5^&0FG;+;}gphOWMd%>uTV4&k8pi}rbCoF${XK+cyah2@dSy_3ft)xTUR#D?epHyew#U< zo*YotcHrXSjk3e?B_-qt!OLy#Ey_CKMac(%V+*pc+nOfKQlYwibTIyUEE0QwuWQ>| zK)f|PppTOH3CL!HO5O@wo-jBSQpYfllK2@ox-h3V1Kb0QD-3*N zx!6~lRq{sQ`bI!C-MJ*EPTx7;UkUQvmd{TTW$Oa%O^j`_oWD4P;HPbF`L6(zS3~17 z5Z^X{m>EKw8jRzGsPEtqf>+oyoV;x=P46ZxLX5df=;1_?JQEdfZs@nNyT{oHz)neaMTzw>D!(Gs z)urID+IU{spA}jc6_e{{P=~LR$c|5?JlVH@>%RW^sRQd0k<(|d_s!Za<3|zCm-`u? zDY3H2+hV_py*7&|l(6P^64Mz7HyNK?x3cidh$qT{6{Xw}0v$F}YHv1MZQcg5DzX#u cr literal 0 HcmV?d00001 diff --git a/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/50.png b/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/50.png new file mode 100644 index 0000000000000000000000000000000000000000..d143fdcce436930623e21d5c7da1cb09efe53632 GIT binary patch literal 1667 zcmV-}27LL6P)BzUI7YEh>I8nyvuB3%sZQ6P5}_WgnNG;A66 zrGoEE^7SEa8;D^H(#ybr7L8enJznrfi~JR~wtyJ+L|P8)(~c3qv$A7MwY=H`62fEH z(^}Tvo6(*nnAfVTmVp?iAwAZr;uOALv5yvf-iplDfmDywaRo|OBfSF*!X5?eo=Y#y zQfY}E~NQ6 zyvGUNC<~^gqva4D!M-3>nKWNXa!H;cmc9dqq|(?^1dICzi1ZS0N`@gZU{~Y<385SN zh7A6z1b6lqkhg(xn+Zs_;Ku)fl-U7G*&b;wM!o@tWq7n7Jrcq#*w;Ds_98);k1T5tl=t2xTzEj>i=TU`|bMhn!{L`iZ{DR-9wK}HX6TD(T1vke zXMe}ow+q%3DXoE@AXwTAZ8J0-1JaDL%}J9F#9SB%qm4%b2lg^#wa}`B<5VLdaU+OA$ib-mTq*=V zV{??vSkPC+o91&Qme^bhdY~@r*D;p|f$`OdtQ$VlHp%Y!D6t?()Rhm!TS%3LZFI$t zW(MfKjWRYen{lGyax31VuPyIeOjb`J`4!-#-oM%$3d$J;d4^O!_p+G;)k?a0w9G+5 zuhk~!P1|LB0b*g(3{_>*CF+hvjsFE(6VqjRXNk4ImJQ!s+kh`BK!U?O;~C`);xo&` z%kg-7iCCYddeLSS=)*>tA$Yz`>d}*!nOfC2o{Csj21p3* zw#iE*Z`wMxUZzJ#=>=$wZoSQZ5lENFnKM-l??NmnV{{s=F+ae%aBmT)%kRQB8+*IZFY-L}Z?E?9;2*?=3yLmw5$}}atKgZ`I zYHuj+QuZ-z7s&buB=*)LQiBNHl_<%61~5ZK<#}P-G=Ad=h!<-f8Lga6vE#t5GThFJ zk*96W&iK4s!QoNIol9T;8jnZ3o(j}@9ymQSfblRy&A3V$&3y2T-bxZ>Oz}NZBDM^v zD_)B69+|F&O0t1^jq&w}B^!S?Lj&Ooe@{iuD7F ztSQRgfiTm!p`=b#xx)Bi#8V{=s*!uD6o&~MUckRWVqubSZ`z=uXs$Ih{<}da_7L%@ zEdGgXt4exApClwNQ5^_$V0nB9Otv|orYHW}D5m0dcI6Ix9<)A7(SQ8LTTcP0azFq8 N002ovPDHLkV1iQ`8&?1T literal 0 HcmV?d00001 diff --git a/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/57.png b/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000000000000000000000000000000000000..55d5723144f6a7984b843d5595099075be6c70ac GIT binary patch literal 2034 zcmV5f>02xEG~#a1XEPdmI@UJn^bK-Ku}9b(MlTEA~7|VSfaEBLM8B8 z3SG3spjbpf!(v3;q_wBuXpJ(Q|%L^yDA8zKHGv_}u z&zZA~m_jn_56nlJ54;vQ5O^i+ z)eU(2W*Iq1!-+^MfcN%qjW!YEGQp0%WitXJ1!qYB zr1bA>Y-%4Zc%WpQLs*Xel?>l)vd=8(nJSRa0{dika+=_ol5q_{ZkGMwlAfso`6%#~ z49M3B{$4W9A&hl0mTFdMjbcn0%|JBlalI#-ZP?2NmzFHElGJ)bb>)Q;Z8Ift3CPys;p?Rd)Xgc$d4i7SU*LKA6no6M3m||R9w+NPg4$Q6@zf1PXrGru$9~xw5H|s9*-Pnib?n=W+ zNOxBnYM(7gS3FQBp7|~gvcLOs8S2UGr$~Pq-n@;{h@ihl&jsa!`!kbcG!}vhs zJArh4Y(qT%LK)rfJb932Mj%{hd>ZL{tEZjDDLMWYDrX)28aXU~Ey&<7W?BuKe&{|c zU+yR?^98`epnJ=_6JFpYA#i1ks*JoULCV?&WNR~31{g0ytZQyuFJ}n8(dHb9O);wX z-ppS@7fG#-*b{b&o?0>S1Wf`d7fKrSlig!^=AZy2rwYP;6MJLM1l?BF=a);QTGNqT zvipJlIsU%~q^#sfK2TOXnZtM<8Z74v!8h2Pzn9iKZJkoK#h;ZXT|2kf`p_hh!Qt!1 zgI2`uwmi_{fIlel!^-1t=7BQFviFgA9C+2<@;1vst*S(OC(Wrs1Glt|Pe62sW#tX% za7e9dsBVGox78aDcWDm6%Wdkae7kIp13lIP85}m5Ox-hY^39g#HAdCL=mjb`Dx32_ zn<_q#10+&!rIsXC$EZ&WbF652(-PfpoVNcDKnlcX2Ew_KxDxS5Nw{iUa=IptFUbDc z)}-n|2wrQGuA3a$=8yz(snE*oil9rQ;_^X&)XTWI6Rj`FoCEr9#ZK-kk31q5lr#EkvWV{HmwzQGKVWaU!GvD?#QSqYj@esVq zrnbc(u{K849_Kytc#*`KjD6b@$UA^sZS!`^XJkQ|lZBF}d}#*cim3SIF5{X60!M-# z%FQkLVc>|&x4mA$VYSIuz=f`jil3@gWc?MGUT|PD5vdE-A-_!`3jo;M4&3#xD%~OY zN}JDQ=QT@GFfdmrIe{z*t&hYFh*a@zlbNCRMg8W2_+yqQ^)V&?w!w3kdu^8XCec1# zB0W+(sbsg!!L8U;R`Pl$lmt5CdqBebo5%=V6%{L1+{+}s+C&HK`tA6lq&KrosFkjb zXCW5|6`e~Htra}~F+L9Q`^jTIMk2Kcs{1GdPB?MU%*R0+Cz9siaFcO*VLVb2x;!dg zQm@pDCDL1-k&?t%jA}KS_EZMU;iA=e$vTieEqTOv4w~Gy>(H`sivPu)58?KX2j zyLv&k9m#w#vNx;G8lTncQS0n#)oFgz#E~>uh$rpz)^AVxIJsIk8lcZ~DtF27hUDoI zw*y1Hqq8{`T6g~hRo7kHK$F*dYeNgW6zYb-6KNk70xN9lUuV@DwAE%AXi^+oUqLtf z2f|Y0Vt=uH?)AM$+(;ouY_@1#BJsIWLfp~yyu@`ewg6T>=Pvg`Ov>qI2f}RQRNI^e z900A}#{)v!Bk>1_t<3=R!{zxQFyC&Ss1#qP-_xDnQ+6w+{z-R@wMzT{0W&$u*W#zf QHvj+t07*qoM6N<$f)-QbI{*Lx literal 0 HcmV?d00001 diff --git a/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/58.png b/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000000000000000000000000000000000000..e2653bcfdec2a44ee06769b43623c5d175d78cdf GIT binary patch literal 2073 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 D4#5I> literal 0 HcmV?d00001 diff --git a/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/72.png b/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/72.png new file mode 100644 index 0000000000000000000000000000000000000000..d33f50de3477c2b87796b9bc94921876e75e3fc6 GIT binary patch literal 2676 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 g`QwrORm?8@55c0Zz%;Pu*^KO) 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 literal 0 HcmV?d00001 diff --git a/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/Contents.json b/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..859fae7f90 --- /dev/null +++ b/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,158 @@ +{ + "images" : [ + { + "filename" : "40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "57.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "57x57" + }, + { + "filename" : "114.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "57x57" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "50.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "50x50" + }, + { + "filename" : "100.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "50x50" + }, + { + "filename" : "72.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "72x72" + }, + { + "filename" : "144.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "72x72" + }, + { + "filename" : "76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "appstore.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/appstore.png b/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/AppIcon.appiconset/appstore.png new file mode 100644 index 0000000000000000000000000000000000000000..772f3ff1857cb29cb74ff53b2bb34138bb02e311 GIT binary patch literal 62918 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 literal 0 HcmV?d00001 diff --git a/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/Contents.json b/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/samples/swift/ios/FaceDetection/FaceDetection/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/samples/swift/ios/FaceDetection/FaceDetection/Base.lproj/LaunchScreen.storyboard b/samples/swift/ios/FaceDetection/FaceDetection/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..865e9329f3 --- /dev/null +++ b/samples/swift/ios/FaceDetection/FaceDetection/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/swift/ios/FaceDetection/FaceDetection/Base.lproj/Main.storyboard b/samples/swift/ios/FaceDetection/FaceDetection/Base.lproj/Main.storyboard new file mode 100644 index 0000000000..071ca8c392 --- /dev/null +++ b/samples/swift/ios/FaceDetection/FaceDetection/Base.lproj/Main.storyboard @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/swift/ios/FaceDetection/FaceDetection/DetectionBasedTracker.h b/samples/swift/ios/FaceDetection/FaceDetection/DetectionBasedTracker.h new file mode 100644 index 0000000000..ee9156076c --- /dev/null +++ b/samples/swift/ios/FaceDetection/FaceDetection/DetectionBasedTracker.h @@ -0,0 +1,32 @@ +// +// DetectionBasedTracker.h +// +// Created by Giles Payne on 2020/04/05. +// + +#pragma once + +#ifdef __cplusplus +#import +#endif + +#import + +@class Rect2i; +@class Mat; + +@interface DetectionBasedTracker : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithCascadeName:(NSString*)cascadeName minFaceSize:(int)minFaceSize; + +- (void)start; + +- (void)stop; + +- (void)setFaceSize:(int)size; + +- (void)detect:(Mat*)imageGray faces:(NSMutableArray*)faces; + +@end diff --git a/samples/swift/ios/FaceDetection/FaceDetection/DetectionBasedTracker.mm b/samples/swift/ios/FaceDetection/FaceDetection/DetectionBasedTracker.mm new file mode 100644 index 0000000000..2bc5a22956 --- /dev/null +++ b/samples/swift/ios/FaceDetection/FaceDetection/DetectionBasedTracker.mm @@ -0,0 +1,85 @@ +// +// DetectionBasedTracker.mm +// +// Created by Giles Payne on 2020/04/05. +// + +#import "DetectionBasedTracker.h" +#import "Mat.h" +#import "Rect2i.h" +#import "CVObjcUtil.h" + +class CascadeDetectorAdapter: public cv::DetectionBasedTracker::IDetector +{ +public: + CascadeDetectorAdapter(cv::Ptr detector):IDetector(), Detector(detector) {} + + void detect(const cv::Mat &Image, std::vector &objects) + { + Detector->detectMultiScale(Image, objects, scaleFactor, minNeighbours, 0, minObjSize, maxObjSize); + } + + virtual ~CascadeDetectorAdapter() {} + +private: + CascadeDetectorAdapter(); + cv::Ptr Detector; +}; + + +struct DetectorAgregator +{ + cv::Ptr mainDetector; + cv::Ptr trackingDetector; + cv::Ptr tracker; + DetectorAgregator(cv::Ptr& _mainDetector, cv::Ptr& _trackingDetector):mainDetector(_mainDetector), trackingDetector(_trackingDetector) { + CV_Assert(_mainDetector); + CV_Assert(_trackingDetector); + cv::DetectionBasedTracker::Parameters DetectorParams; + tracker = cv::makePtr(mainDetector, trackingDetector, DetectorParams); + } +}; + +@implementation DetectionBasedTracker { + DetectorAgregator* agregator; +} + +- (instancetype)initWithCascadeName:(NSString*)cascadeName minFaceSize:(int)faceSize { + self = [super init]; + if (self) { + auto mainDetector = cv::makePtr(cv::makePtr(cascadeName.UTF8String)); + auto trackingDetector = cv::makePtr( + cv::makePtr(cascadeName.UTF8String)); + agregator = new DetectorAgregator(mainDetector, trackingDetector); + if (faceSize > 0) { + agregator->mainDetector->setMinObjectSize(cv::Size(faceSize, faceSize)); + } + } + return self; +} + +- (void)dealloc +{ + delete agregator; +} + +- (void)start { + agregator->tracker->run(); +} + +- (void)stop { + agregator->tracker->stop(); +} + +- (void)setFaceSize:(int)size { + agregator->mainDetector->setMinObjectSize(cv::Size(size, size)); +} + +- (void)detect:(Mat*)imageGray faces:(NSMutableArray*)faces { + std::vector rectFaces; + agregator->tracker->process(*((cv::Mat*)imageGray.nativePtr)); + agregator->tracker->getObjects(rectFaces); + CV2OBJC(cv::Rect, Rect2i, rectFaces, faces); +} + +@end diff --git a/samples/swift/ios/FaceDetection/FaceDetection/FaceDetection-Bridging-Header.h b/samples/swift/ios/FaceDetection/FaceDetection/FaceDetection-Bridging-Header.h new file mode 100644 index 0000000000..260d556fa5 --- /dev/null +++ b/samples/swift/ios/FaceDetection/FaceDetection/FaceDetection-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "DetectionBasedTracker.h" diff --git a/samples/swift/ios/FaceDetection/FaceDetection/Info.plist b/samples/swift/ios/FaceDetection/FaceDetection/Info.plist new file mode 100644 index 0000000000..19b473c26f --- /dev/null +++ b/samples/swift/ios/FaceDetection/FaceDetection/Info.plist @@ -0,0 +1,49 @@ + + + + + NSCameraUsageDescription + Please allow access to the camera for image processing + UIUserInterfaceStyle + Light + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/samples/swift/ios/FaceDetection/FaceDetection/ViewController.swift b/samples/swift/ios/FaceDetection/FaceDetection/ViewController.swift new file mode 100644 index 0000000000..2fc6415b0c --- /dev/null +++ b/samples/swift/ios/FaceDetection/FaceDetection/ViewController.swift @@ -0,0 +1,90 @@ +// +// ViewController.swift +// +// Created by Giles Payne on 2020/03/02. +// + +import UIKit +import OpenCV + +extension Rect { + func rotateClockwise(parentHeight:Int32) { + let tmpX = self.x + self.x = parentHeight - (self.y + self.height) + self.y = tmpX + swapDims() + } + + func rotateCounterclockwise(parentWidth:Int32) { + let tmpY = self.y + self.y = parentWidth - (self.x + self.width) + self.x = tmpY + swapDims() + } + + func swapDims() { + let tmpWidth = self.width + self.width = self.height + self.height = tmpWidth + } +} + +class ViewController: UIViewController, CvVideoCameraDelegate2 { + + let swiftDetector = CascadeClassifier(filename: Bundle(for: ViewController.self).path(forResource:"lbpcascade_frontalface", ofType:"xml")!) + let nativeDetector = DetectionBasedTracker(cascadeName: Bundle(for: ViewController.self).path(forResource:"lbpcascade_frontalface", ofType:"xml")!, minFaceSize: 0) + var rgba: Mat? = nil + var gray: Mat = Mat() + var relativeFaceSize: Float = 0.2 + var absoluteFaceSize: Int32 = 0 + let FACE_RECT_COLOR = Scalar(0.0, 255.0, 0.0, 255.0) + let FACE_RECT_THICKNESS: Int32 = 4 + + func processImage(_ image: Mat!) { + let orientation = UIDevice.current.orientation + switch orientation { + case .landscapeLeft: + rgba = Mat() + Core.rotate(src: image, dst: rgba!, rotateCode: .ROTATE_90_COUNTERCLOCKWISE) + case .landscapeRight: + rgba = Mat() + Core.rotate(src: image, dst: rgba!, rotateCode: .ROTATE_90_CLOCKWISE) + default: + rgba = image + } + + Imgproc.cvtColor(src: rgba!, dst: gray, code: .COLOR_RGB2GRAY) + + if (absoluteFaceSize == 0) { + let height = gray.rows() + if (round(Float(height) * relativeFaceSize) > 0) { + absoluteFaceSize = Int32(round(Float(height) * relativeFaceSize)) + } + } + + let faces = NSMutableArray() + + swiftDetector.detectMultiScale(image: gray, objects: faces, scaleFactor: 1.1, minNeighbors: Int32(2), flags: Int32(2), minSize: Size(width: absoluteFaceSize, height: absoluteFaceSize), maxSize: Size()) + //nativeDetector!.detect(gray, faces: faces) + + for face in faces as! [Rect] { + if orientation == .landscapeLeft { + face.rotateClockwise(parentHeight: gray.rows()) + } else if orientation == .landscapeRight { + face.rotateCounterclockwise(parentWidth: gray.cols()) + } + Imgproc.rectangle(img: image, pt1: face.tl(), pt2: face.br(), color: FACE_RECT_COLOR, thickness: FACE_RECT_THICKNESS) + } + } + + var camera: CvVideoCamera2? = nil + + @IBOutlet weak var cameraHolder: UIView! + override func viewDidLoad() { + super.viewDidLoad() + camera = CvVideoCamera2(parentView: cameraHolder) + camera?.rotateVideo = true + camera?.delegate = self + camera?.start() + } +} diff --git a/samples/swift/ios/FaceDetection/FaceDetection/lbpcascade_frontalface.xml b/samples/swift/ios/FaceDetection/FaceDetection/lbpcascade_frontalface.xml new file mode 100644 index 0000000000..fc7648ef51 --- /dev/null +++ b/samples/swift/ios/FaceDetection/FaceDetection/lbpcascade_frontalface.xml @@ -0,0 +1,1505 @@ + + + + + BOOST + LBP + 24 + 24 + + GAB + 0.9950000047683716 + 0.5000000000000000 + 0.9500000000000000 + 1 + 100 + + 256 + 20 + + + <_> + 3 + -0.7520892024040222 + + + <_> + + 0 -1 46 -67130709 -21569 -1426120013 -1275125205 -21585 + -16385 587145899 -24005 + + -0.6543210148811340 0.8888888955116272 + + <_> + + 0 -1 13 -163512766 -769593758 -10027009 -262145 -514457854 + -193593353 -524289 -1 + + -0.7739216089248657 0.7278633713722229 + + <_> + + 0 -1 2 -363936790 -893203669 -1337948010 -136907894 + 1088782736 -134217726 -741544961 -1590337 + + -0.7068563103675842 0.6761534214019775 + + <_> + 4 + -0.4872078299522400 + + + <_> + + 0 -1 84 2147483647 1946124287 -536870913 2147450879 + 738132490 1061101567 243204619 2147446655 + + -0.8083735704421997 0.7685696482658386 + + <_> + + 0 -1 21 2147483647 263176079 1879048191 254749487 1879048191 + -134252545 -268435457 801111999 + + -0.7698410153388977 0.6592915654182434 + + <_> + + 0 -1 106 -98110272 1610939566 -285484400 -850010381 + -189334372 -1671954433 -571026695 -262145 + + -0.7506558895111084 0.5444605946540833 + + <_> + + 0 -1 48 -798690576 -131075 1095771153 -237144073 -65569 -1 + -216727745 -69206049 + + -0.7775990366935730 0.5465461611747742 + + <_> + 4 + -1.1592328548431396 + + + <_> + + 0 -1 47 -21585 -20549 -100818262 -738254174 -20561 -36865 + -151016790 -134238549 + + -0.5601882934570313 0.7743113040924072 + + <_> + + 0 -1 12 -286003217 183435247 -268994614 -421330945 + -402686081 1090387966 -286785545 -402653185 + + -0.6124526262283325 0.6978127956390381 + + <_> + + 0 -1 26 -50347012 970882927 -50463492 -1253377 -134218251 + -50364513 -33619992 -172490753 + + -0.6114496588706970 0.6537628173828125 + + <_> + + 0 -1 8 -273 -135266321 1877977738 -2088243418 -134217987 + 2146926575 -18910642 1095231247 + + -0.6854077577590942 0.5403239130973816 + + <_> + 5 + -0.7562355995178223 + + + <_> + + 0 -1 96 -1273 1870659519 -20971602 -67633153 -134250731 + 2004875127 -250 -150995969 + + -0.4051094949245453 0.7584033608436585 + + <_> + + 0 -1 33 -868162224 -76810262 -4262145 -257 1465211989 + -268959873 -2656269 -524289 + + -0.7388162612915039 0.5340843200683594 + + <_> + + 0 -1 57 -12817 -49 -541103378 -152950 -38993 -20481 -1153876 + -72478976 + + -0.6582943797111511 0.5339496731758118 + + <_> + + 0 -1 125 -269484161 -452984961 -319816180 -1594032130 -2111 + -990117891 -488975296 -520947741 + + -0.5981323719024658 0.5323504805564880 + + <_> + + 0 -1 53 557787431 670265215 -1342193665 -1075892225 + 1998528318 1056964607 -33570977 -1 + + -0.6498787999153137 0.4913350641727448 + + <_> + 5 + -0.8085358142852783 + + + <_> + + 0 -1 60 -536873708 880195381 -16842788 -20971521 -176687276 + -168427659 -16777260 -33554626 + + -0.5278195738792419 0.6946372389793396 + + <_> + + 0 -1 7 -1 -62981529 -1090591130 805330978 -8388827 -41945787 + -39577 -531118985 + + -0.5206505060195923 0.6329920291900635 + + <_> + + 0 -1 98 -725287348 1347747543 -852489 -16809993 1489881036 + -167903241 -1 -1 + + -0.7516061067581177 0.4232024252414703 + + <_> + + 0 -1 44 -32777 1006582562 -65 935312171 -8388609 -1078198273 + -1 733886267 + + -0.7639313936233521 0.4123568832874298 + + <_> + + 0 -1 24 -85474705 2138828511 -1036436754 817625855 + 1123369029 -58796809 -1013468481 -194513409 + + -0.5123769044876099 0.5791834592819214 + + <_> + 5 + -0.5549971461296082 + + + <_> + + 0 -1 42 -17409 -20481 -268457797 -134239493 -17473 -1 -21829 + -21846 + + -0.3763174116611481 0.7298233509063721 + + <_> + + 0 -1 6 -805310737 -2098262358 -269504725 682502698 + 2147483519 1740574719 -1090519233 -268472385 + + -0.5352765917778015 0.5659480094909668 + + <_> + + 0 -1 61 -67109678 -6145 -8 -87884584 -20481 -1073762305 + -50856216 -16849696 + + -0.5678374171257019 0.4961479902267456 + + <_> + + 0 -1 123 -138428633 1002418167 -1359008245 -1908670465 + -1346685918 910098423 -1359010520 -1346371657 + + -0.5706262588500977 0.4572288393974304 + + <_> + + 0 -1 9 -89138513 -4196353 1256531674 -1330665426 1216308261 + -36190633 33498198 -151796633 + + -0.5344601869583130 0.4672054052352905 + + <_> + 5 + -0.8776460289955139 + + + <_> + + 0 -1 105 1073769576 206601725 -34013449 -33554433 -789514004 + -101384321 -690225153 -264193 + + -0.7700348496437073 0.5943940877914429 + + <_> + + 0 -1 30 -1432340997 -823623681 -49153 -34291724 -269484035 + -1342767105 -1078198273 -1277955 + + -0.5043668746948242 0.6151274442672730 + + <_> + + 0 -1 35 -1067385040 -195758209 -436748425 -134217731 + -50855988 -129 -1 -1 + + -0.6808040738105774 0.4667325913906097 + + <_> + + 0 -1 119 832534325 -34111555 -26050561 -423659521 -268468364 + 2105014143 -2114244 -17367185 + + -0.4927591383457184 0.5401885509490967 + + <_> + + 0 -1 82 -1089439888 -1080524865 2143059967 -1114121 + -1140949004 -3 -2361356 -739516 + + -0.6445107460021973 0.4227822124958038 + + <_> + 6 + -1.1139287948608398 + + + <_> + + 0 -1 52 -1074071553 -1074003969 -1 -1280135430 -5324817 -1 + -335548482 582134442 + + -0.5307556986808777 0.6258179545402527 + + <_> + + 0 -1 99 -706937396 -705364068 -540016724 -570495027 + -570630659 -587857963 -33628164 -35848193 + + -0.5227634310722351 0.5049746036529541 + + <_> + + 0 -1 18 -2035630093 42119158 -268503053 -1671444 261017599 + 1325432815 1954394111 -805306449 + + -0.4983572661876679 0.5106441378593445 + + <_> + + 0 -1 111 -282529488 -1558073088 1426018736 -170526448 + -546832487 -5113037 -34243375 -570427929 + + -0.4990860521793366 0.5060507059097290 + + <_> + + 0 -1 92 1016332500 -606301707 915094269 -1080086049 + -1837027144 -1361600280 2147318747 1067975613 + + -0.5695009231567383 0.4460467398166657 + + <_> + + 0 -1 51 -656420166 -15413034 -141599534 -603435836 + 1505950458 -787556946 -79823438 -1326199134 + + -0.6590405106544495 0.3616424500942230 + + <_> + 7 + -0.8243625760078430 + + + <_> + + 0 -1 28 -901591776 -201916417 -262 -67371009 -143312112 + -524289 -41943178 -1 + + -0.4972776770591736 0.6027074456214905 + + <_> + + 0 -1 112 -4507851 -411340929 -268437513 -67502145 -17350859 + -32901 -71344315 -29377 + + -0.4383158981800079 0.5966237187385559 + + <_> + + 0 -1 69 -75894785 -117379438 -239063587 -12538500 1485072126 + 2076233213 2123118847 801906927 + + -0.6386105418205261 0.3977999985218048 + + <_> + + 0 -1 19 -823480413 786628589 -16876049 -1364262914 242165211 + 1315930109 -696268833 -455082829 + + -0.5512794256210327 0.4282079637050629 + + <_> + + 0 -1 73 -521411968 6746762 -1396236286 -2038436114 + -185612509 57669627 -143132877 -1041235973 + + -0.6418755054473877 0.3549866080284119 + + <_> + + 0 -1 126 -478153869 1076028979 -1645895615 1365298272 + -557859073 -339771473 1442574528 -1058802061 + + -0.4841901361942291 0.4668019413948059 + + <_> + + 0 -1 45 -246350404 -1650402048 -1610612745 -788400696 + 1467604861 -2787397 1476263935 -4481349 + + -0.5855734348297119 0.3879135847091675 + + <_> + 7 + -1.2237116098403931 + + + <_> + + 0 -1 114 -24819 1572863935 -16809993 -67108865 2146778388 + 1433927541 -268608444 -34865205 + + -0.2518476545810700 0.7088654041290283 + + <_> + + 0 -1 97 -1841359 -134271049 -32769 -5767369 -1116675 -2185 + -8231 -33603327 + + -0.4303432404994965 0.5283288359642029 + + <_> + + 0 -1 25 -1359507589 -1360593090 -1073778729 -269553812 + -809512977 1744707583 -41959433 -134758978 + + -0.4259553551673889 0.5440809130668640 + + <_> + + 0 -1 34 729753407 -134270989 -1140907329 -235200777 + 658456383 2147467263 -1140900929 -16385 + + -0.5605589151382446 0.4220733344554901 + + <_> + + 0 -1 134 -310380553 -420675595 -193005472 -353568129 + 1205338070 -990380036 887604324 -420544526 + + -0.5192656517028809 0.4399855434894562 + + <_> + + 0 -1 16 -1427119361 1978920959 -287119734 -487068946 + 114759245 -540578051 -707510259 -671660453 + + -0.5013077259063721 0.4570254683494568 + + <_> + + 0 -1 74 -738463762 -889949281 -328301948 -121832450 + -1142658284 -1863576559 2146417353 -263185 + + -0.4631414115428925 0.4790246188640595 + + <_> + 7 + -0.5544230937957764 + + + <_> + + 0 -1 113 -76228780 -65538 -1 -67174401 -148007 -33 -221796 + -272842924 + + -0.3949716091156006 0.6082032322883606 + + <_> + + 0 -1 110 369147696 -1625232112 2138570036 -1189900 790708019 + -1212613127 799948719 -4456483 + + -0.4855885505676270 0.4785369932651520 + + <_> + + 0 -1 37 784215839 -290015241 536832799 -402984963 + -1342414991 -838864897 -176769 -268456129 + + -0.4620285332202911 0.4989669024944305 + + <_> + + 0 -1 41 -486418688 -171915327 -340294900 -21938 -519766032 + -772751172 -73096060 -585322623 + + -0.6420643329620361 0.3624351918697357 + + <_> + + 0 -1 117 -33554953 -475332625 -1423463824 -2077230421 + -4849669 -2080505925 -219032928 -1071915349 + + -0.4820112884044647 0.4632140696048737 + + <_> + + 0 -1 65 -834130468 -134217476 -1349314083 -1073803559 + -619913764 -1449131844 -1386890321 -1979118423 + + -0.4465552568435669 0.5061788558959961 + + <_> + + 0 -1 56 -285249779 1912569855 -16530 -1731022870 -1161904146 + -1342177297 -268439634 -1464078708 + + -0.5190586447715759 0.4441480338573456 + + <_> + 7 + -0.7161560654640198 + + + <_> + + 0 -1 20 1246232575 1078001186 -10027057 60102 -277348353 + -43646987 -1210581153 1195769615 + + -0.4323809444904327 0.5663768053054810 + + <_> + + 0 -1 15 -778583572 -612921106 -578775890 -4036478 + -1946580497 -1164766570 -1986687009 -12103599 + + -0.4588732719421387 0.4547033011913300 + + <_> + + 0 -1 129 -1073759445 2013231743 -1363169553 -1082459201 + -1414286549 868185983 -1356133589 -1077936257 + + -0.5218553543090820 0.4111092388629913 + + <_> + + 0 -1 102 -84148365 -2093417722 -1204850272 564290299 + -67121221 -1342177350 -1309195902 -776734797 + + -0.4920000731945038 0.4326725304126740 + + <_> + + 0 -1 88 -25694458 67104495 -290216278 -168563037 2083877442 + 1702788383 -144191964 -234882162 + + -0.4494568109512329 0.4448510706424713 + + <_> + + 0 -1 59 -857980836 904682741 -1612267521 232279415 + 1550862252 -574825221 -357380888 -4579409 + + -0.5180826783180237 0.3888972699642181 + + <_> + + 0 -1 27 -98549440 -137838400 494928389 -246013630 939541351 + -1196072350 -620603549 2137216273 + + -0.6081240773200989 0.3333222270011902 + + <_> + 8 + -0.6743940711021423 + + + <_> + + 0 -1 29 -150995201 2071191945 -1302151626 536934335 + -1059008937 914128709 1147328110 -268369925 + + -0.1790193915367127 0.6605972051620483 + + <_> + + 0 -1 128 -134509479 1610575703 -1342177289 1861484541 + -1107833788 1577058173 -333558568 -136319041 + + -0.3681024610996246 0.5139749646186829 + + <_> + + 0 -1 70 -1 1060154476 -1090984524 -630918524 -539492875 + 779616255 -839568424 -321 + + -0.3217232525348663 0.6171553134918213 + + <_> + + 0 -1 4 -269562385 -285029906 -791084350 -17923776 235286671 + 1275504943 1344390399 -966276889 + + -0.4373284578323364 0.4358185231685638 + + <_> + + 0 -1 76 17825984 -747628419 595427229 1474759671 575672208 + -1684005538 872217086 -1155858277 + + -0.4404836893081665 0.4601220190525055 + + <_> + + 0 -1 124 -336593039 1873735591 -822231622 -355795238 + -470820869 -1997537409 -1057132384 -1015285005 + + -0.4294152259826660 0.4452161788940430 + + <_> + + 0 -1 54 -834212130 -593694721 -322142257 -364892500 + -951029539 -302125121 -1615106053 -79249765 + + -0.3973052501678467 0.4854526817798615 + + <_> + + 0 -1 95 1342144479 2147431935 -33554561 -47873 -855685912 -1 + 1988052447 536827383 + + -0.7054683566093445 0.2697997391223908 + + <_> + 9 + -1.2042298316955566 + + + <_> + + 0 -1 39 1431368960 -183437936 -537002499 -137497097 + 1560590321 -84611081 -2097193 -513 + + -0.5905947685241699 0.5101932883262634 + + <_> + + 0 -1 120 -1645259691 2105491231 2130706431 1458995007 + -8567536 -42483883 -33780003 -21004417 + + -0.4449204802513123 0.4490709304809570 + + <_> + + 0 -1 89 -612381022 -505806938 -362027516 -452985106 + 275854917 1920431639 -12600561 -134221825 + + -0.4693818688392639 0.4061094820499420 + + <_> + + 0 -1 14 -805573153 -161 -554172679 -530519488 -16779441 + 2000682871 -33604275 -150997129 + + -0.3600351214408875 0.5056326985359192 + + <_> + + 0 -1 67 6192 435166195 1467449341 2046691505 -1608493775 + -4755729 -1083162625 -71365637 + + -0.4459891915321350 0.4132415652275085 + + <_> + + 0 -1 86 -41689215 -3281034 1853357967 -420712635 -415924289 + -270209208 -1088293113 -825311232 + + -0.4466069042682648 0.4135067760944367 + + <_> + + 0 -1 80 -117391116 -42203396 2080374461 -188709 -542008165 + -356831940 -1091125345 -1073796897 + + -0.3394956290721893 0.5658645033836365 + + <_> + + 0 -1 75 -276830049 1378714472 -1342181951 757272098 + 1073740607 -282199241 -415761549 170896931 + + -0.5346512198448181 0.3584479391574860 + + <_> + + 0 -1 55 -796075825 -123166849 2113667055 -217530421 + -1107432194 -16385 -806359809 -391188771 + + -0.4379335641860962 0.4123645126819611 + + <_> + 10 + -0.8402050137519836 + + + <_> + + 0 -1 71 -890246622 15525883 -487690486 47116238 -1212319899 + -1291847681 -68159890 -469829921 + + -0.2670986354351044 0.6014143228530884 + + <_> + + 0 -1 31 -1361180685 -1898008841 -1090588811 -285410071 + -1074016265 -840443905 2147221487 -262145 + + -0.4149844348430634 0.4670888185501099 + + <_> + + 0 -1 40 1426190596 1899364271 2142731795 -142607505 + -508232452 -21563393 -41960001 -65 + + -0.4985891580581665 0.3719584941864014 + + <_> + + 0 -1 109 -201337965 10543906 -236498096 -746195597 + 1974565825 -15204415 921907633 -190058309 + + -0.4568729996681213 0.3965812027454376 + + <_> + + 0 -1 130 -595026732 -656401928 -268649235 -571490699 + -440600392 -133131 -358810952 -2004088646 + + -0.4770836830139160 0.3862601518630981 + + <_> + + 0 -1 66 941674740 -1107882114 1332789109 -67691015 + -1360463693 -1556612430 -609108546 733546933 + + -0.4877715110778809 0.3778986334800720 + + <_> + + 0 -1 49 -17114945 -240061474 1552871558 -82775604 -932393844 + -1308544889 -532635478 -99042357 + + -0.3721654713153839 0.4994400143623352 + + <_> + + 0 -1 133 -655906006 1405502603 -939205164 1884929228 + -498859222 559417357 -1928559445 -286264385 + + -0.3934195041656494 0.4769641458988190 + + <_> + + 0 -1 0 -335837777 1860677295 -90 -1946186226 931096183 + 251612987 2013265917 -671232197 + + -0.4323300719261169 0.4342164099216461 + + <_> + + 0 -1 103 37769424 -137772680 374692301 2002666345 -536176194 + -1644484728 807009019 1069089930 + + -0.4993278682231903 0.3665378093719482 + + <_> + 9 + -1.1974394321441650 + + + <_> + + 0 -1 43 -5505 2147462911 2143265466 -4511070 -16450 -257 + -201348440 -71333206 + + -0.3310225307941437 0.5624626278877258 + + <_> + + 0 -1 90 -136842268 -499330741 2015250980 -87107126 + -641665744 -788524639 -1147864792 -134892563 + + -0.5266560912132263 0.3704403042793274 + + <_> + + 0 -1 104 -146800880 -1780368555 2111170033 -140904684 + -16777551 -1946681885 -1646463595 -839131947 + + -0.4171888828277588 0.4540435671806335 + + <_> + + 0 -1 85 -832054034 -981663763 -301990281 -578814081 + -932319000 -1997406723 -33555201 -69206017 + + -0.4556705355644226 0.3704262077808380 + + <_> + + 0 -1 24 -118492417 -1209026825 1119023838 -1334313353 + 1112948738 -297319313 1378887291 -139469193 + + -0.4182529747486115 0.4267231225967407 + + <_> + + 0 -1 78 -1714382628 -2353704 -112094959 -549613092 + -1567058760 -1718550464 -342315012 -1074972227 + + -0.3625369668006897 0.4684656262397766 + + <_> + + 0 -1 5 -85219702 316836394 -33279 1904970288 2117267315 + -260901769 -621461759 -88607770 + + -0.4742925167083740 0.3689507246017456 + + <_> + + 0 -1 11 -294654041 -353603585 -1641159686 -50331921 + -2080899877 1145569279 -143132713 -152044037 + + -0.3666271567344666 0.4580127298831940 + + <_> + + 0 -1 32 1887453658 -638545712 -1877976819 -34320972 + -1071067983 -661345416 -583338277 1060190561 + + -0.4567637443542481 0.3894708156585693 + + <_> + 9 + -0.5733128190040588 + + + <_> + + 0 -1 122 -994063296 1088745462 -318837116 -319881377 + 1102566613 1165490103 -121679694 -134744129 + + -0.4055117964744568 0.5487945079803467 + + <_> + + 0 -1 68 -285233233 -538992907 1811935199 -369234005 -529 + -20593 -20505 -1561401854 + + -0.3787897229194641 0.4532003402709961 + + <_> + + 0 -1 58 -1335245632 1968917183 1940861695 536816369 + -1226071367 -570908176 457026619 1000020667 + + -0.4258328974246979 0.4202791750431061 + + <_> + + 0 -1 94 -1360318719 -1979797897 -50435249 -18646473 + -608879292 -805306691 -269304244 -17840167 + + -0.4561023116111755 0.4002747833728790 + + <_> + + 0 -1 87 2062765935 -16449 -1275080721 -16406 45764335 + -1090552065 -772846337 -570464322 + + -0.4314672648906708 0.4086346626281738 + + <_> + + 0 -1 127 -536896021 1080817663 -738234288 -965478709 + -2082767969 1290855887 1993822934 -990381609 + + -0.4174543321132660 0.4249868988990784 + + <_> + + 0 -1 3 -818943025 168730891 -293610428 -79249354 669224671 + 621166734 1086506807 1473768907 + + -0.4321364760398865 0.4090838730335236 + + <_> + + 0 -1 79 -68895696 -67107736 -1414315879 -841676168 + -619843344 -1180610531 -1081990469 1043203389 + + -0.5018386244773865 0.3702533841133118 + + <_> + + 0 -1 116 -54002134 -543485719 -2124882422 -1437445858 + -115617074 -1195787391 -1096024366 -2140472445 + + -0.5037505626678467 0.3564981222152710 + + <_> + 9 + -0.4892596900463104 + + + <_> + + 0 -1 132 -67113211 2003808111 1862135111 846461923 -2752 + 2002237273 -273154752 1937223539 + + -0.2448196411132813 0.5689709186553955 + + <_> + + 0 -1 62 1179423888 -78064940 -611839555 -539167899 + -1289358360 -1650810108 -892540499 -1432827684 + + -0.4633283913135529 0.3587929606437683 + + <_> + + 0 -1 23 -285212705 -78450761 -656212031 -264050110 -27787425 + -1334349961 -547662981 -135796924 + + -0.3731099069118500 0.4290455579757690 + + <_> + + 0 -1 77 341863476 403702016 -550588417 1600194541 + -1080690735 951127993 -1388580949 -1153717473 + + -0.3658909499645233 0.4556473195552826 + + <_> + + 0 -1 22 -586880702 -204831512 -100644596 -39319550 + -1191150794 705692513 457203315 -75806957 + + -0.5214384198188782 0.3221037387847900 + + <_> + + 0 -1 72 -416546870 545911370 -673716192 -775559454 + -264113598 139424 -183369982 -204474641 + + -0.4289036989212036 0.4004956185817719 + + <_> + + 0 -1 50 -1026505020 -589692154 -1740499937 -1563770497 + 1348491006 -60710713 -1109853489 -633909413 + + -0.4621542394161224 0.3832748532295227 + + <_> + + 0 -1 108 -1448872304 -477895040 -1778390608 -772418127 + -1789923416 -1612057181 -805306693 -1415842113 + + -0.3711548447608948 0.4612701535224915 + + <_> + + 0 -1 92 407905424 -582449988 52654751 -1294472 -285103725 + -74633006 1871559083 1057955850 + + -0.5180652141571045 0.3205870389938355 + + <_> + 10 + -0.5911940932273865 + + + <_> + + 0 -1 81 4112 -1259563825 -846671428 -100902460 1838164148 + -74153752 -90653988 -1074263896 + + -0.2592592537403107 0.5873016119003296 + + <_> + + 0 -1 1 -285216785 -823206977 -1085589 -1081346 1207959293 + 1157103471 2097133565 -2097169 + + -0.3801195919513702 0.4718827307224274 + + <_> + + 0 -1 121 -12465 -536875169 2147478367 2130706303 -37765492 + -866124467 -318782328 -1392509185 + + -0.3509117066860199 0.5094807147979736 + + <_> + + 0 -1 38 2147449663 -20741 -16794757 1945873146 -16710 -1 + -8406341 -67663041 + + -0.4068757295608521 0.4130136370658875 + + <_> + + 0 -1 17 -155191713 866117231 1651407483 548272812 -479201468 + -447742449 1354229504 -261884429 + + -0.4557141065597534 0.3539792001247406 + + <_> + + 0 -1 100 -225319378 -251682065 -492783986 -792341777 + -1287261695 1393643841 -11274182 -213909521 + + -0.4117803275585175 0.4118592441082001 + + <_> + + 0 -1 63 -382220122 -2002072729 -51404800 -371201558 + -923011069 -2135301457 -2066104743 -1042557441 + + -0.4008397758007050 0.4034757018089294 + + <_> + + 0 -1 101 -627353764 -48295149 1581203952 -436258614 + -105268268 -1435893445 -638126888 -1061107126 + + -0.5694189667701721 0.2964762747287750 + + <_> + + 0 -1 118 -8399181 1058107691 -621022752 -251003468 -12582915 + -574619739 -994397789 -1648362021 + + -0.3195341229438782 0.5294018983840942 + + <_> + + 0 -1 92 -348343812 -1078389516 1717960437 364735981 + -1783841602 -4883137 -457572354 -1076950384 + + -0.3365339040756226 0.5067458748817444 + + <_> + 10 + -0.7612916231155396 + + + <_> + + 0 -1 10 -1976661318 -287957604 -1659497122 -782068 43591089 + -453637880 1435470000 -1077438561 + + -0.4204545319080353 0.5165745615959168 + + <_> + + 0 -1 131 -67110925 14874979 -142633168 -1338923040 + 2046713291 -2067933195 1473503712 -789579837 + + -0.3762553930282593 0.4075302779674530 + + <_> + + 0 -1 83 -272814301 -1577073 -1118685 -305156120 -1052289 + -1073813756 -538971154 -355523038 + + -0.4253497421741486 0.3728055357933044 + + <_> + + 0 -1 135 -2233 -214486242 -538514758 573747007 -159390971 + 1994225489 -973738098 -203424005 + + -0.3601998090744019 0.4563256204128265 + + <_> + + 0 -1 115 -261031688 -1330369299 -641860609 1029570301 + -1306461192 -1196149518 -1529767778 683139823 + + -0.4034293889999390 0.4160816967487335 + + <_> + + 0 -1 64 -572993608 -34042628 -417865 -111109 -1433365268 + -19869715 -1920939864 -1279457063 + + -0.3620899617671967 0.4594142735004425 + + <_> + + 0 -1 36 -626275097 -615256993 1651946018 805366393 + 2016559730 -430780849 -799868165 -16580645 + + -0.3903816640377045 0.4381459355354309 + + <_> + + 0 -1 93 1354797300 -1090957603 1976418270 -1342502178 + -1851873892 -1194637077 -1153521668 -1108399474 + + -0.3591445386409760 0.4624078869819641 + + <_> + + 0 -1 91 68157712 1211368313 -304759523 1063017136 798797750 + -275513546 648167355 -1145357350 + + -0.4297670423984528 0.4023293554782867 + + <_> + + 0 -1 107 -546318240 -1628569602 -163577944 -537002306 + -545456389 -1325465645 -380446736 -1058473386 + + -0.5727006793022156 0.2995934784412384 + + <_> + + 0 0 3 5 + <_> + + 0 0 4 2 + <_> + + 0 0 6 3 + <_> + + 0 1 2 3 + <_> + + 0 1 3 3 + <_> + + 0 1 3 7 + <_> + + 0 4 3 3 + <_> + + 0 11 3 4 + <_> + + 0 12 8 4 + <_> + + 0 14 4 3 + <_> + + 1 0 5 3 + <_> + + 1 1 2 2 + <_> + + 1 3 3 1 + <_> + + 1 7 4 4 + <_> + + 1 12 2 2 + <_> + + 1 13 4 1 + <_> + + 1 14 4 3 + <_> + + 1 17 3 2 + <_> + + 2 0 2 3 + <_> + + 2 1 2 2 + <_> + + 2 2 4 6 + <_> + + 2 3 4 4 + <_> + + 2 7 2 1 + <_> + + 2 11 2 3 + <_> + + 2 17 3 2 + <_> + + 3 0 2 2 + <_> + + 3 1 7 3 + <_> + + 3 7 2 1 + <_> + + 3 7 2 4 + <_> + + 3 18 2 2 + <_> + + 4 0 2 3 + <_> + + 4 3 2 1 + <_> + + 4 6 2 1 + <_> + + 4 6 2 5 + <_> + + 4 7 5 2 + <_> + + 4 8 4 3 + <_> + + 4 18 2 2 + <_> + + 5 0 2 2 + <_> + + 5 3 4 4 + <_> + + 5 6 2 5 + <_> + + 5 9 2 2 + <_> + + 5 10 2 2 + <_> + + 6 3 4 4 + <_> + + 6 4 4 3 + <_> + + 6 5 2 3 + <_> + + 6 5 2 5 + <_> + + 6 5 4 3 + <_> + + 6 6 4 2 + <_> + + 6 6 4 4 + <_> + + 6 18 1 2 + <_> + + 6 21 2 1 + <_> + + 7 0 3 7 + <_> + + 7 4 2 3 + <_> + + 7 9 5 1 + <_> + + 7 21 2 1 + <_> + + 8 0 1 4 + <_> + + 8 5 2 2 + <_> + + 8 5 3 2 + <_> + + 8 17 3 1 + <_> + + 8 18 1 2 + <_> + + 9 0 5 3 + <_> + + 9 2 2 6 + <_> + + 9 5 1 1 + <_> + + 9 11 1 1 + <_> + + 9 16 1 1 + <_> + + 9 16 2 1 + <_> + + 9 17 1 1 + <_> + + 9 18 1 1 + <_> + + 10 5 1 2 + <_> + + 10 5 3 3 + <_> + + 10 7 1 5 + <_> + + 10 8 1 1 + <_> + + 10 9 1 1 + <_> + + 10 10 1 1 + <_> + + 10 10 1 2 + <_> + + 10 14 3 3 + <_> + + 10 15 1 1 + <_> + + 10 15 2 1 + <_> + + 10 16 1 1 + <_> + + 10 16 2 1 + <_> + + 10 17 1 1 + <_> + + 10 21 1 1 + <_> + + 11 3 2 2 + <_> + + 11 5 1 2 + <_> + + 11 5 3 3 + <_> + + 11 5 4 6 + <_> + + 11 6 1 1 + <_> + + 11 7 2 2 + <_> + + 11 8 1 2 + <_> + + 11 10 1 1 + <_> + + 11 10 1 2 + <_> + + 11 15 1 1 + <_> + + 11 17 1 1 + <_> + + 11 18 1 1 + <_> + + 12 0 2 2 + <_> + + 12 1 2 5 + <_> + + 12 2 4 1 + <_> + + 12 3 1 3 + <_> + + 12 7 3 4 + <_> + + 12 10 3 2 + <_> + + 12 11 1 1 + <_> + + 12 12 3 2 + <_> + + 12 14 4 3 + <_> + + 12 17 1 1 + <_> + + 12 21 2 1 + <_> + + 13 6 2 5 + <_> + + 13 7 3 5 + <_> + + 13 11 3 2 + <_> + + 13 17 2 2 + <_> + + 13 17 3 2 + <_> + + 13 18 1 2 + <_> + + 13 18 2 2 + <_> + + 14 0 2 2 + <_> + + 14 1 1 3 + <_> + + 14 2 3 2 + <_> + + 14 7 2 1 + <_> + + 14 13 2 1 + <_> + + 14 13 3 3 + <_> + + 14 17 2 2 + <_> + + 15 0 2 2 + <_> + + 15 0 2 3 + <_> + + 15 4 3 2 + <_> + + 15 4 3 6 + <_> + + 15 6 3 2 + <_> + + 15 11 3 4 + <_> + + 15 13 3 2 + <_> + + 15 17 2 2 + <_> + + 15 17 3 2 + <_> + + 16 1 2 3 + <_> + + 16 3 2 4 + <_> + + 16 6 1 1 + <_> + + 16 16 2 2 + <_> + + 17 1 2 2 + <_> + + 17 1 2 5 + <_> + + 17 12 2 2 + <_> + + 18 0 2 2 +