├── .gitignore ├── libs └── .gitignore ├── res ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-ldpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png ├── drawable-xxhdpi │ └── ic_launcher.png ├── drawable-xxxhdpi │ └── ic_launcher.png ├── layout │ ├── bpm.xml │ ├── app.xml │ └── config.xml ├── values │ └── strings.xml ├── menu │ └── app.xml └── raw │ └── lbpcascade_frontalface.xml ├── assets └── fonts │ └── ds_digital │ ├── DS-DIGI.TTF │ ├── DS-DIGIB.TTF │ ├── DS-DIGII.TTF │ ├── DS-DIGIT.TTF │ └── DIGITAL.TXT ├── jni ├── Application.mk ├── Android.mk ├── pt_chambino_p_pulse_Pulse_Face.h ├── pt_chambino_p_pulse_Pulse.h ├── pt_chambino_p_pulse_Pulse_Face.cpp └── pt_chambino_p_pulse_Pulse.cpp ├── project.properties ├── ant.properties ├── LICENSE ├── proguard-project.txt ├── src ├── pt │ └── chambino │ │ └── p │ │ └── pulse │ │ ├── dialog │ │ ├── BpmDialog.java │ │ └── ConfigDialog.java │ │ ├── view │ │ ├── PulseView.java │ │ └── BpmView.java │ │ ├── Pulse.java │ │ └── App.java └── org │ └── opencv │ └── android │ ├── MyJavaCameraView.java │ └── MyCameraBridgeViewBase.java ├── custom_rules.xml ├── AndroidManifest.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | gen 4 | local.properties 5 | -------------------------------------------------------------------------------- /libs/.gitignore: -------------------------------------------------------------------------------- 1 | OpenCV-android-sdk 2 | armeabi-v7a 3 | -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PChambino/pulse/HEAD/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PChambino/pulse/HEAD/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PChambino/pulse/HEAD/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /assets/fonts/ds_digital/DS-DIGI.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PChambino/pulse/HEAD/assets/fonts/ds_digital/DS-DIGI.TTF -------------------------------------------------------------------------------- /assets/fonts/ds_digital/DS-DIGIB.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PChambino/pulse/HEAD/assets/fonts/ds_digital/DS-DIGIB.TTF -------------------------------------------------------------------------------- /assets/fonts/ds_digital/DS-DIGII.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PChambino/pulse/HEAD/assets/fonts/ds_digital/DS-DIGII.TTF -------------------------------------------------------------------------------- /assets/fonts/ds_digital/DS-DIGIT.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PChambino/pulse/HEAD/assets/fonts/ds_digital/DS-DIGIT.TTF -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PChambino/pulse/HEAD/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PChambino/pulse/HEAD/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PChambino/pulse/HEAD/res/drawable-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /jni/Application.mk: -------------------------------------------------------------------------------- 1 | APP_STL := gnustl_static 2 | APP_CPPFLAGS := -frtti -fexceptions -O3 3 | APP_ABI := armeabi-v7a 4 | APP_PLATFORM := android-8 5 | -------------------------------------------------------------------------------- /res/layout/bpm.xml: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /jni/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | include $(CLEAR_VARS) 4 | 5 | include libs/OpenCV-android-sdk/sdk/native/jni/OpenCV.mk 6 | 7 | LOCAL_SRC_FILES := pt_chambino_p_pulse_Pulse_Face.cpp 8 | LOCAL_SRC_FILES += pt_chambino_p_pulse_Pulse.cpp 9 | LOCAL_SRC_FILES += Pulse.cpp 10 | LOCAL_SRC_FILES += EvmGdownIIR.cpp 11 | LOCAL_SRC_FILES += ext/opencv.cpp 12 | LOCAL_SRC_FILES += profiler/Profiler.cpp 13 | LOCAL_C_INCLUDES += $(LOCAL_PATH) 14 | LOCAL_LDLIBS += -llog -ldl 15 | 16 | LOCAL_MODULE := pulse 17 | 18 | include $(BUILD_SHARED_LIBRARY) 19 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Pulse 4 | 5 | 6 | Record 7 | Switch Camera 8 | Config 9 | 10 | 11 | Face Detection 12 | Magnification 13 | FPS 14 | Done 15 | 16 | 17 | Average BPM 18 | 19 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | android.library.reference.1=libs/OpenCV-android-sdk/sdk/java 14 | # Project target. 15 | target=android-17 16 | -------------------------------------------------------------------------------- /res/menu/app.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 13 | 18 | -------------------------------------------------------------------------------- /ant.properties: -------------------------------------------------------------------------------- 1 | # This file is used to override default values used by the Ant build system. 2 | # 3 | # This file must be checked into Version Control Systems, as it is 4 | # integral to the build system of your project. 5 | 6 | # This file is only used by the Ant script. 7 | 8 | # You can use this to override default values such as 9 | # 'source.dir' for the location of your java source folder and 10 | # 'out.dir' for the location of your output folder. 11 | 12 | # You can also use it define how the release builds are signed by declaring 13 | # the following properties: 14 | # 'key.store' for the location of your keystore and 15 | # 'key.alias' for the name of the key to use. 16 | # The password will be asked during the build when you use the 'release' target. 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Pedro Boloto Chambino 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /src/pt/chambino/p/pulse/dialog/BpmDialog.java: -------------------------------------------------------------------------------- 1 | package pt.chambino.p.pulse.dialog; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.app.Dialog; 6 | import android.app.DialogFragment; 7 | import android.os.Bundle; 8 | import android.view.View; 9 | import pt.chambino.p.pulse.App; 10 | import pt.chambino.p.pulse.R; 11 | import pt.chambino.p.pulse.view.BpmView; 12 | 13 | public class BpmDialog extends DialogFragment { 14 | 15 | private BpmView bpmView; 16 | private double bpm; 17 | 18 | @Override 19 | public Dialog onCreateDialog(Bundle savedInstanceState) { 20 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 21 | builder.setTitle(R.string.average_bpm); 22 | builder.setNeutralButton(android.R.string.ok, null); 23 | 24 | View dialogView = getActivity().getLayoutInflater().inflate(R.layout.bpm, null); 25 | builder.setView(dialogView); 26 | 27 | bpmView = ((BpmView)dialogView.findViewById(R.id.bpm)); 28 | bpmView.setBpm(bpm); 29 | 30 | return builder.create(); 31 | } 32 | 33 | @Override 34 | public void onAttach(Activity activity) { 35 | super.onAttach(activity); 36 | App app = (App)activity; 37 | bpm = app.getRecordedBpmAverage(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /res/layout/app.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 17 | 18 | 23 | 24 | 25 | 26 | 31 | 32 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /custom_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /jni/pt_chambino_p_pulse_Pulse_Face.h: -------------------------------------------------------------------------------- 1 | /* DO NOT EDIT THIS FILE - it is machine generated */ 2 | #include 3 | /* Header for class pt_chambino_p_pulse_Pulse_Face */ 4 | 5 | #ifndef _Included_pt_chambino_p_pulse_Pulse_Face 6 | #define _Included_pt_chambino_p_pulse_Pulse_Face 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | /* 11 | * Class: pt_chambino_p_pulse_Pulse_Face 12 | * Method: _id 13 | * Signature: (J)I 14 | */ 15 | JNIEXPORT jint JNICALL Java_pt_chambino_p_pulse_Pulse_00024Face__1id 16 | (JNIEnv *, jclass, jlong); 17 | 18 | /* 19 | * Class: pt_chambino_p_pulse_Pulse_Face 20 | * Method: _box 21 | * Signature: (JJ)V 22 | */ 23 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse_00024Face__1box 24 | (JNIEnv *, jclass, jlong, jlong); 25 | 26 | /* 27 | * Class: pt_chambino_p_pulse_Pulse_Face 28 | * Method: _bpm 29 | * Signature: (J)D 30 | */ 31 | JNIEXPORT jdouble JNICALL Java_pt_chambino_p_pulse_Pulse_00024Face__1bpm 32 | (JNIEnv *, jclass, jlong); 33 | 34 | /* 35 | * Class: pt_chambino_p_pulse_Pulse_Face 36 | * Method: _pulse 37 | * Signature: (JJ)V 38 | */ 39 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse_00024Face__1pulse 40 | (JNIEnv *, jclass, jlong, jlong); 41 | 42 | /* 43 | * Class: pt_chambino_p_pulse_Pulse_Face 44 | * Method: _existsPulse 45 | * Signature: (J)Z 46 | */ 47 | JNIEXPORT jboolean JNICALL Java_pt_chambino_p_pulse_Pulse_00024Face__1existsPulse 48 | (JNIEnv *, jclass, jlong); 49 | 50 | #ifdef __cplusplus 51 | } 52 | #endif 53 | #endif 54 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /assets/fonts/ds_digital/DIGITAL.TXT: -------------------------------------------------------------------------------- 1 | DS-Font's TrueType Fonts 2 | Font name: DS-Digital (Normal, Bold, Italic, Bold Italic), Version 1.0 3 | Author: Dusit Supasawat 4 | Web Site: http://ds-font.hypermart.net 5 | Contact me: Dusit Supasawat, 325/38 Suksawat32 Ratburana Bangkok Thailand 10140 6 | Email address: dusit@mailcity.com 7 | 8 | Thanks for trying! We hope you really enjoy this my typeface. This font is 9 | distributed as shareware. You can use this font for a long time as you want. 10 | After all, when you think this font can be usefulness for you. You can send 11 | me some money, that would be way cool. 12 | 13 | I'm only asking $20 US shareware fee per this typeface for personal use. 14 | And $45 US is the usual amount per this typeface for commercial use. 15 | 16 | Distribution: You are free to distribute this archive so long as this text 17 | file is distributed with the archive, the font file have not been modified, 18 | and it is understood that the font's copyright remains with the original 19 | author (Dusit Supasawat). 20 | 21 | To register send your payment to: 22 | 23 | Dusit Supasawat 24 | 325/38 Suksawat32 Ratburana 25 | Bangkok Thailand 10140 26 | 27 | And fill out something as this order form, and send it in with your payment. 28 | 29 | Font name:_________________________________________ 30 | Your information 31 | Name:______________________________________________ 32 | Address:___________________________________________ 33 | City, State : _____________________________________ 34 | Zip Code:__________________________________________ 35 | Country:___________________________________________ 36 | E-MAIL address:____________________________________ 37 | 38 | 39 | You will receive fonts which you order by Email after registration. These fonts 40 | will be generated for you by specify your name in font information. -------------------------------------------------------------------------------- /res/layout/config.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 11 | 12 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 36 | 37 | 42 | 43 | 44 | 45 | 50 | 51 | 52 | 55 | 56 | 61 | 62 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pulse 2 | ===== 3 | 4 | ### Android-based implementation of Eulerian Video Magnification for vital signs monitoring 5 | 6 | http://p.chambino.com/dissertation 7 | 8 | Eulerian Video Magnification is a recently presented method capable of 9 | revealing temporal variations in videos that are impossible to see with 10 | the naked eye. Using this method, it is possible to visualize the flow of 11 | blood as it fills the face. From its result, a person’s heart rate is possible 12 | to be extracted. 13 | 14 | This research work was developed at Fraunhofer Portugal and its goal is to 15 | test the feasibility of the implementation of the Eulerian Video Magnification 16 | method on smartphones by developing an Android application for monitoring 17 | vital signs based on the Eulerian Video Magnification method. 18 | 19 | There has been some successful effort on the assessment of vital signs, 20 | such as, heart rate, and breathing rate, in a contact-free way using a 21 | webcamera and even a smartphone. However, since the Eulerian Video 22 | Magnification method was recently proposed, its implementation has not been 23 | tested in smartphones yet.Thus, the Eulerian Video Magnification method 24 | performance for color amplification was optimized in order to execute on an 25 | Android device at a reasonable speed. 26 | 27 | The Android application implemented includes features, such as, detection 28 | of a person’s cardiac pulse, dealing with artifacts’ motion, and real-time 29 | display of the magnified blood flow. Then, the application measurements were 30 | evaluated through tests with several individuals and compared to the ones 31 | detected by the ViTrox application and to the readings of a sphygmomanometer. 32 | 33 | 34 | Dependencies 35 | ============ 36 | 37 | * Android SDK (v17) 38 | * Android NDK (r10e) 39 | * OpenCV Android SDK (2.4.11) 40 | * [pulse-cpp] 41 | 42 | [pulse-cpp]: https://github.com/pchambino/pulse-cpp 43 | 44 | 45 | Setup 46 | ===== 47 | 48 | cd pulse 49 | android update project -p . -t android-17 50 | # a local.properties file with sdk.dir should have been created 51 | echo ndk.dir=PATH_TO_ANDROID_NDK >> local.properties 52 | echo pulse-cpp.dir=PATH_TO_PULSE_CPP >> local.properties 53 | # copy OpenCV Android SDK to libs directory 54 | android update lib-project -p libs/OpenCV-android-sdk/sdk/java -t android-17 55 | echo android.library=true >> project.properties 56 | ant debug install 57 | 58 | 59 | Attribution 60 | =========== 61 | 62 | [Heart] designed by [Diego Naive] from The Noun Project 63 | 64 | [Heart]: http://thenounproject.com/noun/heart/#icon-No15259 65 | [Diego Naive]: http://thenounproject.com/diegonaive 66 | -------------------------------------------------------------------------------- /src/pt/chambino/p/pulse/view/PulseView.java: -------------------------------------------------------------------------------- 1 | package pt.chambino.p.pulse.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | 10 | public class PulseView extends View { 11 | 12 | public PulseView(Context context) { 13 | super(context); 14 | init(); 15 | } 16 | 17 | public PulseView(Context context, AttributeSet attrs) { 18 | super(context, attrs); 19 | init(); 20 | } 21 | 22 | public PulseView(Context context, AttributeSet attrs, int defStyle) { 23 | super(context, attrs, defStyle); 24 | init(); 25 | } 26 | 27 | private double[] pulse; 28 | private int gridSize = 100; 29 | private int gridStep = 5; 30 | 31 | private Paint pulsePaint; 32 | private Paint zeroPaint; 33 | private Paint gridPaint; 34 | 35 | private void init() { 36 | setNoPulse(); 37 | 38 | setBackgroundColor(Color.DKGRAY); 39 | pulsePaint = initPulsePaint(); 40 | zeroPaint = initZeroPaint(); 41 | gridPaint = initGridPaint(); 42 | } 43 | 44 | private Paint initPulsePaint() { 45 | Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); 46 | p.setColor(Color.RED); 47 | p.setStrokeWidth(4); 48 | return p; 49 | } 50 | 51 | private Paint initZeroPaint() { 52 | Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); 53 | p.setColor(Color.LTGRAY); 54 | p.setStrokeWidth(4); 55 | return p; 56 | } 57 | 58 | private Paint initGridPaint() { 59 | Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); 60 | p.setColor(Color.LTGRAY); 61 | p.setStrokeWidth(1); 62 | return p; 63 | } 64 | 65 | public double[] getPulse() { 66 | return pulse; 67 | } 68 | 69 | public void setPulse(final double[] pulse) { 70 | this.pulse = pulse; 71 | invalidate(); 72 | } 73 | 74 | public void setNoPulse() { 75 | setPulse(new double[0]); 76 | } 77 | 78 | public int getGridSize() { 79 | return gridSize; 80 | } 81 | 82 | public void setGridSize(int gridSize) { 83 | this.gridSize = gridSize; 84 | invalidate(); 85 | } 86 | 87 | public int getGridStep() { 88 | return gridStep; 89 | } 90 | 91 | public void setGridStep(int gridStep) { 92 | this.gridStep = gridStep; 93 | invalidate(); 94 | } 95 | 96 | @Override 97 | protected void onDraw(Canvas canvas) { 98 | // vertical grid lines 99 | for (int i = 0; i < gridSize; i += gridStep) { 100 | canvas.drawLine(x(i), 0, x(i), getHeight(), gridPaint); 101 | } 102 | 103 | // vertical and horizontal zero lines 104 | canvas.drawLine(2, 0, 2, getHeight(), zeroPaint); 105 | canvas.drawLine(0, y(0), getWidth(), y(0), zeroPaint); 106 | 107 | // pulse signal 108 | for (int i = 1; i < pulse.length; i++) { 109 | canvas.drawLine(x(i-1), y(pulse[i-1]), x(i), y(pulse[i]), pulsePaint); 110 | } 111 | } 112 | 113 | private float x(int x) { 114 | return x * getWidth() / (gridSize - 1); 115 | } 116 | 117 | private float y(double y) { 118 | return (float)(getHeight() / 2. - y * getHeight() / 3.); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/pt/chambino/p/pulse/view/BpmView.java: -------------------------------------------------------------------------------- 1 | package pt.chambino.p.pulse.view; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ObjectAnimator; 6 | import android.animation.ValueAnimator; 7 | import android.content.Context; 8 | import android.graphics.Canvas; 9 | import android.graphics.Color; 10 | import android.graphics.Paint; 11 | import android.graphics.Typeface; 12 | import android.util.AttributeSet; 13 | import android.util.TypedValue; 14 | import android.view.Gravity; 15 | import android.view.animation.AccelerateInterpolator; 16 | import android.widget.TextView; 17 | 18 | public class BpmView extends TextView { 19 | 20 | public BpmView(Context context) { 21 | super(context); 22 | init(); 23 | } 24 | 25 | public BpmView(Context context, AttributeSet attrs) { 26 | super(context, attrs); 27 | init(); 28 | } 29 | 30 | public BpmView(Context context, AttributeSet attrs, int defStyle) { 31 | super(context, attrs, defStyle); 32 | init(); 33 | } 34 | 35 | private double bpm; 36 | 37 | private Paint circlePaint; 38 | private ValueAnimator circlePaintAnimator; 39 | 40 | private void init() { 41 | setNoBpm(); 42 | 43 | setTextSize(TypedValue.COMPLEX_UNIT_FRACTION_PARENT, 60f); 44 | setTypeface(Typeface.createFromAsset(getContext().getAssets(), "fonts/ds_digital/DS-DIGIB.TTF")); 45 | setGravity(Gravity.CENTER); 46 | 47 | circlePaint = initCirclePaint(); 48 | 49 | circlePaintAnimator = ObjectAnimator.ofInt(circlePaint, "Alpha", 0, 256); 50 | circlePaintAnimator.setInterpolator(new AccelerateInterpolator()); 51 | circlePaintAnimator.setDuration(1000); 52 | circlePaintAnimator.setRepeatCount(ValueAnimator.INFINITE); 53 | circlePaintAnimator.setRepeatMode(ValueAnimator.REVERSE); 54 | circlePaintAnimator.addListener(new AnimatorListenerAdapter() { 55 | @Override 56 | public void onAnimationRepeat(Animator animation) { 57 | if (getText() == "-" && circlePaint.getAlpha() == 0) { 58 | animation.cancel(); 59 | } 60 | } 61 | }); 62 | circlePaintAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 63 | @Override 64 | public void onAnimationUpdate(ValueAnimator animation) { 65 | invalidate(); 66 | } 67 | }); 68 | } 69 | 70 | private Paint initCirclePaint() { 71 | Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); 72 | p.setColor(Color.RED); 73 | p.setAlpha(0); 74 | p.setStyle(Paint.Style.FILL); 75 | return p; 76 | } 77 | 78 | public double getBpm() { 79 | return bpm; 80 | } 81 | 82 | public void setBpm(double bpm) { 83 | this.bpm = bpm; 84 | long rounded = Math.round(bpm); 85 | if (rounded == 0) { 86 | setText("-"); 87 | } else { 88 | setText(String.valueOf(rounded)); 89 | } 90 | } 91 | 92 | public void setNoBpm() { 93 | setBpm(0); 94 | } 95 | 96 | @Override 97 | protected void onDraw(Canvas canvas) { 98 | super.onDraw(canvas); 99 | canvas.drawCircle(20, getHeight() / 2f, 10, circlePaint); 100 | if (getText() != "-" && !circlePaintAnimator.isStarted()) { 101 | circlePaintAnimator.start(); 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /jni/pt_chambino_p_pulse_Pulse.h: -------------------------------------------------------------------------------- 1 | /* DO NOT EDIT THIS FILE - it is machine generated */ 2 | #include 3 | /* Header for class pt_chambino_p_pulse_Pulse */ 4 | 5 | #ifndef _Included_pt_chambino_p_pulse_Pulse 6 | #define _Included_pt_chambino_p_pulse_Pulse 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | /* 11 | * Class: pt_chambino_p_pulse_Pulse 12 | * Method: _initialize 13 | * Signature: ()J 14 | */ 15 | JNIEXPORT jlong JNICALL Java_pt_chambino_p_pulse_Pulse__1initialize 16 | (JNIEnv *, jclass); 17 | 18 | /* 19 | * Class: pt_chambino_p_pulse_Pulse 20 | * Method: _load 21 | * Signature: (JLjava/lang/String;)V 22 | */ 23 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse__1load 24 | (JNIEnv *, jclass, jlong, jstring); 25 | 26 | /* 27 | * Class: pt_chambino_p_pulse_Pulse 28 | * Method: _start 29 | * Signature: (JII)V 30 | */ 31 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse__1start 32 | (JNIEnv *, jclass, jlong, jint, jint); 33 | 34 | /* 35 | * Class: pt_chambino_p_pulse_Pulse 36 | * Method: _onFrame 37 | * Signature: (JJ)V 38 | */ 39 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse__1onFrame 40 | (JNIEnv *, jclass, jlong, jlong); 41 | 42 | /* 43 | * Class: pt_chambino_p_pulse_Pulse 44 | * Method: _facesCount 45 | * Signature: (J)I 46 | */ 47 | JNIEXPORT jint JNICALL Java_pt_chambino_p_pulse_Pulse__1facesCount 48 | (JNIEnv *, jclass, jlong); 49 | 50 | /* 51 | * Class: pt_chambino_p_pulse_Pulse 52 | * Method: _face 53 | * Signature: (JI)J 54 | */ 55 | JNIEXPORT jlong JNICALL Java_pt_chambino_p_pulse_Pulse__1face 56 | (JNIEnv *, jclass, jlong, jint); 57 | 58 | /* 59 | * Class: pt_chambino_p_pulse_Pulse 60 | * Method: _relativeMinFaceSize 61 | * Signature: (J)D 62 | */ 63 | JNIEXPORT jdouble JNICALL Java_pt_chambino_p_pulse_Pulse__1relativeMinFaceSize 64 | (JNIEnv *, jclass, jlong); 65 | 66 | /* 67 | * Class: pt_chambino_p_pulse_Pulse 68 | * Method: _maxSignalSize 69 | * Signature: (J)I 70 | */ 71 | JNIEXPORT jint JNICALL Java_pt_chambino_p_pulse_Pulse__1maxSignalSize 72 | (JNIEnv *, jclass, jlong); 73 | 74 | /* 75 | * Class: pt_chambino_p_pulse_Pulse 76 | * Method: _faceDetection 77 | * Signature: (J)Z 78 | */ 79 | JNIEXPORT jboolean JNICALL Java_pt_chambino_p_pulse_Pulse__1faceDetection__J 80 | (JNIEnv *, jclass, jlong); 81 | 82 | /* 83 | * Class: pt_chambino_p_pulse_Pulse 84 | * Method: _faceDetection 85 | * Signature: (JZ)V 86 | */ 87 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse__1faceDetection__JZ 88 | (JNIEnv *, jclass, jlong, jboolean); 89 | 90 | /* 91 | * Class: pt_chambino_p_pulse_Pulse 92 | * Method: _magnification 93 | * Signature: (J)Z 94 | */ 95 | JNIEXPORT jboolean JNICALL Java_pt_chambino_p_pulse_Pulse__1magnification__J 96 | (JNIEnv *, jclass, jlong); 97 | 98 | /* 99 | * Class: pt_chambino_p_pulse_Pulse 100 | * Method: _magnification 101 | * Signature: (JZ)V 102 | */ 103 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse__1magnification__JZ 104 | (JNIEnv *, jclass, jlong, jboolean); 105 | 106 | /* 107 | * Class: pt_chambino_p_pulse_Pulse 108 | * Method: _magnificationFactor 109 | * Signature: (J)I 110 | */ 111 | JNIEXPORT jint JNICALL Java_pt_chambino_p_pulse_Pulse__1magnificationFactor__J 112 | (JNIEnv *, jclass, jlong); 113 | 114 | /* 115 | * Class: pt_chambino_p_pulse_Pulse 116 | * Method: _magnificationFactor 117 | * Signature: (JI)V 118 | */ 119 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse__1magnificationFactor__JI 120 | (JNIEnv *, jclass, jlong, jint); 121 | 122 | /* 123 | * Class: pt_chambino_p_pulse_Pulse 124 | * Method: _destroy 125 | * Signature: (J)V 126 | */ 127 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse__1destroy 128 | (JNIEnv *, jclass, jlong); 129 | 130 | #ifdef __cplusplus 131 | } 132 | #endif 133 | #endif 134 | -------------------------------------------------------------------------------- /src/pt/chambino/p/pulse/dialog/ConfigDialog.java: -------------------------------------------------------------------------------- 1 | package pt.chambino.p.pulse.dialog; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.app.Dialog; 6 | import android.app.DialogFragment; 7 | import android.os.Bundle; 8 | import android.view.View; 9 | import android.widget.CompoundButton; 10 | import android.widget.SeekBar; 11 | import android.widget.Switch; 12 | import pt.chambino.p.pulse.App; 13 | import pt.chambino.p.pulse.Pulse; 14 | import pt.chambino.p.pulse.R; 15 | 16 | public class ConfigDialog extends DialogFragment { 17 | 18 | private Switch faceDetectionSwitch; 19 | private Switch magnificationSwitch; 20 | private SeekBar magnificationSeekBar; 21 | private Switch fpsSwitch; 22 | 23 | @Override 24 | public Dialog onCreateDialog(Bundle savedInstanceState) { 25 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 26 | builder.setTitle(R.string.config); 27 | builder.setNeutralButton(R.string.done, null); 28 | 29 | View dialogView = getActivity().getLayoutInflater().inflate(R.layout.config, null); 30 | builder.setView(dialogView); 31 | 32 | faceDetectionSwitch = ((Switch)dialogView.findViewById(R.id.face_detection)); 33 | faceDetectionSwitch.setOnCheckedChangeListener(new FaceDetectionSwitchConfig()); 34 | 35 | magnificationSwitch = ((Switch)dialogView.findViewById(R.id.magnification)); 36 | magnificationSwitch.setOnCheckedChangeListener(new MagnificationSwitchConfig()); 37 | 38 | magnificationSeekBar = ((SeekBar)dialogView.findViewById(R.id.magnificationFactor)); 39 | magnificationSeekBar.setOnSeekBarChangeListener(new MagnificationSeekBarConfig()); 40 | 41 | fpsSwitch = ((Switch)dialogView.findViewById(R.id.fps)); 42 | fpsSwitch.setOnCheckedChangeListener(new FpsSwitchConfig()); 43 | 44 | Pulse pulse = getApp().getPulse(); 45 | // on configuration change Pulse may not have loaded yet 46 | if (pulse != null) { 47 | faceDetectionSwitch.setChecked(pulse.hasFaceDetection()); 48 | magnificationSwitch.setChecked(pulse.hasMagnification()); 49 | magnificationSeekBar.setEnabled(pulse.hasMagnification()); 50 | magnificationSeekBar.setProgress(pulse.getMagnificationFactor()); 51 | } 52 | fpsSwitch.setChecked(getApp().getCamera().isFpsMeterEnabled()); 53 | 54 | return builder.create(); 55 | } 56 | 57 | private App getApp() { 58 | return (App)getActivity(); 59 | } 60 | 61 | private class FaceDetectionSwitchConfig implements CompoundButton.OnCheckedChangeListener { 62 | @Override 63 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 64 | if (getApp().getPulse() != null) getApp().getPulse().setFaceDetection(isChecked); 65 | } 66 | } 67 | 68 | private class MagnificationSwitchConfig implements CompoundButton.OnCheckedChangeListener { 69 | @Override 70 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 71 | magnificationSeekBar.setEnabled(isChecked); 72 | if (getApp().getPulse() != null) getApp().getPulse().setMagnification(isChecked); 73 | } 74 | } 75 | 76 | private class MagnificationSeekBarConfig implements SeekBar.OnSeekBarChangeListener { 77 | @Override 78 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 79 | if (getApp().getPulse() != null) getApp().getPulse().setMagnificationFactor(progress); 80 | } 81 | 82 | @Override 83 | public void onStartTrackingTouch(SeekBar seekBar) { 84 | } 85 | 86 | @Override 87 | public void onStopTrackingTouch(SeekBar seekBar) { 88 | } 89 | } 90 | 91 | private class FpsSwitchConfig implements CompoundButton.OnCheckedChangeListener { 92 | @Override 93 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 94 | getApp().getCamera().setFpsMeter(isChecked); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/pt/chambino/p/pulse/Pulse.java: -------------------------------------------------------------------------------- 1 | package pt.chambino.p.pulse; 2 | 3 | import org.opencv.core.Mat; 4 | import org.opencv.core.MatOfDouble; 5 | import org.opencv.core.MatOfRect; 6 | import org.opencv.core.Rect; 7 | 8 | public class Pulse { 9 | 10 | public Pulse() { 11 | self = _initialize(); 12 | } 13 | 14 | public void load(String filename) { 15 | _load(self, filename); 16 | } 17 | 18 | public void start(int width, int height) { 19 | _start(self, width, height); 20 | } 21 | 22 | public void onFrame(Mat frame) { 23 | _onFrame(self, frame.getNativeObjAddr()); 24 | } 25 | 26 | public Face[] getFaces() { 27 | int count = _facesCount(self); 28 | Face[] faces = new Face[count]; 29 | for (int i = 0; i < count; i++) { 30 | faces[i] = new Face(_face(self, i)); 31 | } 32 | return faces; 33 | } 34 | 35 | public double getRelativeMinFaceSize() { 36 | return _relativeMinFaceSize(self); 37 | } 38 | 39 | public int getMaxSignalSize() { 40 | return _maxSignalSize(self); 41 | } 42 | 43 | public boolean hasFaceDetection() { 44 | return _faceDetection(self); 45 | } 46 | 47 | public void setFaceDetection(boolean m) { 48 | _faceDetection(self, m); 49 | } 50 | 51 | public boolean hasMagnification() { 52 | return _magnification(self); 53 | } 54 | 55 | public void setMagnification(boolean m) { 56 | _magnification(self, m); 57 | } 58 | 59 | public int getMagnificationFactor() { 60 | return _magnificationFactor(self); 61 | } 62 | 63 | public void setMagnificationFactor(int m) { 64 | _magnificationFactor(self, m); 65 | } 66 | 67 | public void release() { 68 | _destroy(self); 69 | self = 0; 70 | } 71 | 72 | @Override 73 | protected void finalize() throws Throwable { 74 | super.finalize(); 75 | release(); 76 | } 77 | 78 | private long self = 0; 79 | private static native long _initialize(); 80 | private static native void _load(long self, String filename); 81 | private static native void _start(long self, int width, int height); 82 | private static native void _onFrame(long self, long frame); 83 | private static native int _facesCount(long self); 84 | private static native long _face(long self, int i); 85 | private static native double _relativeMinFaceSize(long self); 86 | private static native int _maxSignalSize(long self); 87 | private static native boolean _faceDetection(long self); 88 | private static native void _faceDetection(long self, boolean m); 89 | private static native boolean _magnification(long self); 90 | private static native void _magnification(long self, boolean m); 91 | private static native int _magnificationFactor(long self); 92 | private static native void _magnificationFactor(long self, int m); 93 | private static native void _destroy(long self); 94 | 95 | public static class Face { 96 | 97 | private MatOfRect box; 98 | private MatOfDouble pulse; 99 | 100 | private Face(long addr) { 101 | this.self = addr; 102 | } 103 | 104 | public int getId() { 105 | return _id(self); 106 | } 107 | 108 | public Rect getBox() { 109 | if (box == null) box = new MatOfRect(); 110 | _box(self, box.getNativeObjAddr()); 111 | return box.toArray()[0]; 112 | } 113 | 114 | public double getBpm() { 115 | return _bpm(self); 116 | } 117 | 118 | public double[] getPulse() { 119 | if (pulse == null) pulse = new MatOfDouble(); 120 | _pulse(self, pulse.getNativeObjAddr()); 121 | return pulse.toArray(); 122 | } 123 | 124 | public boolean existsPulse() { 125 | return _existsPulse(self); 126 | } 127 | 128 | private long self = 0; 129 | private static native int _id(long self); 130 | private static native void _box(long self, long mat); 131 | private static native double _bpm(long self); 132 | private static native void _pulse(long self, long mat); 133 | private static native boolean _existsPulse(long self); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /jni/pt_chambino_p_pulse_Pulse_Face.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "Pulse.hpp" 6 | 7 | #include 8 | #define LOG_TAG "Pulse::Pulse::Face" 9 | #define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) 10 | 11 | using std::vector; 12 | using cv::Mat; 13 | using cv::Mat1d; 14 | using cv::Rect; 15 | 16 | /* 17 | * Class: pt_chambino_p_pulse_Pulse_Face 18 | * Method: _id 19 | * Signature: (J)I 20 | */ 21 | JNIEXPORT jint JNICALL Java_pt_chambino_p_pulse_Pulse_00024Face__1id 22 | (JNIEnv *jenv, jclass, jlong self) 23 | { 24 | LOGD("Java_pt_chambino_p_pulse_Pulse_00024Face__1id enter"); 25 | jint id = 0; 26 | try 27 | { 28 | if (self) 29 | id = ((Pulse::Face*)self)->id; 30 | } 31 | catch(cv::Exception& e) 32 | { 33 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 34 | if(!je) je = jenv->FindClass("java/lang/Exception"); 35 | jenv->ThrowNew(je, e.what()); 36 | } 37 | catch (...) 38 | { 39 | jclass je = jenv->FindClass("java/lang/Exception"); 40 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 41 | } 42 | LOGD("Java_pt_chambino_p_pulse_Pulse_00024Face__1id exit"); 43 | return id; 44 | } 45 | 46 | /* 47 | * Class: pt_chambino_p_pulse_Pulse_Face 48 | * Method: _box 49 | * Signature: (JJ)V 50 | */ 51 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse_00024Face__1box 52 | (JNIEnv *jenv, jclass, jlong self, jlong mat) 53 | { 54 | LOGD("Java_pt_chambino_p_pulse_Pulse_00024Face__1box enter"); 55 | try 56 | { 57 | if (self) { 58 | vector v; 59 | v.push_back(((Pulse::Face*)self)->evm.box); 60 | *((Mat*)mat) = Mat(v, true); 61 | } 62 | } 63 | catch(cv::Exception& e) 64 | { 65 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 66 | if(!je) je = jenv->FindClass("java/lang/Exception"); 67 | jenv->ThrowNew(je, e.what()); 68 | } 69 | catch (...) 70 | { 71 | jclass je = jenv->FindClass("java/lang/Exception"); 72 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 73 | } 74 | LOGD("Java_pt_chambino_p_pulse_Pulse_00024Face__1box exit"); 75 | } 76 | 77 | /* 78 | * Class: pt_chambino_p_pulse_Pulse_Face 79 | * Method: _bpm 80 | * Signature: (J)D 81 | */ 82 | JNIEXPORT jdouble JNICALL Java_pt_chambino_p_pulse_Pulse_00024Face__1bpm 83 | (JNIEnv *jenv, jclass, jlong self) 84 | { 85 | LOGD("Java_pt_chambino_p_pulse_Pulse_00024Face__1bpm enter"); 86 | jdouble bpm = 0; 87 | try 88 | { 89 | if (self) 90 | bpm = ((Pulse::Face*)self)->bpm; 91 | } 92 | catch(cv::Exception& e) 93 | { 94 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 95 | if(!je) je = jenv->FindClass("java/lang/Exception"); 96 | jenv->ThrowNew(je, e.what()); 97 | } 98 | catch (...) 99 | { 100 | jclass je = jenv->FindClass("java/lang/Exception"); 101 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 102 | } 103 | LOGD("Java_pt_chambino_p_pulse_Pulse_00024Face__1bpm exit"); 104 | return bpm; 105 | } 106 | 107 | /* 108 | * Class: pt_chambino_p_pulse_Pulse_Face 109 | * Method: _pulse 110 | * Signature: (JJ)V 111 | */ 112 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse_00024Face__1pulse 113 | (JNIEnv *jenv, jclass, jlong self, jlong mat) 114 | { 115 | LOGD("Java_pt_chambino_p_pulse_Pulse_00024Face__1pulse enter"); 116 | try 117 | { 118 | if (self) 119 | *((Mat*)mat) = ((Pulse::Face*)self)->pulse; 120 | } 121 | catch(cv::Exception& e) 122 | { 123 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 124 | if(!je) je = jenv->FindClass("java/lang/Exception"); 125 | jenv->ThrowNew(je, e.what()); 126 | } 127 | catch (...) 128 | { 129 | jclass je = jenv->FindClass("java/lang/Exception"); 130 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 131 | } 132 | LOGD("Java_pt_chambino_p_pulse_Pulse_00024Face__1pulse exit"); 133 | } 134 | 135 | /* 136 | * Class: pt_chambino_p_pulse_Pulse_Face 137 | * Method: _existsPulse 138 | * Signature: (J)Z 139 | */ 140 | JNIEXPORT jboolean JNICALL Java_pt_chambino_p_pulse_Pulse_00024Face__1existsPulse 141 | (JNIEnv *jenv, jclass, jlong self) 142 | { 143 | LOGD("Java_pt_chambino_p_pulse_Pulse_00024Face__1existsPulse enter"); 144 | jboolean result = false; 145 | try 146 | { 147 | if (self) 148 | result = ((Pulse::Face*)self)->existsPulse; 149 | } 150 | catch(cv::Exception& e) 151 | { 152 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 153 | if(!je) je = jenv->FindClass("java/lang/Exception"); 154 | jenv->ThrowNew(je, e.what()); 155 | } 156 | catch (...) 157 | { 158 | jclass je = jenv->FindClass("java/lang/Exception"); 159 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 160 | } 161 | LOGD("Java_pt_chambino_p_pulse_Pulse_00024Face__1existsPulse exit"); 162 | return result; 163 | } 164 | -------------------------------------------------------------------------------- /src/org/opencv/android/MyJavaCameraView.java: -------------------------------------------------------------------------------- 1 | package org.opencv.android; 2 | 3 | import java.util.List; 4 | 5 | import android.content.Context; 6 | import android.content.res.Configuration; 7 | import android.graphics.ImageFormat; 8 | import android.graphics.SurfaceTexture; 9 | import android.hardware.Camera; 10 | import android.hardware.Camera.PreviewCallback; 11 | import android.os.Build; 12 | import android.util.AttributeSet; 13 | import android.util.Log; 14 | import android.view.ViewGroup.LayoutParams; 15 | import org.opencv.core.Core; 16 | 17 | import org.opencv.core.CvType; 18 | import org.opencv.core.Mat; 19 | import org.opencv.core.Size; 20 | import org.opencv.imgproc.Imgproc; 21 | 22 | /** 23 | * This class is an implementation of the Bridge View between OpenCV and Java Camera. 24 | * This class relays on the functionality available in base class and only implements 25 | * required functions: 26 | * connectCamera - opens Java camera and sets the PreviewCallback to be delivered. 27 | * disconnectCamera - closes the camera and stops preview. 28 | * When frame is delivered via callback from Camera - it processed via OpenCV to be 29 | * converted to RGBA32 and then passed to the external callback for modifications if required. 30 | */ 31 | public class MyJavaCameraView extends MyCameraBridgeViewBase implements PreviewCallback { 32 | 33 | private static final int MAGIC_TEXTURE_ID = 10; 34 | private static final String TAG = "MyJavaCameraView"; 35 | 36 | private byte mBuffer[]; 37 | private Mat[] mFrameChain; 38 | private int mChainIdx = 0; 39 | private Thread mThread; 40 | private boolean mStopThread; 41 | 42 | protected Camera mCamera; 43 | protected MyJavaCameraView.JavaCameraFrame mCameraFrame; 44 | private SurfaceTexture mSurfaceTexture; 45 | 46 | public static class JavaCameraSizeAccessor implements MyCameraBridgeViewBase.ListItemAccessor { 47 | 48 | public int getWidth(Object obj) { 49 | Camera.Size size = (Camera.Size) obj; 50 | return size.width; 51 | } 52 | 53 | public int getHeight(Object obj) { 54 | Camera.Size size = (Camera.Size) obj; 55 | return size.height; 56 | } 57 | } 58 | 59 | public MyJavaCameraView(Context context, int cameraId) { 60 | super(context, cameraId); 61 | } 62 | 63 | public MyJavaCameraView(Context context, AttributeSet attrs) { 64 | super(context, attrs); 65 | } 66 | 67 | protected boolean initializeCamera(int width, int height) { 68 | Log.d(TAG, "Initialize java camera"); 69 | boolean result = true; 70 | synchronized (this) { 71 | mCamera = null; 72 | 73 | if (mCameraIndex == -1) { 74 | Log.d(TAG, "Trying to open camera with old open()"); 75 | try { 76 | mCamera = Camera.open(); 77 | } 78 | catch (Exception e){ 79 | Log.e(TAG, "Camera is not available (in use or does not exist): " + e.getLocalizedMessage()); 80 | } 81 | 82 | if(mCamera == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { 83 | boolean connected = false; 84 | for (int camIdx = 0; camIdx < Camera.getNumberOfCameras(); ++camIdx) { 85 | Log.d(TAG, "Trying to open camera with new open(" + Integer.valueOf(camIdx) + ")"); 86 | try { 87 | mCamera = Camera.open(camIdx); 88 | connected = true; 89 | } catch (RuntimeException e) { 90 | Log.e(TAG, "Camera #" + camIdx + "failed to open: " + e.getLocalizedMessage()); 91 | } 92 | if (connected) break; 93 | } 94 | } 95 | } else { 96 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { 97 | Log.d(TAG, "Trying to open camera with new open(" + Integer.valueOf(mCameraIndex) + ")"); 98 | try { 99 | mCamera = Camera.open(mCameraIndex); 100 | } catch (RuntimeException e) { 101 | Log.e(TAG, "Camera #" + mCameraIndex + "failed to open: " + e.getLocalizedMessage()); 102 | } 103 | } 104 | } 105 | 106 | if (mCamera == null) 107 | return false; 108 | 109 | /* Now set camera parameters */ 110 | try { 111 | Camera.Parameters params = mCamera.getParameters(); 112 | Log.d(TAG, "getSupportedPreviewSizes()"); 113 | List sizes = params.getSupportedPreviewSizes(); 114 | 115 | if (sizes != null) { 116 | for (android.hardware.Camera.Size s : sizes) Log.d(TAG, s.width+"x"+s.height); 117 | 118 | /* Select the size that fits surface considering maximum size allowed */ 119 | Size frameSize = calculateCameraFrameSize(sizes, new MyJavaCameraView.JavaCameraSizeAccessor(), width, height); 120 | 121 | params.setPreviewFormat(ImageFormat.NV21); 122 | Log.d(TAG, "Set preview size to " + Integer.valueOf((int)frameSize.width) + "x" + Integer.valueOf((int)frameSize.height)); 123 | params.setPreviewSize((int)frameSize.width, (int)frameSize.height); 124 | 125 | List FocusModes = params.getSupportedFocusModes(); 126 | if (FocusModes != null && FocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) 127 | { 128 | params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); 129 | } 130 | 131 | if (params.isVideoStabilizationSupported()) { 132 | params.setVideoStabilization(true); 133 | } 134 | 135 | params.setRecordingHint(true); 136 | 137 | mCamera.setParameters(params); 138 | params = mCamera.getParameters(); 139 | 140 | mFrameWidth = params.getPreviewSize().width; 141 | mFrameHeight = params.getPreviewSize().height; 142 | 143 | int size = mFrameWidth * mFrameHeight; 144 | size = size * ImageFormat.getBitsPerPixel(params.getPreviewFormat()) / 8; 145 | mBuffer = new byte[size]; 146 | 147 | mCamera.addCallbackBuffer(mBuffer); 148 | mCamera.setPreviewCallbackWithBuffer(this); 149 | 150 | mFrameChain = new Mat[2]; 151 | mFrameChain[0] = new Mat(mFrameHeight + (mFrameHeight/2), mFrameWidth, CvType.CV_8UC1); 152 | mFrameChain[1] = new Mat(mFrameHeight + (mFrameHeight/2), mFrameWidth, CvType.CV_8UC1); 153 | 154 | mCameraFrame = new MyJavaCameraView.JavaCameraFrame(mFrameChain[mChainIdx], mFrameWidth, mFrameHeight); 155 | 156 | if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { 157 | mFrameWidth = params.getPreviewSize().height; 158 | mFrameHeight = params.getPreviewSize().width; 159 | } 160 | 161 | AllocateCache(); 162 | 163 | if ((getLayoutParams().width == LayoutParams.MATCH_PARENT) && (getLayoutParams().height == LayoutParams.MATCH_PARENT)) { 164 | mScale = Math.max(((float)width)/mFrameWidth, ((float)height)/mFrameHeight); 165 | } 166 | 167 | if (mFpsMeter != null) { 168 | mFpsMeter.setResolution(mFrameWidth, mFrameHeight); 169 | } 170 | 171 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 172 | mSurfaceTexture = new SurfaceTexture(MAGIC_TEXTURE_ID); 173 | mCamera.setPreviewTexture(mSurfaceTexture); 174 | } else 175 | mCamera.setPreviewDisplay(null); 176 | 177 | /* Finally we are ready to start the preview */ 178 | Log.d(TAG, "startPreview"); 179 | mCamera.startPreview(); 180 | } 181 | else 182 | result = false; 183 | } catch (Exception e) { 184 | result = false; 185 | e.printStackTrace(); 186 | } 187 | } 188 | 189 | return result; 190 | } 191 | 192 | protected void releaseCamera() { 193 | synchronized (this) { 194 | if (mCamera != null) { 195 | mCamera.stopPreview(); 196 | mCamera.release(); 197 | } 198 | mCamera = null; 199 | if (mFrameChain != null) { 200 | mFrameChain[0].release(); 201 | mFrameChain[1].release(); 202 | } 203 | if (mCameraFrame != null) 204 | mCameraFrame.release(); 205 | } 206 | } 207 | 208 | @Override 209 | protected boolean connectCamera(int width, int height) { 210 | 211 | /* 1. We need to instantiate camera 212 | * 2. We need to start thread which will be getting frames 213 | */ 214 | /* First step - initialize camera connection */ 215 | Log.d(TAG, "Connecting to camera"); 216 | if (!initializeCamera(width, height)) 217 | return false; 218 | 219 | /* now we can start update thread */ 220 | Log.d(TAG, "Starting processing thread"); 221 | mStopThread = false; 222 | mThread = new Thread(new MyJavaCameraView.CameraWorker()); 223 | mThread.start(); 224 | 225 | return true; 226 | } 227 | 228 | protected void disconnectCamera() { 229 | /* 1. We need to stop thread which updating the frames 230 | * 2. Stop camera and release it 231 | */ 232 | Log.d(TAG, "Disconnecting from camera"); 233 | try { 234 | mStopThread = true; 235 | Log.d(TAG, "Notify thread"); 236 | synchronized (this) { 237 | this.notify(); 238 | } 239 | Log.d(TAG, "Wating for thread"); 240 | if (mThread != null) 241 | mThread.join(); 242 | } catch (InterruptedException e) { 243 | e.printStackTrace(); 244 | } finally { 245 | mThread = null; 246 | } 247 | 248 | /* Now release camera */ 249 | releaseCamera(); 250 | } 251 | 252 | public void onPreviewFrame(byte[] frame, Camera arg1) { 253 | Log.d(TAG, "Preview Frame received. Frame size: " + frame.length); 254 | synchronized (this) { 255 | mFrameChain[1 - mChainIdx].put(0, 0, frame); 256 | this.notify(); 257 | } 258 | if (mCamera != null) 259 | mCamera.addCallbackBuffer(mBuffer); 260 | } 261 | 262 | public class JavaCameraFrame implements MyCameraBridgeViewBase.CvCameraViewFrame { 263 | public Mat gray() { 264 | mGray = mYuvFrameData.submat(0, mHeight, 0, mWidth); 265 | return rotate(mGray); 266 | } 267 | 268 | public Mat rgba() { 269 | Imgproc.cvtColor(mYuvFrameData, mRgba, Imgproc.COLOR_YUV2BGR_NV12, 4); 270 | return rotate(mRgba); 271 | } 272 | 273 | public Mat rgb() { 274 | Imgproc.cvtColor(mYuvFrameData, mRgb, Imgproc.COLOR_YUV2BGR_NV12); 275 | return rotate(mRgb); 276 | } 277 | 278 | private Mat rotate(Mat frame) { 279 | Camera.CameraInfo info = new Camera.CameraInfo(); 280 | Camera.getCameraInfo(mCameraIndex, info); 281 | 282 | // FIXME all this flipping and transposing is very performance expensive 283 | if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { 284 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 285 | Core.flip(frame.t(), frame, -1); 286 | } else { 287 | Core.flip(frame.t(), frame, 1); 288 | } 289 | } else { 290 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 291 | Core.flip(frame, frame, 1); 292 | } 293 | } 294 | return frame; 295 | } 296 | 297 | public JavaCameraFrame(Mat Yuv420sp, int width, int height) { 298 | super(); 299 | mWidth = width; 300 | mHeight = height; 301 | mYuvFrameData = Yuv420sp; 302 | mRgba = new Mat(); 303 | mRgb = new Mat(); 304 | mGray = new Mat(); 305 | } 306 | 307 | public void release() { 308 | mRgba.release(); 309 | mRgb.release(); 310 | mGray.release(); 311 | } 312 | 313 | private JavaCameraFrame(MyCameraBridgeViewBase.CvCameraViewFrame obj) { 314 | } 315 | 316 | private Mat mYuvFrameData; 317 | private Mat mRgba; 318 | private Mat mRgb; 319 | private Mat mGray; 320 | private int mWidth; 321 | private int mHeight; 322 | }; 323 | 324 | private class CameraWorker implements Runnable { 325 | 326 | public void run() { 327 | do { 328 | synchronized (MyJavaCameraView.this) { 329 | try { 330 | MyJavaCameraView.this.wait(); 331 | } catch (InterruptedException e) { 332 | // TODO Auto-generated catch block 333 | e.printStackTrace(); 334 | } 335 | } 336 | 337 | if (!mStopThread) { 338 | if (!mFrameChain[mChainIdx].empty()) 339 | deliverAndDrawFrame(mCameraFrame); 340 | mChainIdx = 1 - mChainIdx; 341 | } 342 | } while (!mStopThread); 343 | Log.d(TAG, "Finish processing thread"); 344 | } 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /jni/pt_chambino_p_pulse_Pulse.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "Pulse.hpp" 6 | 7 | #include 8 | #define LOG_TAG "Pulse::Pulse" 9 | #define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) 10 | 11 | using std::string; 12 | using cv::Mat; 13 | 14 | /* 15 | * Class: pt_chambino_p_pulse_Pulse 16 | * Method: _initialize 17 | * Signature: ()J 18 | */ 19 | JNIEXPORT jlong JNICALL Java_pt_chambino_p_pulse_Pulse__1initialize 20 | (JNIEnv *jenv, jclass) 21 | { 22 | LOGD("Java_pt_chambino_p_pulse_Pulse__1initialize enter"); 23 | jlong result = 0; 24 | try 25 | { 26 | result = (jlong)new Pulse(); 27 | } 28 | catch(cv::Exception& e) 29 | { 30 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 31 | if(!je) je = jenv->FindClass("java/lang/Exception"); 32 | jenv->ThrowNew(je, e.what()); 33 | } 34 | catch (...) 35 | { 36 | jclass je = jenv->FindClass("java/lang/Exception"); 37 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 38 | } 39 | LOGD("Java_pt_chambino_p_pulse_Pulse__1initialize exit"); 40 | return result; 41 | } 42 | 43 | /* 44 | * Class: pt_chambino_p_pulse_Pulse 45 | * Method: _load 46 | * Signature: (JLjava/lang/String;)V 47 | */ 48 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse__1load 49 | (JNIEnv *jenv, jclass, jlong self, jstring jfilename) 50 | { 51 | LOGD("Java_pt_chambino_p_pulse_Pulse__1load enter"); 52 | string filename(jenv->GetStringUTFChars(jfilename, NULL)); 53 | try 54 | { 55 | if (self) 56 | ((Pulse*)self)->load(filename); 57 | } 58 | catch(cv::Exception& e) 59 | { 60 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 61 | if(!je) je = jenv->FindClass("java/lang/Exception"); 62 | jenv->ThrowNew(je, e.what()); 63 | } 64 | catch (...) 65 | { 66 | jclass je = jenv->FindClass("java/lang/Exception"); 67 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 68 | } 69 | LOGD("Java_pt_chambino_p_pulse_Pulse__1load exit"); 70 | } 71 | 72 | /* 73 | * Class: pt_chambino_p_pulse_Pulse 74 | * Method: _start 75 | * Signature: (JII)V 76 | */ 77 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse__1start 78 | (JNIEnv *jenv, jclass, jlong self, jint width, jint height) 79 | { 80 | LOGD("Java_pt_chambino_p_pulse_Pulse__1start enter"); 81 | try 82 | { 83 | if (self) 84 | ((Pulse*)self)->start(width, height); 85 | } 86 | catch(cv::Exception& e) 87 | { 88 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 89 | if(!je) je = jenv->FindClass("java/lang/Exception"); 90 | jenv->ThrowNew(je, e.what()); 91 | } 92 | catch (...) 93 | { 94 | jclass je = jenv->FindClass("java/lang/Exception"); 95 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 96 | } 97 | LOGD("Java_pt_chambino_p_pulse_Pulse__1start exit"); 98 | } 99 | 100 | /* 101 | * Class: pt_chambino_p_pulse_Pulse 102 | * Method: _onFrame 103 | * Signature: (JJ)V 104 | */ 105 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse__1onFrame 106 | (JNIEnv *jenv, jclass, jlong self, jlong frame) 107 | { 108 | LOGD("Java_pt_chambino_p_pulse_Pulse__1onFrame enter"); 109 | try 110 | { 111 | if (self) { 112 | ((Pulse*)self)->onFrame(*((Mat*)frame)); 113 | } 114 | } 115 | catch(cv::Exception& e) 116 | { 117 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 118 | if(!je) je = jenv->FindClass("java/lang/Exception"); 119 | jenv->ThrowNew(je, e.what()); 120 | } 121 | catch (...) 122 | { 123 | jclass je = jenv->FindClass("java/lang/Exception"); 124 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 125 | } 126 | LOGD("Java_pt_chambino_p_pulse_Pulse__1onFrame exit"); 127 | } 128 | 129 | /* 130 | * Class: pt_chambino_p_pulse_Pulse 131 | * Method: _facesCount 132 | * Signature: (J)I 133 | */ 134 | JNIEXPORT jint JNICALL Java_pt_chambino_p_pulse_Pulse__1facesCount 135 | (JNIEnv *jenv, jclass, jlong self) 136 | { 137 | LOGD("Java_pt_chambino_p_pulse_Pulse__1facesCount enter"); 138 | try 139 | { 140 | if (self) 141 | return ((Pulse*)self)->faces.size(); 142 | } 143 | catch(cv::Exception& e) 144 | { 145 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 146 | if(!je) je = jenv->FindClass("java/lang/Exception"); 147 | jenv->ThrowNew(je, e.what()); 148 | } 149 | catch (...) 150 | { 151 | jclass je = jenv->FindClass("java/lang/Exception"); 152 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 153 | } 154 | LOGD("Java_pt_chambino_p_pulse_Pulse__1facesCount exit"); 155 | return 0; 156 | } 157 | 158 | /* 159 | * Class: pt_chambino_p_pulse_Pulse 160 | * Method: _face 161 | * Signature: (JI)J 162 | */ 163 | JNIEXPORT jlong JNICALL Java_pt_chambino_p_pulse_Pulse__1face 164 | (JNIEnv *jenv, jclass, jlong self, jint i) 165 | { 166 | LOGD("Java_pt_chambino_p_pulse_Pulse__1face enter"); 167 | try 168 | { 169 | if (self) 170 | return (jlong)(&((Pulse*)self)->faces.at(i)); 171 | } 172 | catch(cv::Exception& e) 173 | { 174 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 175 | if(!je) je = jenv->FindClass("java/lang/Exception"); 176 | jenv->ThrowNew(je, e.what()); 177 | } 178 | catch (...) 179 | { 180 | jclass je = jenv->FindClass("java/lang/Exception"); 181 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 182 | } 183 | LOGD("Java_pt_chambino_p_pulse_Pulse__1face exit"); 184 | return 0; 185 | } 186 | 187 | /* 188 | * Class: pt_chambino_p_pulse_Pulse 189 | * Method: _relativeMinFaceSize 190 | * Signature: (J)D 191 | */ 192 | JNIEXPORT jdouble JNICALL Java_pt_chambino_p_pulse_Pulse__1relativeMinFaceSize 193 | (JNIEnv *jenv, jclass, jlong self) 194 | { 195 | LOGD("Java_pt_chambino_p_pulse_Pulse__1maxSignalSize enter"); 196 | jdouble result = 0; 197 | try 198 | { 199 | if (self) 200 | result = ((Pulse*)self)->relativeMinFaceSize; 201 | } 202 | catch(cv::Exception& e) 203 | { 204 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 205 | if(!je) je = jenv->FindClass("java/lang/Exception"); 206 | jenv->ThrowNew(je, e.what()); 207 | } 208 | catch (...) 209 | { 210 | jclass je = jenv->FindClass("java/lang/Exception"); 211 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 212 | } 213 | LOGD("Java_pt_chambino_p_pulse_Pulse__1maxSignalSize exit"); 214 | return result; 215 | } 216 | 217 | /* 218 | * Class: pt_chambino_p_pulse_Pulse 219 | * Method: _maxSignalSize 220 | * Signature: (J)I 221 | */ 222 | JNIEXPORT jint JNICALL Java_pt_chambino_p_pulse_Pulse__1maxSignalSize 223 | (JNIEnv *jenv, jclass, jlong self) 224 | { 225 | LOGD("Java_pt_chambino_p_pulse_Pulse__1maxSignalSize enter"); 226 | jint result = 0; 227 | try 228 | { 229 | if (self) 230 | result = ((Pulse*)self)->maxSignalSize; 231 | } 232 | catch(cv::Exception& e) 233 | { 234 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 235 | if(!je) je = jenv->FindClass("java/lang/Exception"); 236 | jenv->ThrowNew(je, e.what()); 237 | } 238 | catch (...) 239 | { 240 | jclass je = jenv->FindClass("java/lang/Exception"); 241 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 242 | } 243 | LOGD("Java_pt_chambino_p_pulse_Pulse__1maxSignalSize exit"); 244 | return result; 245 | } 246 | 247 | /* 248 | * Class: pt_chambino_p_pulse_Pulse 249 | * Method: _faceDetection 250 | * Signature: (J)Z 251 | */ 252 | JNIEXPORT jboolean JNICALL Java_pt_chambino_p_pulse_Pulse__1faceDetection__J 253 | (JNIEnv *jenv, jclass, jlong self) 254 | { 255 | LOGD("Java_pt_chambino_p_pulse_Pulse__1faceDetection__J enter"); 256 | jboolean result = false; 257 | try 258 | { 259 | if (self) 260 | result = ((Pulse*)self)->faceDetection.enabled; 261 | } 262 | catch(cv::Exception& e) 263 | { 264 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 265 | if(!je) je = jenv->FindClass("java/lang/Exception"); 266 | jenv->ThrowNew(je, e.what()); 267 | } 268 | catch (...) 269 | { 270 | jclass je = jenv->FindClass("java/lang/Exception"); 271 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 272 | } 273 | LOGD("Java_pt_chambino_p_pulse_Pulse__1faceDetection__J exit"); 274 | return result; 275 | } 276 | 277 | /* 278 | * Class: pt_chambino_p_pulse_Pulse 279 | * Method: _faceDetection 280 | * Signature: (JZ)V 281 | */ 282 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse__1faceDetection__JZ 283 | (JNIEnv *jenv, jclass, jlong self, jboolean m) 284 | { 285 | LOGD("Java_pt_chambino_p_pulse_Pulse__1faceDetection__JZ enter"); 286 | try 287 | { 288 | if (self) 289 | ((Pulse*)self)->faceDetection.enabled = m; 290 | } 291 | catch(cv::Exception& e) 292 | { 293 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 294 | if(!je) je = jenv->FindClass("java/lang/Exception"); 295 | jenv->ThrowNew(je, e.what()); 296 | } 297 | catch (...) 298 | { 299 | jclass je = jenv->FindClass("java/lang/Exception"); 300 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 301 | } 302 | LOGD("Java_pt_chambino_p_pulse_Pulse__1faceDetection__JZ exit"); 303 | } 304 | 305 | /* 306 | * Class: pt_chambino_p_pulse_Pulse 307 | * Method: _magnification 308 | * Signature: (J)Z 309 | */ 310 | JNIEXPORT jboolean JNICALL Java_pt_chambino_p_pulse_Pulse__1magnification__J 311 | (JNIEnv *jenv, jclass, jlong self) 312 | { 313 | LOGD("Java_pt_chambino_p_pulse_Pulse__1magnification__J enter"); 314 | jboolean result = false; 315 | try 316 | { 317 | if (self) 318 | result = ((Pulse*)self)->evm.magnify; 319 | } 320 | catch(cv::Exception& e) 321 | { 322 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 323 | if(!je) je = jenv->FindClass("java/lang/Exception"); 324 | jenv->ThrowNew(je, e.what()); 325 | } 326 | catch (...) 327 | { 328 | jclass je = jenv->FindClass("java/lang/Exception"); 329 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 330 | } 331 | LOGD("Java_pt_chambino_p_pulse_Pulse__1magnification__J exit"); 332 | return result; 333 | } 334 | 335 | /* 336 | * Class: pt_chambino_p_pulse_Pulse 337 | * Method: _magnification 338 | * Signature: (JZ)V 339 | */ 340 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse__1magnification__JZ 341 | (JNIEnv *jenv, jclass, jlong self, jboolean m) 342 | { 343 | LOGD("Java_pt_chambino_p_pulse_Pulse__1magnification__JZ enter"); 344 | try 345 | { 346 | if (self) 347 | ((Pulse*)self)->evm.magnify = m; 348 | } 349 | catch(cv::Exception& e) 350 | { 351 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 352 | if(!je) je = jenv->FindClass("java/lang/Exception"); 353 | jenv->ThrowNew(je, e.what()); 354 | } 355 | catch (...) 356 | { 357 | jclass je = jenv->FindClass("java/lang/Exception"); 358 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 359 | } 360 | LOGD("Java_pt_chambino_p_pulse_Pulse__1magnification__JZ exit"); 361 | } 362 | 363 | /* 364 | * Class: pt_chambino_p_pulse_Pulse 365 | * Method: _magnificationFactor 366 | * Signature: (J)I 367 | */ 368 | JNIEXPORT jint JNICALL Java_pt_chambino_p_pulse_Pulse__1magnificationFactor__J 369 | (JNIEnv *jenv, jclass, jlong self) 370 | { 371 | LOGD("Java_pt_chambino_p_pulse_Pulse__1maxSignalSize enter"); 372 | jint result = 0; 373 | try 374 | { 375 | if (self) 376 | result = ((Pulse*)self)->evm.alpha; 377 | } 378 | catch(cv::Exception& e) 379 | { 380 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 381 | if(!je) je = jenv->FindClass("java/lang/Exception"); 382 | jenv->ThrowNew(je, e.what()); 383 | } 384 | catch (...) 385 | { 386 | jclass je = jenv->FindClass("java/lang/Exception"); 387 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 388 | } 389 | LOGD("Java_pt_chambino_p_pulse_Pulse__1maxSignalSize exit"); 390 | return result; 391 | } 392 | 393 | /* 394 | * Class: pt_chambino_p_pulse_Pulse 395 | * Method: _magnificationFactor 396 | * Signature: (JI)V 397 | */ 398 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse__1magnificationFactor__JI 399 | (JNIEnv *jenv, jclass, jlong self, jint m) 400 | { 401 | LOGD("Java_pt_chambino_p_pulse_Pulse__1maxSignalSize enter"); 402 | try 403 | { 404 | if (self) 405 | ((Pulse*)self)->evm.alpha = m; 406 | } 407 | catch(cv::Exception& e) 408 | { 409 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 410 | if(!je) je = jenv->FindClass("java/lang/Exception"); 411 | jenv->ThrowNew(je, e.what()); 412 | } 413 | catch (...) 414 | { 415 | jclass je = jenv->FindClass("java/lang/Exception"); 416 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 417 | } 418 | LOGD("Java_pt_chambino_p_pulse_Pulse__1maxSignalSize exit"); 419 | } 420 | 421 | /* 422 | * Class: pt_chambino_p_pulse_Pulse 423 | * Method: _destroy 424 | * Signature: (J)V 425 | */ 426 | JNIEXPORT void JNICALL Java_pt_chambino_p_pulse_Pulse__1destroy 427 | (JNIEnv *jenv, jclass, jlong self) 428 | { 429 | LOGD("Java_pt_chambino_p_pulse_Pulse__1destroy enter"); 430 | try 431 | { 432 | if (self) 433 | delete (Pulse*)self; 434 | } 435 | catch(cv::Exception& e) 436 | { 437 | jclass je = jenv->FindClass("org/opencv/core/CvException"); 438 | if(!je) je = jenv->FindClass("java/lang/Exception"); 439 | jenv->ThrowNew(je, e.what()); 440 | } 441 | catch (...) 442 | { 443 | jclass je = jenv->FindClass("java/lang/Exception"); 444 | jenv->ThrowNew(je, "Unknown exception in JNI code."); 445 | } 446 | LOGD("Java_pt_chambino_p_pulse_Pulse__1destroy exit"); 447 | } 448 | -------------------------------------------------------------------------------- /src/pt/chambino/p/pulse/App.java: -------------------------------------------------------------------------------- 1 | package pt.chambino.p.pulse; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.Typeface; 10 | import android.os.Bundle; 11 | import android.util.Log; 12 | import android.view.Menu; 13 | import android.view.MenuInflater; 14 | import android.view.MenuItem; 15 | import android.view.WindowManager; 16 | import java.io.File; 17 | import java.io.FileOutputStream; 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.util.LinkedList; 21 | import java.util.List; 22 | import org.opencv.android.BaseLoaderCallback; 23 | import org.opencv.android.MyCameraBridgeViewBase; 24 | import org.opencv.android.MyCameraBridgeViewBase.CvCameraViewListener; 25 | import org.opencv.android.LoaderCallbackInterface; 26 | import org.opencv.android.OpenCVLoader; 27 | import org.opencv.core.Mat; 28 | import org.opencv.core.Rect; 29 | import org.opencv.highgui.Highgui; 30 | import pt.chambino.p.pulse.Pulse.Face; 31 | import pt.chambino.p.pulse.dialog.BpmDialog; 32 | import pt.chambino.p.pulse.dialog.ConfigDialog; 33 | import pt.chambino.p.pulse.view.BpmView; 34 | import pt.chambino.p.pulse.view.PulseView; 35 | 36 | public class App extends Activity implements CvCameraViewListener { 37 | 38 | private static final String TAG = "Pulse::App"; 39 | 40 | private MyCameraBridgeViewBase camera; 41 | private BpmView bpmView; 42 | private PulseView pulseView; 43 | private Pulse pulse; 44 | 45 | private Paint faceBoxPaint; 46 | private Paint faceBoxTextPaint; 47 | 48 | private ConfigDialog configDialog; 49 | 50 | private BaseLoaderCallback loaderCallback = new BaseLoaderCallback(this) { 51 | @Override 52 | public void onManagerConnected(int status) { 53 | switch (status) { 54 | 55 | case LoaderCallbackInterface.SUCCESS: 56 | loaderCallbackSuccess(); 57 | break; 58 | 59 | default: 60 | super.onManagerConnected(status); 61 | break; 62 | } 63 | } 64 | }; 65 | 66 | private void loaderCallbackSuccess() { 67 | System.loadLibrary("pulse"); 68 | 69 | pulse = new Pulse(); 70 | pulse.setFaceDetection(initFaceDetection); 71 | pulse.setMagnification(initMagnification); 72 | pulse.setMagnificationFactor(initMagnificationFactor); 73 | 74 | File dir = getDir("cascade", Context.MODE_PRIVATE); 75 | File file = createFileFromResource(dir, R.raw.lbpcascade_frontalface, "xml"); 76 | pulse.load(file.getAbsolutePath()); 77 | dir.delete(); 78 | 79 | pulseView.setGridSize(pulse.getMaxSignalSize()); 80 | 81 | camera.enableView(); 82 | } 83 | 84 | private File createFileFromResource(File dir, int id, String extension) { 85 | String name = getResources().getResourceEntryName(id) + "." + extension; 86 | InputStream is = getResources().openRawResource(id); 87 | File file = new File(dir, name); 88 | 89 | try { 90 | FileOutputStream os = new FileOutputStream(file); 91 | 92 | byte[] buffer = new byte[4096]; 93 | int bytesRead; 94 | while ((bytesRead = is.read(buffer)) != -1) { 95 | os.write(buffer, 0, bytesRead); 96 | } 97 | is.close(); 98 | os.close(); 99 | } catch (IOException ex) { 100 | Log.e(TAG, "Failed to create file: " + file.getPath(), ex); 101 | } 102 | 103 | return file; 104 | } 105 | 106 | @Override 107 | public void onCreate(Bundle savedInstanceState) { 108 | super.onCreate(savedInstanceState); 109 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 110 | 111 | setContentView(R.layout.app); 112 | 113 | camera = (MyCameraBridgeViewBase) findViewById(R.id.camera); 114 | camera.setCvCameraViewListener(this); 115 | camera.SetCaptureFormat(Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGB); 116 | camera.setMaxFrameSize(600, 600); 117 | 118 | bpmView = (BpmView) findViewById(R.id.bpm); 119 | bpmView.setBackgroundColor(Color.DKGRAY); 120 | bpmView.setTextColor(Color.LTGRAY); 121 | 122 | pulseView = (PulseView) findViewById(R.id.pulse); 123 | 124 | faceBoxPaint = initFaceBoxPaint(); 125 | faceBoxTextPaint = initFaceBoxTextPaint(); 126 | } 127 | 128 | private static final String CAMERA_ID = "camera-id"; 129 | private static final String FPS_METER = "fps-meter"; 130 | private static final String FACE_DETECTION = "face-detection"; 131 | private static final String MAGNIFICATION = "magnification"; 132 | private static final String MAGNIFICATION_FACTOR = "magnification-factor"; 133 | 134 | private boolean initFaceDetection = true; 135 | private boolean initMagnification = true; 136 | private int initMagnificationFactor = 100; 137 | 138 | @Override 139 | protected void onRestoreInstanceState(Bundle savedInstanceState) { 140 | super.onRestoreInstanceState(savedInstanceState); 141 | 142 | camera.setCameraId(savedInstanceState.getInt(CAMERA_ID)); 143 | camera.setFpsMeter(savedInstanceState.getBoolean(FPS_METER)); 144 | 145 | initFaceDetection = savedInstanceState.getBoolean(FACE_DETECTION, initFaceDetection); 146 | initMagnification = savedInstanceState.getBoolean(MAGNIFICATION, initMagnification); 147 | initMagnificationFactor = savedInstanceState.getInt(MAGNIFICATION_FACTOR, initMagnificationFactor); 148 | } 149 | 150 | @Override 151 | protected void onSaveInstanceState(Bundle outState) { 152 | super.onSaveInstanceState(outState); 153 | 154 | outState.putInt(CAMERA_ID, camera.getCameraId()); 155 | outState.putBoolean(FPS_METER, camera.isFpsMeterEnabled()); 156 | 157 | // if OpenCV Manager is not installed, pulse hasn't loaded 158 | if (pulse != null) { 159 | outState.putBoolean(FACE_DETECTION, pulse.hasFaceDetection()); 160 | outState.putBoolean(MAGNIFICATION, pulse.hasMagnification()); 161 | outState.putInt(MAGNIFICATION_FACTOR, pulse.getMagnificationFactor()); 162 | } 163 | } 164 | 165 | 166 | @Override 167 | public void onResume() { 168 | super.onResume(); 169 | OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_11, this, loaderCallback); 170 | } 171 | 172 | @Override 173 | public void onPause() { 174 | if (camera != null) { 175 | camera.disableView(); 176 | } 177 | bpmView.setNoBpm(); 178 | pulseView.setNoPulse(); 179 | super.onPause(); 180 | } 181 | 182 | @Override 183 | public boolean onCreateOptionsMenu(Menu menu) { 184 | MenuInflater inflater = getMenuInflater(); 185 | inflater.inflate(R.menu.app, menu); 186 | return true; 187 | } 188 | 189 | @Override 190 | public boolean onOptionsItemSelected(MenuItem item) { 191 | switch (item.getItemId()) { 192 | case R.id.record: 193 | onRecord(item); 194 | return true; 195 | case R.id.switch_camera: 196 | camera.switchCamera(); 197 | return true; 198 | case R.id.config: 199 | if (configDialog == null) configDialog = new ConfigDialog(); 200 | configDialog.show(getFragmentManager(), null); 201 | return true; 202 | } 203 | return super.onOptionsItemSelected(item); 204 | } 205 | 206 | private boolean recording = false; 207 | private List recordedBpms; 208 | private BpmDialog bpmDialog; 209 | private double recordedBpmAverage; 210 | 211 | private void onRecord(MenuItem item) { 212 | recording = !recording; 213 | if (recording) { 214 | item.setIcon(android.R.drawable.ic_media_pause); 215 | 216 | if (recordedBpms == null) recordedBpms = new LinkedList(); 217 | else recordedBpms.clear(); 218 | } else { 219 | item.setIcon(android.R.drawable.ic_media_play); 220 | 221 | recordedBpmAverage = 0; 222 | for (double bpm : recordedBpms) recordedBpmAverage += bpm; 223 | recordedBpmAverage /= recordedBpms.size(); 224 | 225 | if (bpmDialog == null) bpmDialog = new BpmDialog(); 226 | bpmDialog.show(getFragmentManager(), null); 227 | } 228 | } 229 | 230 | public double getRecordedBpmAverage() { 231 | return recordedBpmAverage; 232 | } 233 | 234 | public Pulse getPulse() { 235 | return pulse; 236 | } 237 | 238 | public MyCameraBridgeViewBase getCamera() { 239 | return camera; 240 | } 241 | 242 | private Rect noFaceRect; 243 | 244 | private Rect initNoFaceRect(int width, int height) { 245 | double r = pulse.getRelativeMinFaceSize(); 246 | int x = (int)(width * (1. - r) / 2.); 247 | int y = (int)(height * (1. - r) / 2.); 248 | int w = (int)(width * r); 249 | int h = (int)(height * r); 250 | return new Rect(x, y, w, h); 251 | } 252 | 253 | @Override 254 | public void onCameraViewStarted(int width, int height) { 255 | Log.d(TAG, "onCameraViewStarted("+width+", "+height+")"); 256 | pulse.start(width, height); 257 | noFaceRect = initNoFaceRect(width, height); 258 | } 259 | 260 | @Override 261 | public void onCameraViewStopped() { 262 | } 263 | 264 | @Override 265 | public Mat onCameraFrame(Mat frame) { 266 | pulse.onFrame(frame); 267 | return frame; 268 | } 269 | 270 | @Override 271 | public void onCameraFrame(Canvas canvas) { 272 | Face face = getCurrentFace(pulse.getFaces()); // TODO support multiple faces 273 | if (face != null) { 274 | onFace(canvas, face); 275 | } else { 276 | // draw no face box 277 | canvas.drawPath(createFaceBoxPath(noFaceRect), faceBoxPaint); 278 | canvas.drawText("Face here", 279 | canvas.getWidth() / 2f, 280 | canvas.getHeight() / 2f, 281 | faceBoxTextPaint); 282 | 283 | // no faces 284 | runOnUiThread(new Runnable() { 285 | @Override 286 | public void run() { 287 | bpmView.setNoBpm(); 288 | pulseView.setNoPulse(); 289 | } 290 | }); 291 | } 292 | } 293 | 294 | private int currentFaceId = 0; 295 | 296 | private Face getCurrentFace(Face[] faces) { 297 | Face face = null; 298 | 299 | if (currentFaceId > 0) { 300 | face = findFace(faces, currentFaceId); 301 | } 302 | 303 | if (face == null && faces.length > 0) { 304 | face = faces[0]; 305 | } 306 | 307 | if (face == null) { 308 | currentFaceId = 0; 309 | } else { 310 | currentFaceId = face.getId(); 311 | } 312 | 313 | return face; 314 | } 315 | 316 | private Face findFace(Face[] faces, int id) { 317 | for (Face face : faces) { 318 | if (face.getId() == id) { 319 | return face; 320 | } 321 | } 322 | return null; 323 | } 324 | 325 | private void onFace(Canvas canvas, Face face) { 326 | // grab face box 327 | Rect box = face.getBox(); 328 | 329 | // draw face box 330 | canvas.drawPath(createFaceBoxPath(box), faceBoxPaint); 331 | 332 | if (pulse.hasFaceDetection() && !face.existsPulse()) { 333 | // draw hint text 334 | canvas.drawText("Hold still", 335 | box.x + box.width / 2f, 336 | box.y + box.height / 2f - 20, 337 | faceBoxTextPaint); 338 | canvas.drawText("in a", 339 | box.x + box.width / 2f, 340 | box.y + box.height / 2f, 341 | faceBoxTextPaint); 342 | canvas.drawText("bright place", 343 | box.x + box.width / 2f, 344 | box.y + box.height / 2f + 20, 345 | faceBoxTextPaint); 346 | } 347 | 348 | // update views 349 | if (face.existsPulse()) { 350 | final double bpm = face.getBpm(); 351 | final double[] signal = face.getPulse(); 352 | runOnUiThread(new Runnable() { 353 | @Override 354 | public void run() { 355 | bpmView.setBpm(bpm); 356 | pulseView.setPulse(signal); 357 | } 358 | }); 359 | if (recording) { 360 | recordedBpms.add(bpm); 361 | } 362 | } else { 363 | // no pulse 364 | runOnUiThread(new Runnable() { 365 | @Override 366 | public void run() { 367 | bpmView.setNoBpm(); 368 | pulseView.setNoPulse(); 369 | } 370 | }); 371 | } 372 | } 373 | 374 | private Paint initFaceBoxPaint() { 375 | Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); 376 | p.setColor(Color.WHITE); 377 | p.setStyle(Paint.Style.STROKE); 378 | p.setStrokeWidth(4); 379 | p.setStrokeCap(Paint.Cap.ROUND); 380 | p.setStrokeJoin(Paint.Join.ROUND); 381 | p.setShadowLayer(2, 0, 0, Color.BLACK); 382 | return p; 383 | } 384 | 385 | private Paint initFaceBoxTextPaint() { 386 | Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); 387 | p.setColor(Color.WHITE); 388 | p.setShadowLayer(2, 0, 0, Color.DKGRAY); 389 | p.setTypeface(Typeface.createFromAsset(getAssets(), "fonts/ds_digital/DS-DIGIB.TTF")); 390 | p.setTextAlign(Paint.Align.CENTER); 391 | p.setTextSize(20f); 392 | return p; 393 | } 394 | 395 | private Path createFaceBoxPath(Rect box) { 396 | float size = box.width * 0.25f; 397 | Path path = new Path(); 398 | // top left 399 | path.moveTo(box.x, box.y + size); 400 | path.lineTo(box.x, box.y); 401 | path.lineTo(box.x + size, box.y); 402 | // top right 403 | path.moveTo(box.x + box.width, box.y + size); 404 | path.lineTo(box.x + box.width, box.y); 405 | path.lineTo(box.x + box.width - size, box.y); 406 | // bottom left 407 | path.moveTo(box.x, box.y + box.height - size); 408 | path.lineTo(box.x, box.y + box.height); 409 | path.lineTo(box.x + size, box.y + box.height); 410 | // bottom right 411 | path.moveTo(box.x + box.width, box.y + box.height - size); 412 | path.lineTo(box.x + box.width, box.y + box.height); 413 | path.lineTo(box.x + box.width - size, box.y + box.height); 414 | return path; 415 | } 416 | } 417 | -------------------------------------------------------------------------------- /src/org/opencv/android/MyCameraBridgeViewBase.java: -------------------------------------------------------------------------------- 1 | package org.opencv.android; 2 | 3 | import java.util.List; 4 | 5 | import org.opencv.core.Mat; 6 | import org.opencv.core.Size; 7 | import org.opencv.highgui.Highgui; 8 | 9 | import android.app.Activity; 10 | import android.app.AlertDialog; 11 | import android.content.Context; 12 | import android.content.DialogInterface; 13 | import android.content.res.TypedArray; 14 | import android.graphics.Bitmap; 15 | import android.graphics.Canvas; 16 | import android.graphics.Matrix; 17 | import android.util.AttributeSet; 18 | import android.util.Log; 19 | import android.view.SurfaceHolder; 20 | import android.view.SurfaceView; 21 | import static android.view.View.VISIBLE; 22 | 23 | import pt.chambino.p.pulse.R; 24 | 25 | /** 26 | * This is a basic class, implementing the interaction with Camera and OpenCV library. 27 | * The main responsibility of it - is to control when camera can be enabled, process the frame, 28 | * call external listener to make any adjustments to the frame and then draw the resulting 29 | * frame to the screen. 30 | * The clients shall implement CvCameraViewListener. 31 | */ 32 | public abstract class MyCameraBridgeViewBase extends SurfaceView implements SurfaceHolder.Callback { 33 | 34 | private static final String TAG = "MyCameraBridge"; 35 | private static final int MAX_UNSPECIFIED = -1; 36 | private static final int STOPPED = 0; 37 | private static final int STARTED = 1; 38 | 39 | private int mState = STOPPED; 40 | private Bitmap mCacheBitmap; 41 | private MyCameraBridgeViewBase.CvCameraViewListener2 mListener; 42 | private boolean mSurfaceExist; 43 | private Object mSyncObject = new Object(); 44 | 45 | protected int mFrameWidth; 46 | protected int mFrameHeight; 47 | protected int mMaxHeight; 48 | protected int mMaxWidth; 49 | protected float mScale = 1; 50 | protected int mPreviewFormat = Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA; 51 | protected int mCameraIndex = -1; 52 | protected boolean mEnabled; 53 | protected FpsMeter mFpsMeter = null; 54 | 55 | public MyCameraBridgeViewBase(Context context, int cameraId) { 56 | super(context); 57 | mCameraIndex = cameraId; 58 | getHolder().addCallback(this); 59 | mMaxWidth = MAX_UNSPECIFIED; 60 | mMaxHeight = MAX_UNSPECIFIED; 61 | } 62 | 63 | public MyCameraBridgeViewBase(Context context, AttributeSet attrs) { 64 | super(context, attrs); 65 | 66 | int count = attrs.getAttributeCount(); 67 | Log.d(TAG, "Attr count: " + Integer.valueOf(count)); 68 | 69 | TypedArray styledAttrs = getContext().obtainStyledAttributes(attrs, R.styleable.CameraBridgeViewBase); 70 | if (styledAttrs.getBoolean(R.styleable.CameraBridgeViewBase_show_fps, false)) 71 | enableFpsMeter(); 72 | 73 | mCameraIndex = styledAttrs.getInt(R.styleable.CameraBridgeViewBase_camera_id, -1); 74 | 75 | getHolder().addCallback(this); 76 | mMaxWidth = MAX_UNSPECIFIED; 77 | mMaxHeight = MAX_UNSPECIFIED; 78 | } 79 | 80 | public interface CvCameraViewListener { 81 | /** 82 | * This method is invoked when camera preview has started. After this method is invoked 83 | * the frames will start to be delivered to client via the onCameraFrame() callback. 84 | * @param width - the width of the frames that will be delivered 85 | * @param height - the height of the frames that will be delivered 86 | */ 87 | public void onCameraViewStarted(int width, int height); 88 | 89 | /** 90 | * This method is invoked when camera preview has been stopped for some reason. 91 | * No frames will be delivered via onCameraFrame() callback after this method is called. 92 | */ 93 | public void onCameraViewStopped(); 94 | 95 | /** 96 | * This method is invoked when delivery of the frame needs to be done. 97 | * The returned values - is a modified frame which needs to be displayed on the screen. 98 | * TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc) 99 | */ 100 | public Mat onCameraFrame(Mat inputFrame); 101 | 102 | public void onCameraFrame(Canvas canvas); 103 | } 104 | 105 | public interface CvCameraViewListener2 { 106 | /** 107 | * This method is invoked when camera preview has started. After this method is invoked 108 | * the frames will start to be delivered to client via the onCameraFrame() callback. 109 | * @param width - the width of the frames that will be delivered 110 | * @param height - the height of the frames that will be delivered 111 | */ 112 | public void onCameraViewStarted(int width, int height); 113 | 114 | /** 115 | * This method is invoked when camera preview has been stopped for some reason. 116 | * No frames will be delivered via onCameraFrame() callback after this method is called. 117 | */ 118 | public void onCameraViewStopped(); 119 | 120 | /** 121 | * This method is invoked when delivery of the frame needs to be done. 122 | * The returned values - is a modified frame which needs to be displayed on the screen. 123 | * TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc) 124 | */ 125 | public Mat onCameraFrame(MyCameraBridgeViewBase.CvCameraViewFrame inputFrame); 126 | 127 | public void onCameraFrame(Canvas canvas); 128 | }; 129 | 130 | protected class CvCameraViewListenerAdapter implements MyCameraBridgeViewBase.CvCameraViewListener2 { 131 | public CvCameraViewListenerAdapter(MyCameraBridgeViewBase.CvCameraViewListener oldStypeListener) { 132 | mOldStyleListener = oldStypeListener; 133 | } 134 | 135 | public void onCameraViewStarted(int width, int height) { 136 | mOldStyleListener.onCameraViewStarted(width, height); 137 | } 138 | 139 | public void onCameraViewStopped() { 140 | mOldStyleListener.onCameraViewStopped(); 141 | } 142 | 143 | public Mat onCameraFrame(MyCameraBridgeViewBase.CvCameraViewFrame inputFrame) { 144 | Mat result = null; 145 | switch (mPreviewFormat) { 146 | case Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGB: 147 | result = mOldStyleListener.onCameraFrame(inputFrame.rgb()); 148 | break; 149 | case Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA: 150 | result = mOldStyleListener.onCameraFrame(inputFrame.rgba()); 151 | break; 152 | case Highgui.CV_CAP_ANDROID_GREY_FRAME: 153 | result = mOldStyleListener.onCameraFrame(inputFrame.gray()); 154 | break; 155 | default: 156 | Log.e(TAG, "Invalid frame format! Only RGBA and Gray Scale are supported!"); 157 | }; 158 | 159 | return result; 160 | } 161 | 162 | @Override 163 | public void onCameraFrame(Canvas canvas) { 164 | mOldStyleListener.onCameraFrame(canvas); 165 | } 166 | 167 | public void setFrameFormat(int format) { 168 | mPreviewFormat = format; 169 | } 170 | 171 | private CvCameraViewListenerAdapter() {} 172 | 173 | private int mPreviewFormat = Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA; 174 | private MyCameraBridgeViewBase.CvCameraViewListener mOldStyleListener; 175 | }; 176 | 177 | /** 178 | * This class interface is abstract representation of single frame from camera for onCameraFrame callback 179 | * Attention: Do not use objects, that represents this interface out of onCameraFrame callback! 180 | */ 181 | public interface CvCameraViewFrame { 182 | 183 | /** 184 | * This method returns RGB Mat with frame 185 | */ 186 | public Mat rgb(); 187 | 188 | /** 189 | * This method returns RGBA Mat with frame 190 | */ 191 | public Mat rgba(); 192 | 193 | /** 194 | * This method returns single channel gray scale Mat with frame 195 | */ 196 | public Mat gray(); 197 | }; 198 | 199 | public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { 200 | Log.d(TAG, "call surfaceChanged event"); 201 | synchronized(mSyncObject) { 202 | if (!mSurfaceExist) { 203 | mSurfaceExist = true; 204 | checkCurrentState(); 205 | } else { 206 | /** Surface changed. We need to stop camera and restart with new parameters */ 207 | /* Pretend that old surface has been destroyed */ 208 | mSurfaceExist = false; 209 | checkCurrentState(); 210 | /* Now use new surface. Say we have it now */ 211 | mSurfaceExist = true; 212 | checkCurrentState(); 213 | } 214 | } 215 | } 216 | 217 | public void surfaceCreated(SurfaceHolder holder) { 218 | /* Do nothing. Wait until surfaceChanged delivered */ 219 | } 220 | 221 | public void surfaceDestroyed(SurfaceHolder holder) { 222 | synchronized(mSyncObject) { 223 | mSurfaceExist = false; 224 | checkCurrentState(); 225 | } 226 | } 227 | 228 | public void switchCamera() { 229 | setCameraId((mCameraIndex + 1) % 2); 230 | } 231 | 232 | public int getCameraId() { 233 | return mCameraIndex; 234 | } 235 | 236 | public void setCameraId(int cameraId) { 237 | if (mCameraIndex != cameraId) { 238 | mCameraIndex = cameraId; 239 | if (mEnabled) { 240 | disableView(); 241 | enableView(); 242 | } 243 | } 244 | } 245 | 246 | /** 247 | * This method is provided for clients, so they can enable the camera connection. 248 | * The actual onCameraViewStarted callback will be delivered only after both this method is called and surface is available 249 | */ 250 | public void enableView() { 251 | synchronized(mSyncObject) { 252 | mEnabled = true; 253 | checkCurrentState(); 254 | } 255 | } 256 | 257 | /** 258 | * This method is provided for clients, so they can disable camera connection and stop 259 | * the delivery of frames even though the surface view itself is not destroyed and still stays on the scren 260 | */ 261 | public void disableView() { 262 | synchronized(mSyncObject) { 263 | mEnabled = false; 264 | checkCurrentState(); 265 | } 266 | } 267 | 268 | /** 269 | * This method enables label with fps value on the screen 270 | */ 271 | public void enableFpsMeter() { 272 | if (mFpsMeter == null) { 273 | mFpsMeter = new FpsMeter(); 274 | mFpsMeter.setResolution(mFrameWidth, mFrameHeight); 275 | } 276 | } 277 | 278 | public void disableFpsMeter() { 279 | mFpsMeter = null; 280 | } 281 | 282 | public boolean isFpsMeterEnabled() { 283 | return mFpsMeter != null; 284 | } 285 | 286 | public void setFpsMeter(boolean enable) { 287 | if (enable) enableFpsMeter(); 288 | else disableFpsMeter(); 289 | } 290 | 291 | /** 292 | * 293 | * @param listener 294 | */ 295 | 296 | public void setCvCameraViewListener(MyCameraBridgeViewBase.CvCameraViewListener2 listener) { 297 | mListener = listener; 298 | } 299 | 300 | public void setCvCameraViewListener(MyCameraBridgeViewBase.CvCameraViewListener listener) { 301 | MyCameraBridgeViewBase.CvCameraViewListenerAdapter adapter = new MyCameraBridgeViewBase.CvCameraViewListenerAdapter(listener); 302 | adapter.setFrameFormat(mPreviewFormat); 303 | mListener = adapter; 304 | } 305 | 306 | /** 307 | * This method sets the maximum size that camera frame is allowed to be. When selecting 308 | * size - the biggest size which less or equal the size set will be selected. 309 | * As an example - we set setMaxFrameSize(200,200) and we have 176x152 and 320x240 sizes. The 310 | * preview frame will be selected with 176x152 size. 311 | * This method is useful when need to restrict the size of preview frame for some reason (for example for video recording) 312 | * @param maxWidth - the maximum width allowed for camera frame. 313 | * @param maxHeight - the maximum height allowed for camera frame 314 | */ 315 | public void setMaxFrameSize(int maxWidth, int maxHeight) { 316 | mMaxWidth = maxWidth; 317 | mMaxHeight = maxHeight; 318 | } 319 | 320 | public void SetCaptureFormat(int format) 321 | { 322 | mPreviewFormat = format; 323 | if (mListener instanceof MyCameraBridgeViewBase.CvCameraViewListenerAdapter) { 324 | MyCameraBridgeViewBase.CvCameraViewListenerAdapter adapter = (MyCameraBridgeViewBase.CvCameraViewListenerAdapter) mListener; 325 | adapter.setFrameFormat(mPreviewFormat); 326 | } 327 | } 328 | 329 | /** 330 | * Called when mSyncObject lock is held 331 | */ 332 | private void checkCurrentState() { 333 | int targetState; 334 | 335 | if (mEnabled && mSurfaceExist && getVisibility() == VISIBLE) { 336 | targetState = STARTED; 337 | } else { 338 | targetState = STOPPED; 339 | } 340 | 341 | if (targetState != mState) { 342 | /* The state change detected. Need to exit the current state and enter target state */ 343 | processExitState(mState); 344 | mState = targetState; 345 | processEnterState(mState); 346 | } 347 | } 348 | 349 | private void processEnterState(int state) { 350 | switch(state) { 351 | case STARTED: 352 | onEnterStartedState(); 353 | if (mListener != null) { 354 | mListener.onCameraViewStarted(mFrameWidth, mFrameHeight); 355 | } 356 | break; 357 | case STOPPED: 358 | onEnterStoppedState(); 359 | if (mListener != null) { 360 | mListener.onCameraViewStopped(); 361 | } 362 | break; 363 | }; 364 | } 365 | 366 | private void processExitState(int state) { 367 | switch(state) { 368 | case STARTED: 369 | onExitStartedState(); 370 | break; 371 | case STOPPED: 372 | onExitStoppedState(); 373 | break; 374 | }; 375 | } 376 | 377 | private void onEnterStoppedState() { 378 | /* nothing to do */ 379 | } 380 | 381 | private void onExitStoppedState() { 382 | /* nothing to do */ 383 | } 384 | 385 | // NOTE: The order of bitmap constructor and camera connection is important for android 4.1.x 386 | // Bitmap must be constructed before surface 387 | private void onEnterStartedState() { 388 | /* Connect camera */ 389 | if (!connectCamera(getWidth(), getHeight())) { 390 | AlertDialog ad = new AlertDialog.Builder(getContext()).create(); 391 | ad.setCancelable(false); // This blocks the 'BACK' button 392 | ad.setMessage("It seems that you device does not support camera (or it is locked). Application will be closed."); 393 | ad.setButton(DialogInterface.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() { 394 | public void onClick(DialogInterface dialog, int which) { 395 | dialog.dismiss(); 396 | ((Activity) getContext()).finish(); 397 | } 398 | }); 399 | ad.show(); 400 | 401 | } 402 | } 403 | 404 | private void onExitStartedState() { 405 | disconnectCamera(); 406 | if (mCacheBitmap != null) { 407 | mCacheBitmap.recycle(); 408 | } 409 | } 410 | 411 | /** 412 | * This method shall be called by the subclasses when they have valid 413 | * object and want it to be delivered to external client (via callback) and 414 | * then displayed on the screen. 415 | * @param frame - the current frame to be delivered 416 | */ 417 | protected void deliverAndDrawFrame(MyCameraBridgeViewBase.CvCameraViewFrame frame) { 418 | Mat modified; 419 | 420 | if (mListener != null) { 421 | modified = mListener.onCameraFrame(frame); 422 | } else { 423 | modified = frame.rgba(); 424 | } 425 | 426 | boolean bmpValid = true; 427 | if (modified != null) { 428 | try { 429 | Utils.matToBitmap(modified, mCacheBitmap); 430 | } catch(Exception e) { 431 | Log.e(TAG, "Mat type: " + modified); 432 | Log.e(TAG, "Bitmap type: " + mCacheBitmap.getWidth() + "*" + mCacheBitmap.getHeight()); 433 | Log.e(TAG, "Utils.matToBitmap() throws an exception: " + e.getMessage()); 434 | bmpValid = false; 435 | } 436 | } 437 | 438 | if (bmpValid && mCacheBitmap != null) { 439 | if (mListener != null) { 440 | mListener.onCameraFrame(new Canvas(mCacheBitmap)); 441 | } 442 | Canvas canvas = getHolder().lockCanvas(); 443 | if (canvas != null) { 444 | canvas.drawColor(0, android.graphics.PorterDuff.Mode.CLEAR); 445 | Log.d(TAG, "mStretch value: " + mScale); 446 | 447 | Matrix matrix = new Matrix(); 448 | matrix.preTranslate((canvas.getWidth() - mCacheBitmap.getWidth()) / 2, 449 | (canvas.getHeight() - mCacheBitmap.getHeight()) / 2); 450 | matrix.postScale(mScale, mScale, canvas.getWidth() / 2, canvas.getHeight() / 2); 451 | canvas.drawBitmap(mCacheBitmap, matrix, null); 452 | 453 | if (mFpsMeter != null) { 454 | mFpsMeter.measure(); 455 | mFpsMeter.draw(canvas, 20, 30); 456 | } 457 | getHolder().unlockCanvasAndPost(canvas); 458 | } 459 | } 460 | } 461 | 462 | /** 463 | * This method is invoked shall perform concrete operation to initialize the camera. 464 | * CONTRACT: as a result of this method variables mFrameWidth and mFrameHeight MUST be 465 | * initialized with the size of the Camera frames that will be delivered to external processor. 466 | * @param width - the width of this SurfaceView 467 | * @param height - the height of this SurfaceView 468 | */ 469 | protected abstract boolean connectCamera(int width, int height); 470 | 471 | /** 472 | * Disconnects and release the particular camera object being connected to this surface view. 473 | * Called when syncObject lock is held 474 | */ 475 | protected abstract void disconnectCamera(); 476 | 477 | // NOTE: On Android 4.1.x the function must be called before SurfaceTextre constructor! 478 | protected void AllocateCache() 479 | { 480 | mCacheBitmap = Bitmap.createBitmap(mFrameWidth, mFrameHeight, Bitmap.Config.ARGB_8888); 481 | } 482 | 483 | public interface ListItemAccessor { 484 | public int getWidth(Object obj); 485 | public int getHeight(Object obj); 486 | }; 487 | 488 | /** 489 | * This helper method can be called by subclasses to select camera preview size. 490 | * It goes over the list of the supported preview sizes and selects the maximum one which 491 | * fits both values set via setMaxFrameSize() and surface frame allocated for this view 492 | * @param supportedSizes 493 | * @param surfaceWidth 494 | * @param surfaceHeight 495 | * @return optimal frame size 496 | */ 497 | protected Size calculateCameraFrameSize(List supportedSizes, MyCameraBridgeViewBase.ListItemAccessor accessor, int surfaceWidth, int surfaceHeight) { 498 | int calcWidth = 0; 499 | int calcHeight = 0; 500 | 501 | int maxAllowedWidth = (mMaxWidth != MAX_UNSPECIFIED && mMaxWidth < surfaceWidth)? mMaxWidth : surfaceWidth; 502 | int maxAllowedHeight = (mMaxHeight != MAX_UNSPECIFIED && mMaxHeight < surfaceHeight)? mMaxHeight : surfaceHeight; 503 | 504 | for (Object size : supportedSizes) { 505 | int width = accessor.getWidth(size); 506 | int height = accessor.getHeight(size); 507 | 508 | if (width <= maxAllowedWidth && height <= maxAllowedHeight) { 509 | if (width >= calcWidth && height >= calcHeight) { 510 | calcWidth = (int) width; 511 | calcHeight = (int) height; 512 | } 513 | } 514 | } 515 | 516 | return new Size(calcWidth, calcHeight); 517 | } 518 | } 519 | -------------------------------------------------------------------------------- /res/raw/lbpcascade_frontalface.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | BOOST 9 | LBP 10 | 24 11 | 24 12 | 13 | GAB 14 | 0.9950000047683716 15 | 0.5000000000000000 16 | 0.9500000000000000 17 | 1 18 | 100 19 | 20 | 256 21 | 20 22 | 23 | 24 | <_> 25 | 3 26 | -0.7520892024040222 27 | 28 | 29 | <_> 30 | 31 | 0 -1 46 -67130709 -21569 -1426120013 -1275125205 -21585 32 | -16385 587145899 -24005 33 | 34 | -0.6543210148811340 0.8888888955116272 35 | 36 | <_> 37 | 38 | 0 -1 13 -163512766 -769593758 -10027009 -262145 -514457854 39 | -193593353 -524289 -1 40 | 41 | -0.7739216089248657 0.7278633713722229 42 | 43 | <_> 44 | 45 | 0 -1 2 -363936790 -893203669 -1337948010 -136907894 46 | 1088782736 -134217726 -741544961 -1590337 47 | 48 | -0.7068563103675842 0.6761534214019775 49 | 50 | <_> 51 | 4 52 | -0.4872078299522400 53 | 54 | 55 | <_> 56 | 57 | 0 -1 84 2147483647 1946124287 -536870913 2147450879 58 | 738132490 1061101567 243204619 2147446655 59 | 60 | -0.8083735704421997 0.7685696482658386 61 | 62 | <_> 63 | 64 | 0 -1 21 2147483647 263176079 1879048191 254749487 1879048191 65 | -134252545 -268435457 801111999 66 | 67 | -0.7698410153388977 0.6592915654182434 68 | 69 | <_> 70 | 71 | 0 -1 106 -98110272 1610939566 -285484400 -850010381 72 | -189334372 -1671954433 -571026695 -262145 73 | 74 | -0.7506558895111084 0.5444605946540833 75 | 76 | <_> 77 | 78 | 0 -1 48 -798690576 -131075 1095771153 -237144073 -65569 -1 79 | -216727745 -69206049 80 | 81 | -0.7775990366935730 0.5465461611747742 82 | 83 | <_> 84 | 4 85 | -1.1592328548431396 86 | 87 | 88 | <_> 89 | 90 | 0 -1 47 -21585 -20549 -100818262 -738254174 -20561 -36865 91 | -151016790 -134238549 92 | 93 | -0.5601882934570313 0.7743113040924072 94 | 95 | <_> 96 | 97 | 0 -1 12 -286003217 183435247 -268994614 -421330945 98 | -402686081 1090387966 -286785545 -402653185 99 | 100 | -0.6124526262283325 0.6978127956390381 101 | 102 | <_> 103 | 104 | 0 -1 26 -50347012 970882927 -50463492 -1253377 -134218251 105 | -50364513 -33619992 -172490753 106 | 107 | -0.6114496588706970 0.6537628173828125 108 | 109 | <_> 110 | 111 | 0 -1 8 -273 -135266321 1877977738 -2088243418 -134217987 112 | 2146926575 -18910642 1095231247 113 | 114 | -0.6854077577590942 0.5403239130973816 115 | 116 | <_> 117 | 5 118 | -0.7562355995178223 119 | 120 | 121 | <_> 122 | 123 | 0 -1 96 -1273 1870659519 -20971602 -67633153 -134250731 124 | 2004875127 -250 -150995969 125 | 126 | -0.4051094949245453 0.7584033608436585 127 | 128 | <_> 129 | 130 | 0 -1 33 -868162224 -76810262 -4262145 -257 1465211989 131 | -268959873 -2656269 -524289 132 | 133 | -0.7388162612915039 0.5340843200683594 134 | 135 | <_> 136 | 137 | 0 -1 57 -12817 -49 -541103378 -152950 -38993 -20481 -1153876 138 | -72478976 139 | 140 | -0.6582943797111511 0.5339496731758118 141 | 142 | <_> 143 | 144 | 0 -1 125 -269484161 -452984961 -319816180 -1594032130 -2111 145 | -990117891 -488975296 -520947741 146 | 147 | -0.5981323719024658 0.5323504805564880 148 | 149 | <_> 150 | 151 | 0 -1 53 557787431 670265215 -1342193665 -1075892225 152 | 1998528318 1056964607 -33570977 -1 153 | 154 | -0.6498787999153137 0.4913350641727448 155 | 156 | <_> 157 | 5 158 | -0.8085358142852783 159 | 160 | 161 | <_> 162 | 163 | 0 -1 60 -536873708 880195381 -16842788 -20971521 -176687276 164 | -168427659 -16777260 -33554626 165 | 166 | -0.5278195738792419 0.6946372389793396 167 | 168 | <_> 169 | 170 | 0 -1 7 -1 -62981529 -1090591130 805330978 -8388827 -41945787 171 | -39577 -531118985 172 | 173 | -0.5206505060195923 0.6329920291900635 174 | 175 | <_> 176 | 177 | 0 -1 98 -725287348 1347747543 -852489 -16809993 1489881036 178 | -167903241 -1 -1 179 | 180 | -0.7516061067581177 0.4232024252414703 181 | 182 | <_> 183 | 184 | 0 -1 44 -32777 1006582562 -65 935312171 -8388609 -1078198273 185 | -1 733886267 186 | 187 | -0.7639313936233521 0.4123568832874298 188 | 189 | <_> 190 | 191 | 0 -1 24 -85474705 2138828511 -1036436754 817625855 192 | 1123369029 -58796809 -1013468481 -194513409 193 | 194 | -0.5123769044876099 0.5791834592819214 195 | 196 | <_> 197 | 5 198 | -0.5549971461296082 199 | 200 | 201 | <_> 202 | 203 | 0 -1 42 -17409 -20481 -268457797 -134239493 -17473 -1 -21829 204 | -21846 205 | 206 | -0.3763174116611481 0.7298233509063721 207 | 208 | <_> 209 | 210 | 0 -1 6 -805310737 -2098262358 -269504725 682502698 211 | 2147483519 1740574719 -1090519233 -268472385 212 | 213 | -0.5352765917778015 0.5659480094909668 214 | 215 | <_> 216 | 217 | 0 -1 61 -67109678 -6145 -8 -87884584 -20481 -1073762305 218 | -50856216 -16849696 219 | 220 | -0.5678374171257019 0.4961479902267456 221 | 222 | <_> 223 | 224 | 0 -1 123 -138428633 1002418167 -1359008245 -1908670465 225 | -1346685918 910098423 -1359010520 -1346371657 226 | 227 | -0.5706262588500977 0.4572288393974304 228 | 229 | <_> 230 | 231 | 0 -1 9 -89138513 -4196353 1256531674 -1330665426 1216308261 232 | -36190633 33498198 -151796633 233 | 234 | -0.5344601869583130 0.4672054052352905 235 | 236 | <_> 237 | 5 238 | -0.8776460289955139 239 | 240 | 241 | <_> 242 | 243 | 0 -1 105 1073769576 206601725 -34013449 -33554433 -789514004 244 | -101384321 -690225153 -264193 245 | 246 | -0.7700348496437073 0.5943940877914429 247 | 248 | <_> 249 | 250 | 0 -1 30 -1432340997 -823623681 -49153 -34291724 -269484035 251 | -1342767105 -1078198273 -1277955 252 | 253 | -0.5043668746948242 0.6151274442672730 254 | 255 | <_> 256 | 257 | 0 -1 35 -1067385040 -195758209 -436748425 -134217731 258 | -50855988 -129 -1 -1 259 | 260 | -0.6808040738105774 0.4667325913906097 261 | 262 | <_> 263 | 264 | 0 -1 119 832534325 -34111555 -26050561 -423659521 -268468364 265 | 2105014143 -2114244 -17367185 266 | 267 | -0.4927591383457184 0.5401885509490967 268 | 269 | <_> 270 | 271 | 0 -1 82 -1089439888 -1080524865 2143059967 -1114121 272 | -1140949004 -3 -2361356 -739516 273 | 274 | -0.6445107460021973 0.4227822124958038 275 | 276 | <_> 277 | 6 278 | -1.1139287948608398 279 | 280 | 281 | <_> 282 | 283 | 0 -1 52 -1074071553 -1074003969 -1 -1280135430 -5324817 -1 284 | -335548482 582134442 285 | 286 | -0.5307556986808777 0.6258179545402527 287 | 288 | <_> 289 | 290 | 0 -1 99 -706937396 -705364068 -540016724 -570495027 291 | -570630659 -587857963 -33628164 -35848193 292 | 293 | -0.5227634310722351 0.5049746036529541 294 | 295 | <_> 296 | 297 | 0 -1 18 -2035630093 42119158 -268503053 -1671444 261017599 298 | 1325432815 1954394111 -805306449 299 | 300 | -0.4983572661876679 0.5106441378593445 301 | 302 | <_> 303 | 304 | 0 -1 111 -282529488 -1558073088 1426018736 -170526448 305 | -546832487 -5113037 -34243375 -570427929 306 | 307 | -0.4990860521793366 0.5060507059097290 308 | 309 | <_> 310 | 311 | 0 -1 92 1016332500 -606301707 915094269 -1080086049 312 | -1837027144 -1361600280 2147318747 1067975613 313 | 314 | -0.5695009231567383 0.4460467398166657 315 | 316 | <_> 317 | 318 | 0 -1 51 -656420166 -15413034 -141599534 -603435836 319 | 1505950458 -787556946 -79823438 -1326199134 320 | 321 | -0.6590405106544495 0.3616424500942230 322 | 323 | <_> 324 | 7 325 | -0.8243625760078430 326 | 327 | 328 | <_> 329 | 330 | 0 -1 28 -901591776 -201916417 -262 -67371009 -143312112 331 | -524289 -41943178 -1 332 | 333 | -0.4972776770591736 0.6027074456214905 334 | 335 | <_> 336 | 337 | 0 -1 112 -4507851 -411340929 -268437513 -67502145 -17350859 338 | -32901 -71344315 -29377 339 | 340 | -0.4383158981800079 0.5966237187385559 341 | 342 | <_> 343 | 344 | 0 -1 69 -75894785 -117379438 -239063587 -12538500 1485072126 345 | 2076233213 2123118847 801906927 346 | 347 | -0.6386105418205261 0.3977999985218048 348 | 349 | <_> 350 | 351 | 0 -1 19 -823480413 786628589 -16876049 -1364262914 242165211 352 | 1315930109 -696268833 -455082829 353 | 354 | -0.5512794256210327 0.4282079637050629 355 | 356 | <_> 357 | 358 | 0 -1 73 -521411968 6746762 -1396236286 -2038436114 359 | -185612509 57669627 -143132877 -1041235973 360 | 361 | -0.6418755054473877 0.3549866080284119 362 | 363 | <_> 364 | 365 | 0 -1 126 -478153869 1076028979 -1645895615 1365298272 366 | -557859073 -339771473 1442574528 -1058802061 367 | 368 | -0.4841901361942291 0.4668019413948059 369 | 370 | <_> 371 | 372 | 0 -1 45 -246350404 -1650402048 -1610612745 -788400696 373 | 1467604861 -2787397 1476263935 -4481349 374 | 375 | -0.5855734348297119 0.3879135847091675 376 | 377 | <_> 378 | 7 379 | -1.2237116098403931 380 | 381 | 382 | <_> 383 | 384 | 0 -1 114 -24819 1572863935 -16809993 -67108865 2146778388 385 | 1433927541 -268608444 -34865205 386 | 387 | -0.2518476545810700 0.7088654041290283 388 | 389 | <_> 390 | 391 | 0 -1 97 -1841359 -134271049 -32769 -5767369 -1116675 -2185 392 | -8231 -33603327 393 | 394 | -0.4303432404994965 0.5283288359642029 395 | 396 | <_> 397 | 398 | 0 -1 25 -1359507589 -1360593090 -1073778729 -269553812 399 | -809512977 1744707583 -41959433 -134758978 400 | 401 | -0.4259553551673889 0.5440809130668640 402 | 403 | <_> 404 | 405 | 0 -1 34 729753407 -134270989 -1140907329 -235200777 406 | 658456383 2147467263 -1140900929 -16385 407 | 408 | -0.5605589151382446 0.4220733344554901 409 | 410 | <_> 411 | 412 | 0 -1 134 -310380553 -420675595 -193005472 -353568129 413 | 1205338070 -990380036 887604324 -420544526 414 | 415 | -0.5192656517028809 0.4399855434894562 416 | 417 | <_> 418 | 419 | 0 -1 16 -1427119361 1978920959 -287119734 -487068946 420 | 114759245 -540578051 -707510259 -671660453 421 | 422 | -0.5013077259063721 0.4570254683494568 423 | 424 | <_> 425 | 426 | 0 -1 74 -738463762 -889949281 -328301948 -121832450 427 | -1142658284 -1863576559 2146417353 -263185 428 | 429 | -0.4631414115428925 0.4790246188640595 430 | 431 | <_> 432 | 7 433 | -0.5544230937957764 434 | 435 | 436 | <_> 437 | 438 | 0 -1 113 -76228780 -65538 -1 -67174401 -148007 -33 -221796 439 | -272842924 440 | 441 | -0.3949716091156006 0.6082032322883606 442 | 443 | <_> 444 | 445 | 0 -1 110 369147696 -1625232112 2138570036 -1189900 790708019 446 | -1212613127 799948719 -4456483 447 | 448 | -0.4855885505676270 0.4785369932651520 449 | 450 | <_> 451 | 452 | 0 -1 37 784215839 -290015241 536832799 -402984963 453 | -1342414991 -838864897 -176769 -268456129 454 | 455 | -0.4620285332202911 0.4989669024944305 456 | 457 | <_> 458 | 459 | 0 -1 41 -486418688 -171915327 -340294900 -21938 -519766032 460 | -772751172 -73096060 -585322623 461 | 462 | -0.6420643329620361 0.3624351918697357 463 | 464 | <_> 465 | 466 | 0 -1 117 -33554953 -475332625 -1423463824 -2077230421 467 | -4849669 -2080505925 -219032928 -1071915349 468 | 469 | -0.4820112884044647 0.4632140696048737 470 | 471 | <_> 472 | 473 | 0 -1 65 -834130468 -134217476 -1349314083 -1073803559 474 | -619913764 -1449131844 -1386890321 -1979118423 475 | 476 | -0.4465552568435669 0.5061788558959961 477 | 478 | <_> 479 | 480 | 0 -1 56 -285249779 1912569855 -16530 -1731022870 -1161904146 481 | -1342177297 -268439634 -1464078708 482 | 483 | -0.5190586447715759 0.4441480338573456 484 | 485 | <_> 486 | 7 487 | -0.7161560654640198 488 | 489 | 490 | <_> 491 | 492 | 0 -1 20 1246232575 1078001186 -10027057 60102 -277348353 493 | -43646987 -1210581153 1195769615 494 | 495 | -0.4323809444904327 0.5663768053054810 496 | 497 | <_> 498 | 499 | 0 -1 15 -778583572 -612921106 -578775890 -4036478 500 | -1946580497 -1164766570 -1986687009 -12103599 501 | 502 | -0.4588732719421387 0.4547033011913300 503 | 504 | <_> 505 | 506 | 0 -1 129 -1073759445 2013231743 -1363169553 -1082459201 507 | -1414286549 868185983 -1356133589 -1077936257 508 | 509 | -0.5218553543090820 0.4111092388629913 510 | 511 | <_> 512 | 513 | 0 -1 102 -84148365 -2093417722 -1204850272 564290299 514 | -67121221 -1342177350 -1309195902 -776734797 515 | 516 | -0.4920000731945038 0.4326725304126740 517 | 518 | <_> 519 | 520 | 0 -1 88 -25694458 67104495 -290216278 -168563037 2083877442 521 | 1702788383 -144191964 -234882162 522 | 523 | -0.4494568109512329 0.4448510706424713 524 | 525 | <_> 526 | 527 | 0 -1 59 -857980836 904682741 -1612267521 232279415 528 | 1550862252 -574825221 -357380888 -4579409 529 | 530 | -0.5180826783180237 0.3888972699642181 531 | 532 | <_> 533 | 534 | 0 -1 27 -98549440 -137838400 494928389 -246013630 939541351 535 | -1196072350 -620603549 2137216273 536 | 537 | -0.6081240773200989 0.3333222270011902 538 | 539 | <_> 540 | 8 541 | -0.6743940711021423 542 | 543 | 544 | <_> 545 | 546 | 0 -1 29 -150995201 2071191945 -1302151626 536934335 547 | -1059008937 914128709 1147328110 -268369925 548 | 549 | -0.1790193915367127 0.6605972051620483 550 | 551 | <_> 552 | 553 | 0 -1 128 -134509479 1610575703 -1342177289 1861484541 554 | -1107833788 1577058173 -333558568 -136319041 555 | 556 | -0.3681024610996246 0.5139749646186829 557 | 558 | <_> 559 | 560 | 0 -1 70 -1 1060154476 -1090984524 -630918524 -539492875 561 | 779616255 -839568424 -321 562 | 563 | -0.3217232525348663 0.6171553134918213 564 | 565 | <_> 566 | 567 | 0 -1 4 -269562385 -285029906 -791084350 -17923776 235286671 568 | 1275504943 1344390399 -966276889 569 | 570 | -0.4373284578323364 0.4358185231685638 571 | 572 | <_> 573 | 574 | 0 -1 76 17825984 -747628419 595427229 1474759671 575672208 575 | -1684005538 872217086 -1155858277 576 | 577 | -0.4404836893081665 0.4601220190525055 578 | 579 | <_> 580 | 581 | 0 -1 124 -336593039 1873735591 -822231622 -355795238 582 | -470820869 -1997537409 -1057132384 -1015285005 583 | 584 | -0.4294152259826660 0.4452161788940430 585 | 586 | <_> 587 | 588 | 0 -1 54 -834212130 -593694721 -322142257 -364892500 589 | -951029539 -302125121 -1615106053 -79249765 590 | 591 | -0.3973052501678467 0.4854526817798615 592 | 593 | <_> 594 | 595 | 0 -1 95 1342144479 2147431935 -33554561 -47873 -855685912 -1 596 | 1988052447 536827383 597 | 598 | -0.7054683566093445 0.2697997391223908 599 | 600 | <_> 601 | 9 602 | -1.2042298316955566 603 | 604 | 605 | <_> 606 | 607 | 0 -1 39 1431368960 -183437936 -537002499 -137497097 608 | 1560590321 -84611081 -2097193 -513 609 | 610 | -0.5905947685241699 0.5101932883262634 611 | 612 | <_> 613 | 614 | 0 -1 120 -1645259691 2105491231 2130706431 1458995007 615 | -8567536 -42483883 -33780003 -21004417 616 | 617 | -0.4449204802513123 0.4490709304809570 618 | 619 | <_> 620 | 621 | 0 -1 89 -612381022 -505806938 -362027516 -452985106 622 | 275854917 1920431639 -12600561 -134221825 623 | 624 | -0.4693818688392639 0.4061094820499420 625 | 626 | <_> 627 | 628 | 0 -1 14 -805573153 -161 -554172679 -530519488 -16779441 629 | 2000682871 -33604275 -150997129 630 | 631 | -0.3600351214408875 0.5056326985359192 632 | 633 | <_> 634 | 635 | 0 -1 67 6192 435166195 1467449341 2046691505 -1608493775 636 | -4755729 -1083162625 -71365637 637 | 638 | -0.4459891915321350 0.4132415652275085 639 | 640 | <_> 641 | 642 | 0 -1 86 -41689215 -3281034 1853357967 -420712635 -415924289 643 | -270209208 -1088293113 -825311232 644 | 645 | -0.4466069042682648 0.4135067760944367 646 | 647 | <_> 648 | 649 | 0 -1 80 -117391116 -42203396 2080374461 -188709 -542008165 650 | -356831940 -1091125345 -1073796897 651 | 652 | -0.3394956290721893 0.5658645033836365 653 | 654 | <_> 655 | 656 | 0 -1 75 -276830049 1378714472 -1342181951 757272098 657 | 1073740607 -282199241 -415761549 170896931 658 | 659 | -0.5346512198448181 0.3584479391574860 660 | 661 | <_> 662 | 663 | 0 -1 55 -796075825 -123166849 2113667055 -217530421 664 | -1107432194 -16385 -806359809 -391188771 665 | 666 | -0.4379335641860962 0.4123645126819611 667 | 668 | <_> 669 | 10 670 | -0.8402050137519836 671 | 672 | 673 | <_> 674 | 675 | 0 -1 71 -890246622 15525883 -487690486 47116238 -1212319899 676 | -1291847681 -68159890 -469829921 677 | 678 | -0.2670986354351044 0.6014143228530884 679 | 680 | <_> 681 | 682 | 0 -1 31 -1361180685 -1898008841 -1090588811 -285410071 683 | -1074016265 -840443905 2147221487 -262145 684 | 685 | -0.4149844348430634 0.4670888185501099 686 | 687 | <_> 688 | 689 | 0 -1 40 1426190596 1899364271 2142731795 -142607505 690 | -508232452 -21563393 -41960001 -65 691 | 692 | -0.4985891580581665 0.3719584941864014 693 | 694 | <_> 695 | 696 | 0 -1 109 -201337965 10543906 -236498096 -746195597 697 | 1974565825 -15204415 921907633 -190058309 698 | 699 | -0.4568729996681213 0.3965812027454376 700 | 701 | <_> 702 | 703 | 0 -1 130 -595026732 -656401928 -268649235 -571490699 704 | -440600392 -133131 -358810952 -2004088646 705 | 706 | -0.4770836830139160 0.3862601518630981 707 | 708 | <_> 709 | 710 | 0 -1 66 941674740 -1107882114 1332789109 -67691015 711 | -1360463693 -1556612430 -609108546 733546933 712 | 713 | -0.4877715110778809 0.3778986334800720 714 | 715 | <_> 716 | 717 | 0 -1 49 -17114945 -240061474 1552871558 -82775604 -932393844 718 | -1308544889 -532635478 -99042357 719 | 720 | -0.3721654713153839 0.4994400143623352 721 | 722 | <_> 723 | 724 | 0 -1 133 -655906006 1405502603 -939205164 1884929228 725 | -498859222 559417357 -1928559445 -286264385 726 | 727 | -0.3934195041656494 0.4769641458988190 728 | 729 | <_> 730 | 731 | 0 -1 0 -335837777 1860677295 -90 -1946186226 931096183 732 | 251612987 2013265917 -671232197 733 | 734 | -0.4323300719261169 0.4342164099216461 735 | 736 | <_> 737 | 738 | 0 -1 103 37769424 -137772680 374692301 2002666345 -536176194 739 | -1644484728 807009019 1069089930 740 | 741 | -0.4993278682231903 0.3665378093719482 742 | 743 | <_> 744 | 9 745 | -1.1974394321441650 746 | 747 | 748 | <_> 749 | 750 | 0 -1 43 -5505 2147462911 2143265466 -4511070 -16450 -257 751 | -201348440 -71333206 752 | 753 | -0.3310225307941437 0.5624626278877258 754 | 755 | <_> 756 | 757 | 0 -1 90 -136842268 -499330741 2015250980 -87107126 758 | -641665744 -788524639 -1147864792 -134892563 759 | 760 | -0.5266560912132263 0.3704403042793274 761 | 762 | <_> 763 | 764 | 0 -1 104 -146800880 -1780368555 2111170033 -140904684 765 | -16777551 -1946681885 -1646463595 -839131947 766 | 767 | -0.4171888828277588 0.4540435671806335 768 | 769 | <_> 770 | 771 | 0 -1 85 -832054034 -981663763 -301990281 -578814081 772 | -932319000 -1997406723 -33555201 -69206017 773 | 774 | -0.4556705355644226 0.3704262077808380 775 | 776 | <_> 777 | 778 | 0 -1 24 -118492417 -1209026825 1119023838 -1334313353 779 | 1112948738 -297319313 1378887291 -139469193 780 | 781 | -0.4182529747486115 0.4267231225967407 782 | 783 | <_> 784 | 785 | 0 -1 78 -1714382628 -2353704 -112094959 -549613092 786 | -1567058760 -1718550464 -342315012 -1074972227 787 | 788 | -0.3625369668006897 0.4684656262397766 789 | 790 | <_> 791 | 792 | 0 -1 5 -85219702 316836394 -33279 1904970288 2117267315 793 | -260901769 -621461759 -88607770 794 | 795 | -0.4742925167083740 0.3689507246017456 796 | 797 | <_> 798 | 799 | 0 -1 11 -294654041 -353603585 -1641159686 -50331921 800 | -2080899877 1145569279 -143132713 -152044037 801 | 802 | -0.3666271567344666 0.4580127298831940 803 | 804 | <_> 805 | 806 | 0 -1 32 1887453658 -638545712 -1877976819 -34320972 807 | -1071067983 -661345416 -583338277 1060190561 808 | 809 | -0.4567637443542481 0.3894708156585693 810 | 811 | <_> 812 | 9 813 | -0.5733128190040588 814 | 815 | 816 | <_> 817 | 818 | 0 -1 122 -994063296 1088745462 -318837116 -319881377 819 | 1102566613 1165490103 -121679694 -134744129 820 | 821 | -0.4055117964744568 0.5487945079803467 822 | 823 | <_> 824 | 825 | 0 -1 68 -285233233 -538992907 1811935199 -369234005 -529 826 | -20593 -20505 -1561401854 827 | 828 | -0.3787897229194641 0.4532003402709961 829 | 830 | <_> 831 | 832 | 0 -1 58 -1335245632 1968917183 1940861695 536816369 833 | -1226071367 -570908176 457026619 1000020667 834 | 835 | -0.4258328974246979 0.4202791750431061 836 | 837 | <_> 838 | 839 | 0 -1 94 -1360318719 -1979797897 -50435249 -18646473 840 | -608879292 -805306691 -269304244 -17840167 841 | 842 | -0.4561023116111755 0.4002747833728790 843 | 844 | <_> 845 | 846 | 0 -1 87 2062765935 -16449 -1275080721 -16406 45764335 847 | -1090552065 -772846337 -570464322 848 | 849 | -0.4314672648906708 0.4086346626281738 850 | 851 | <_> 852 | 853 | 0 -1 127 -536896021 1080817663 -738234288 -965478709 854 | -2082767969 1290855887 1993822934 -990381609 855 | 856 | -0.4174543321132660 0.4249868988990784 857 | 858 | <_> 859 | 860 | 0 -1 3 -818943025 168730891 -293610428 -79249354 669224671 861 | 621166734 1086506807 1473768907 862 | 863 | -0.4321364760398865 0.4090838730335236 864 | 865 | <_> 866 | 867 | 0 -1 79 -68895696 -67107736 -1414315879 -841676168 868 | -619843344 -1180610531 -1081990469 1043203389 869 | 870 | -0.5018386244773865 0.3702533841133118 871 | 872 | <_> 873 | 874 | 0 -1 116 -54002134 -543485719 -2124882422 -1437445858 875 | -115617074 -1195787391 -1096024366 -2140472445 876 | 877 | -0.5037505626678467 0.3564981222152710 878 | 879 | <_> 880 | 9 881 | -0.4892596900463104 882 | 883 | 884 | <_> 885 | 886 | 0 -1 132 -67113211 2003808111 1862135111 846461923 -2752 887 | 2002237273 -273154752 1937223539 888 | 889 | -0.2448196411132813 0.5689709186553955 890 | 891 | <_> 892 | 893 | 0 -1 62 1179423888 -78064940 -611839555 -539167899 894 | -1289358360 -1650810108 -892540499 -1432827684 895 | 896 | -0.4633283913135529 0.3587929606437683 897 | 898 | <_> 899 | 900 | 0 -1 23 -285212705 -78450761 -656212031 -264050110 -27787425 901 | -1334349961 -547662981 -135796924 902 | 903 | -0.3731099069118500 0.4290455579757690 904 | 905 | <_> 906 | 907 | 0 -1 77 341863476 403702016 -550588417 1600194541 908 | -1080690735 951127993 -1388580949 -1153717473 909 | 910 | -0.3658909499645233 0.4556473195552826 911 | 912 | <_> 913 | 914 | 0 -1 22 -586880702 -204831512 -100644596 -39319550 915 | -1191150794 705692513 457203315 -75806957 916 | 917 | -0.5214384198188782 0.3221037387847900 918 | 919 | <_> 920 | 921 | 0 -1 72 -416546870 545911370 -673716192 -775559454 922 | -264113598 139424 -183369982 -204474641 923 | 924 | -0.4289036989212036 0.4004956185817719 925 | 926 | <_> 927 | 928 | 0 -1 50 -1026505020 -589692154 -1740499937 -1563770497 929 | 1348491006 -60710713 -1109853489 -633909413 930 | 931 | -0.4621542394161224 0.3832748532295227 932 | 933 | <_> 934 | 935 | 0 -1 108 -1448872304 -477895040 -1778390608 -772418127 936 | -1789923416 -1612057181 -805306693 -1415842113 937 | 938 | -0.3711548447608948 0.4612701535224915 939 | 940 | <_> 941 | 942 | 0 -1 92 407905424 -582449988 52654751 -1294472 -285103725 943 | -74633006 1871559083 1057955850 944 | 945 | -0.5180652141571045 0.3205870389938355 946 | 947 | <_> 948 | 10 949 | -0.5911940932273865 950 | 951 | 952 | <_> 953 | 954 | 0 -1 81 4112 -1259563825 -846671428 -100902460 1838164148 955 | -74153752 -90653988 -1074263896 956 | 957 | -0.2592592537403107 0.5873016119003296 958 | 959 | <_> 960 | 961 | 0 -1 1 -285216785 -823206977 -1085589 -1081346 1207959293 962 | 1157103471 2097133565 -2097169 963 | 964 | -0.3801195919513702 0.4718827307224274 965 | 966 | <_> 967 | 968 | 0 -1 121 -12465 -536875169 2147478367 2130706303 -37765492 969 | -866124467 -318782328 -1392509185 970 | 971 | -0.3509117066860199 0.5094807147979736 972 | 973 | <_> 974 | 975 | 0 -1 38 2147449663 -20741 -16794757 1945873146 -16710 -1 976 | -8406341 -67663041 977 | 978 | -0.4068757295608521 0.4130136370658875 979 | 980 | <_> 981 | 982 | 0 -1 17 -155191713 866117231 1651407483 548272812 -479201468 983 | -447742449 1354229504 -261884429 984 | 985 | -0.4557141065597534 0.3539792001247406 986 | 987 | <_> 988 | 989 | 0 -1 100 -225319378 -251682065 -492783986 -792341777 990 | -1287261695 1393643841 -11274182 -213909521 991 | 992 | -0.4117803275585175 0.4118592441082001 993 | 994 | <_> 995 | 996 | 0 -1 63 -382220122 -2002072729 -51404800 -371201558 997 | -923011069 -2135301457 -2066104743 -1042557441 998 | 999 | -0.4008397758007050 0.4034757018089294 1000 | 1001 | <_> 1002 | 1003 | 0 -1 101 -627353764 -48295149 1581203952 -436258614 1004 | -105268268 -1435893445 -638126888 -1061107126 1005 | 1006 | -0.5694189667701721 0.2964762747287750 1007 | 1008 | <_> 1009 | 1010 | 0 -1 118 -8399181 1058107691 -621022752 -251003468 -12582915 1011 | -574619739 -994397789 -1648362021 1012 | 1013 | -0.3195341229438782 0.5294018983840942 1014 | 1015 | <_> 1016 | 1017 | 0 -1 92 -348343812 -1078389516 1717960437 364735981 1018 | -1783841602 -4883137 -457572354 -1076950384 1019 | 1020 | -0.3365339040756226 0.5067458748817444 1021 | 1022 | <_> 1023 | 10 1024 | -0.7612916231155396 1025 | 1026 | 1027 | <_> 1028 | 1029 | 0 -1 10 -1976661318 -287957604 -1659497122 -782068 43591089 1030 | -453637880 1435470000 -1077438561 1031 | 1032 | -0.4204545319080353 0.5165745615959168 1033 | 1034 | <_> 1035 | 1036 | 0 -1 131 -67110925 14874979 -142633168 -1338923040 1037 | 2046713291 -2067933195 1473503712 -789579837 1038 | 1039 | -0.3762553930282593 0.4075302779674530 1040 | 1041 | <_> 1042 | 1043 | 0 -1 83 -272814301 -1577073 -1118685 -305156120 -1052289 1044 | -1073813756 -538971154 -355523038 1045 | 1046 | -0.4253497421741486 0.3728055357933044 1047 | 1048 | <_> 1049 | 1050 | 0 -1 135 -2233 -214486242 -538514758 573747007 -159390971 1051 | 1994225489 -973738098 -203424005 1052 | 1053 | -0.3601998090744019 0.4563256204128265 1054 | 1055 | <_> 1056 | 1057 | 0 -1 115 -261031688 -1330369299 -641860609 1029570301 1058 | -1306461192 -1196149518 -1529767778 683139823 1059 | 1060 | -0.4034293889999390 0.4160816967487335 1061 | 1062 | <_> 1063 | 1064 | 0 -1 64 -572993608 -34042628 -417865 -111109 -1433365268 1065 | -19869715 -1920939864 -1279457063 1066 | 1067 | -0.3620899617671967 0.4594142735004425 1068 | 1069 | <_> 1070 | 1071 | 0 -1 36 -626275097 -615256993 1651946018 805366393 1072 | 2016559730 -430780849 -799868165 -16580645 1073 | 1074 | -0.3903816640377045 0.4381459355354309 1075 | 1076 | <_> 1077 | 1078 | 0 -1 93 1354797300 -1090957603 1976418270 -1342502178 1079 | -1851873892 -1194637077 -1153521668 -1108399474 1080 | 1081 | -0.3591445386409760 0.4624078869819641 1082 | 1083 | <_> 1084 | 1085 | 0 -1 91 68157712 1211368313 -304759523 1063017136 798797750 1086 | -275513546 648167355 -1145357350 1087 | 1088 | -0.4297670423984528 0.4023293554782867 1089 | 1090 | <_> 1091 | 1092 | 0 -1 107 -546318240 -1628569602 -163577944 -537002306 1093 | -545456389 -1325465645 -380446736 -1058473386 1094 | 1095 | -0.5727006793022156 0.2995934784412384 1096 | 1097 | <_> 1098 | 1099 | 0 0 3 5 1100 | <_> 1101 | 1102 | 0 0 4 2 1103 | <_> 1104 | 1105 | 0 0 6 3 1106 | <_> 1107 | 1108 | 0 1 2 3 1109 | <_> 1110 | 1111 | 0 1 3 3 1112 | <_> 1113 | 1114 | 0 1 3 7 1115 | <_> 1116 | 1117 | 0 4 3 3 1118 | <_> 1119 | 1120 | 0 11 3 4 1121 | <_> 1122 | 1123 | 0 12 8 4 1124 | <_> 1125 | 1126 | 0 14 4 3 1127 | <_> 1128 | 1129 | 1 0 5 3 1130 | <_> 1131 | 1132 | 1 1 2 2 1133 | <_> 1134 | 1135 | 1 3 3 1 1136 | <_> 1137 | 1138 | 1 7 4 4 1139 | <_> 1140 | 1141 | 1 12 2 2 1142 | <_> 1143 | 1144 | 1 13 4 1 1145 | <_> 1146 | 1147 | 1 14 4 3 1148 | <_> 1149 | 1150 | 1 17 3 2 1151 | <_> 1152 | 1153 | 2 0 2 3 1154 | <_> 1155 | 1156 | 2 1 2 2 1157 | <_> 1158 | 1159 | 2 2 4 6 1160 | <_> 1161 | 1162 | 2 3 4 4 1163 | <_> 1164 | 1165 | 2 7 2 1 1166 | <_> 1167 | 1168 | 2 11 2 3 1169 | <_> 1170 | 1171 | 2 17 3 2 1172 | <_> 1173 | 1174 | 3 0 2 2 1175 | <_> 1176 | 1177 | 3 1 7 3 1178 | <_> 1179 | 1180 | 3 7 2 1 1181 | <_> 1182 | 1183 | 3 7 2 4 1184 | <_> 1185 | 1186 | 3 18 2 2 1187 | <_> 1188 | 1189 | 4 0 2 3 1190 | <_> 1191 | 1192 | 4 3 2 1 1193 | <_> 1194 | 1195 | 4 6 2 1 1196 | <_> 1197 | 1198 | 4 6 2 5 1199 | <_> 1200 | 1201 | 4 7 5 2 1202 | <_> 1203 | 1204 | 4 8 4 3 1205 | <_> 1206 | 1207 | 4 18 2 2 1208 | <_> 1209 | 1210 | 5 0 2 2 1211 | <_> 1212 | 1213 | 5 3 4 4 1214 | <_> 1215 | 1216 | 5 6 2 5 1217 | <_> 1218 | 1219 | 5 9 2 2 1220 | <_> 1221 | 1222 | 5 10 2 2 1223 | <_> 1224 | 1225 | 6 3 4 4 1226 | <_> 1227 | 1228 | 6 4 4 3 1229 | <_> 1230 | 1231 | 6 5 2 3 1232 | <_> 1233 | 1234 | 6 5 2 5 1235 | <_> 1236 | 1237 | 6 5 4 3 1238 | <_> 1239 | 1240 | 6 6 4 2 1241 | <_> 1242 | 1243 | 6 6 4 4 1244 | <_> 1245 | 1246 | 6 18 1 2 1247 | <_> 1248 | 1249 | 6 21 2 1 1250 | <_> 1251 | 1252 | 7 0 3 7 1253 | <_> 1254 | 1255 | 7 4 2 3 1256 | <_> 1257 | 1258 | 7 9 5 1 1259 | <_> 1260 | 1261 | 7 21 2 1 1262 | <_> 1263 | 1264 | 8 0 1 4 1265 | <_> 1266 | 1267 | 8 5 2 2 1268 | <_> 1269 | 1270 | 8 5 3 2 1271 | <_> 1272 | 1273 | 8 17 3 1 1274 | <_> 1275 | 1276 | 8 18 1 2 1277 | <_> 1278 | 1279 | 9 0 5 3 1280 | <_> 1281 | 1282 | 9 2 2 6 1283 | <_> 1284 | 1285 | 9 5 1 1 1286 | <_> 1287 | 1288 | 9 11 1 1 1289 | <_> 1290 | 1291 | 9 16 1 1 1292 | <_> 1293 | 1294 | 9 16 2 1 1295 | <_> 1296 | 1297 | 9 17 1 1 1298 | <_> 1299 | 1300 | 9 18 1 1 1301 | <_> 1302 | 1303 | 10 5 1 2 1304 | <_> 1305 | 1306 | 10 5 3 3 1307 | <_> 1308 | 1309 | 10 7 1 5 1310 | <_> 1311 | 1312 | 10 8 1 1 1313 | <_> 1314 | 1315 | 10 9 1 1 1316 | <_> 1317 | 1318 | 10 10 1 1 1319 | <_> 1320 | 1321 | 10 10 1 2 1322 | <_> 1323 | 1324 | 10 14 3 3 1325 | <_> 1326 | 1327 | 10 15 1 1 1328 | <_> 1329 | 1330 | 10 15 2 1 1331 | <_> 1332 | 1333 | 10 16 1 1 1334 | <_> 1335 | 1336 | 10 16 2 1 1337 | <_> 1338 | 1339 | 10 17 1 1 1340 | <_> 1341 | 1342 | 10 21 1 1 1343 | <_> 1344 | 1345 | 11 3 2 2 1346 | <_> 1347 | 1348 | 11 5 1 2 1349 | <_> 1350 | 1351 | 11 5 3 3 1352 | <_> 1353 | 1354 | 11 5 4 6 1355 | <_> 1356 | 1357 | 11 6 1 1 1358 | <_> 1359 | 1360 | 11 7 2 2 1361 | <_> 1362 | 1363 | 11 8 1 2 1364 | <_> 1365 | 1366 | 11 10 1 1 1367 | <_> 1368 | 1369 | 11 10 1 2 1370 | <_> 1371 | 1372 | 11 15 1 1 1373 | <_> 1374 | 1375 | 11 17 1 1 1376 | <_> 1377 | 1378 | 11 18 1 1 1379 | <_> 1380 | 1381 | 12 0 2 2 1382 | <_> 1383 | 1384 | 12 1 2 5 1385 | <_> 1386 | 1387 | 12 2 4 1 1388 | <_> 1389 | 1390 | 12 3 1 3 1391 | <_> 1392 | 1393 | 12 7 3 4 1394 | <_> 1395 | 1396 | 12 10 3 2 1397 | <_> 1398 | 1399 | 12 11 1 1 1400 | <_> 1401 | 1402 | 12 12 3 2 1403 | <_> 1404 | 1405 | 12 14 4 3 1406 | <_> 1407 | 1408 | 12 17 1 1 1409 | <_> 1410 | 1411 | 12 21 2 1 1412 | <_> 1413 | 1414 | 13 6 2 5 1415 | <_> 1416 | 1417 | 13 7 3 5 1418 | <_> 1419 | 1420 | 13 11 3 2 1421 | <_> 1422 | 1423 | 13 17 2 2 1424 | <_> 1425 | 1426 | 13 17 3 2 1427 | <_> 1428 | 1429 | 13 18 1 2 1430 | <_> 1431 | 1432 | 13 18 2 2 1433 | <_> 1434 | 1435 | 14 0 2 2 1436 | <_> 1437 | 1438 | 14 1 1 3 1439 | <_> 1440 | 1441 | 14 2 3 2 1442 | <_> 1443 | 1444 | 14 7 2 1 1445 | <_> 1446 | 1447 | 14 13 2 1 1448 | <_> 1449 | 1450 | 14 13 3 3 1451 | <_> 1452 | 1453 | 14 17 2 2 1454 | <_> 1455 | 1456 | 15 0 2 2 1457 | <_> 1458 | 1459 | 15 0 2 3 1460 | <_> 1461 | 1462 | 15 4 3 2 1463 | <_> 1464 | 1465 | 15 4 3 6 1466 | <_> 1467 | 1468 | 15 6 3 2 1469 | <_> 1470 | 1471 | 15 11 3 4 1472 | <_> 1473 | 1474 | 15 13 3 2 1475 | <_> 1476 | 1477 | 15 17 2 2 1478 | <_> 1479 | 1480 | 15 17 3 2 1481 | <_> 1482 | 1483 | 16 1 2 3 1484 | <_> 1485 | 1486 | 16 3 2 4 1487 | <_> 1488 | 1489 | 16 6 1 1 1490 | <_> 1491 | 1492 | 16 16 2 2 1493 | <_> 1494 | 1495 | 17 1 2 2 1496 | <_> 1497 | 1498 | 17 1 2 5 1499 | <_> 1500 | 1501 | 17 12 2 2 1502 | <_> 1503 | 1504 | 18 0 2 2 1505 | 1506 | --------------------------------------------------------------------------------