diff --git a/lib/build.gradle b/lib/build.gradle index e3700ab1..6a7b4528 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'maven-publish' apply plugin: 'com.jfrog.bintray' group = "io.ona.rdt-capture" -version = '2.0.0' +version = '2.1.0' def home = System.properties['user.home'] android { diff --git a/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/activities/ImageQualityActivity.java b/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/activities/ImageQualityActivity.java index e8db9d78..9d25c71b 100644 --- a/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/activities/ImageQualityActivity.java +++ b/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/activities/ImageQualityActivity.java @@ -4,6 +4,9 @@ import android.content.Intent; import android.os.Bundle; +import org.json.JSONException; +import org.json.JSONObject; + import edu.washington.cs.ubicomplab.rdt_reader.R; import edu.washington.cs.ubicomplab.rdt_reader.core.RDTCaptureResult; import edu.washington.cs.ubicomplab.rdt_reader.core.RDTInterpretationResult; @@ -12,6 +15,7 @@ import edu.washington.cs.ubicomplab.rdt_reader.views.ImageQualityView; import static edu.washington.cs.ubicomplab.rdt_reader.core.Constants.DEFAULT_RDT_NAME; +import static edu.washington.cs.ubicomplab.rdt_reader.core.Constants.RDT_JSON_CONFIG; /** * The {@link android.app.Activity} for showing a real-time camera feed during image capture and @@ -43,6 +47,16 @@ protected void onCreate(Bundle savedInstanceState) { } else { mImageQualityView.setRDTName(DEFAULT_RDT_NAME); } + + try { + // Extract config json obj + String rdtConfig = b.getString(RDT_JSON_CONFIG, null); + if (rdtConfig != null) { + mImageQualityView.setRdtJsonConfig(new JSONObject(rdtConfig)); + } + } catch (JSONException e) { + e.printStackTrace(); + } } /** diff --git a/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/core/Constants.java b/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/core/Constants.java index 12668b91..1be0831b 100644 --- a/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/core/Constants.java +++ b/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/core/Constants.java @@ -81,4 +81,6 @@ public final class Constants { public static final int REQUEST_CAMERA_PERMISSION = 1; public static String SAVED_IMAGE_FILE_PATH = "saved_image_file_path"; public static String SAVED_IMAGE_RESULT = "saved_image_result"; + + public static String RDT_JSON_CONFIG = "rdt_json_config"; } diff --git a/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/core/ImageProcessor.java b/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/core/ImageProcessor.java index 827e79e0..02468da4 100644 --- a/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/core/ImageProcessor.java +++ b/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/core/ImageProcessor.java @@ -12,6 +12,7 @@ import android.content.Context; import android.util.Log; +import org.json.JSONObject; import org.opencv.android.BaseLoaderCallback; import org.opencv.android.LoaderCallbackInterface; import org.opencv.android.OpenCVLoader; @@ -127,12 +128,17 @@ public ImageProcessor(Activity activity, String rdtName) { Log.d(TAG, "REFERENCE LOAD/DETECT/COMPUTE: " + (System.currentTimeMillis() - startTime)); } - /** - * Singleton creation method for this class - * @param activity: the activity that is using this code - * @param rdtName: the name of the target RDT - * @return an instance of this class if one already exists, otherwise creates a new one - */ + private ImageProcessor(JSONObject rdtConfig) { + mRDT = new RDT(rdtConfig); + mRDT.refImgSharpness = measureSharpness(mRDT.refImg); + } + + public static ImageProcessor getInstance(JSONObject rdtConfig) { + if (instance == null) + instance = new ImageProcessor(rdtConfig); + return instance; + } + public static ImageProcessor getInstance(Activity activity, String rdtName) { if (instance == null) instance = new ImageProcessor(activity, rdtName); diff --git a/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/core/RDT.java b/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/core/RDT.java index d6689352..d95ae499 100644 --- a/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/core/RDT.java +++ b/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/core/RDT.java @@ -1,9 +1,9 @@ package edu.washington.cs.ubicomplab.rdt_reader.core; -import android.app.ListActivity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.util.Base64; import org.json.JSONArray; import org.json.JSONException; @@ -60,6 +60,15 @@ public class RDT { public boolean rotated = false; + + public RDT(JSONObject rdtConfig) { + try { + init(rdtConfig, convertBase64StrToBitmap(rdtConfig.optString("REF_IMG"))); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + public RDT(Context context, String rdtName) { try { // Read config.json @@ -69,89 +78,106 @@ public RDT(Context context, String rdtName) { is.read(buffer); is.close(); JSONObject obj = new JSONObject(new String(buffer, "UTF-8")).getJSONObject(rdtName); - - // Load the template image refImageID = context.getResources().getIdentifier(obj.getString("REF_IMG"), "drawable", context.getPackageName()); - refImg = new Mat(); Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), refImageID); - Utils.bitmapToMat(bitmap, refImg); - - if(refImg.height() > refImg.width()) { - Core.rotate(refImg, refImg, Core.ROTATE_90_COUNTERCLOCKWISE); - rotated = true; - } - - cvtColor(refImg, refImg, Imgproc.COLOR_RGB2GRAY); - this.rdtName = rdtName; - - // Pull data related to UI - viewFinderScaleH = obj.getDouble("VIEW_FINDER_SCALE"); - viewFinderScaleW = (viewFinderScaleH * (double)refImg.height()/(double)refImg.width())+Constants.VIEW_FINDER_SCALE_W_PADDING; - //viewFinderScaleW = obj.getDouble("VIEW_FINDER_SCALE_W"); - JSONArray rectTL = obj.getJSONArray("RESULT_WINDOW_TOP_LEFT"); - JSONArray rectBR = obj.getJSONArray("RESULT_WINDOW_BOTTOM_RIGHT"); - resultWindowRect = rotated ? new Rect(new Point(rectTL.getDouble(1), rectTL.getDouble(0)), - new Point(rectBR.getDouble(1), rectBR.getDouble(0))) : - new Rect(new Point(rectTL.getDouble(0), rectTL.getDouble(1)), - new Point(rectBR.getDouble(0), rectBR.getDouble(1))); - - // Pull data related to the result window - topLinePosition = rotated ? obj.getJSONArray("TOP_LINE_POSITION").getDouble(1) - resultWindowRect.x : obj.getJSONArray("TOP_LINE_POSITION").getDouble(0) - resultWindowRect.x; - middleLinePosition = rotated ? obj.getJSONArray("MIDDLE_LINE_POSITION").getDouble(1) - resultWindowRect.x: obj.getJSONArray("MIDDLE_LINE_POSITION").getDouble(0) - resultWindowRect.x; - bottomLinePosition = rotated ? obj.getJSONArray("BOTTOM_LINE_POSITION").getDouble(1) - resultWindowRect.x: obj.getJSONArray("BOTTOM_LINE_POSITION").getDouble(0) - resultWindowRect.x; - topLineName = obj.getString("TOP_LINE_NAME"); - middleLineName = obj.getString("MIDDLE_LINE_NAME"); - bottomLineName = obj.getString("BOTTOM_LINE_NAME"); - lineIntensity = obj.getInt("LINE_INTENSITY"); - lineSearchWidth = obj.has("LINE_SEARCH_WIDTH") ? obj.getInt("LINE_SEARCH_WIDTH") : - Math.max((int)((middleLinePosition-topLinePosition)/2.0),(int)((bottomLinePosition-middleLinePosition)/2.0)); - - checkGlare = obj.has("CHECK_GLARE") ? obj.getBoolean("CHECK_GLARE") : false; - - // Pull data related to fiducials - fiducials = obj.has("FIDUCIALS") ? obj.getJSONArray("FIDUCIALS") : new JSONArray(); - hasFiducial = fiducials.length() > 0; - distanctFromFiducialToResultWindow = 0; - - if (hasFiducial && fiducials.length() == 2) { - JSONArray trueFiducial1 = fiducials.getJSONArray(0); - Point trueFiducialTL1 = rotated - ? new Point(trueFiducial1.getJSONArray(0).getDouble(1), trueFiducial1.getJSONArray(0).getDouble(0)) - : new Point(trueFiducial1.getJSONArray(0).getDouble(0), trueFiducial1.getJSONArray(0).getDouble(1)); - Point trueFiducialBR1 = rotated - ? new Point(trueFiducial1.getJSONArray(1).getDouble(1), trueFiducial1.getJSONArray(1).getDouble(0)) - : new Point(trueFiducial1.getJSONArray(1).getDouble(0), trueFiducial1.getJSONArray(1).getDouble(1)); - - JSONArray trueFiducial2 = fiducials.getJSONArray(1); - Point trueFiducialTL2 = rotated - ? new Point(trueFiducial2.getJSONArray(0).getDouble(1), trueFiducial2.getJSONArray(0).getDouble(0)) - : new Point(trueFiducial2.getJSONArray(0).getDouble(0), trueFiducial2.getJSONArray(0).getDouble(1)); - Point trueFiducialBR2 = rotated - ? new Point(trueFiducial2.getJSONArray(1).getDouble(1), trueFiducial2.getJSONArray(1).getDouble(0)) - : new Point(trueFiducial2.getJSONArray(1).getDouble(0), trueFiducial2.getJSONArray(1).getDouble(1)); - - fiducialRects = new ArrayList<>(); - - fiducialRects.add(new Rect(trueFiducialTL1, trueFiducialBR1)); - fiducialRects.add(new Rect(trueFiducialTL2, trueFiducialBR2)); - - distanctFromFiducialToResultWindow = resultWindowRect.x - (trueFiducialBR2.x + trueFiducialBR1.x)/2.0; - } - - // Store the reference's sharpness - Size kernel = new Size(SHARPNESS_GAUSSIAN_BLUR_WINDOW, - SHARPNESS_GAUSSIAN_BLUR_WINDOW); - Imgproc.GaussianBlur(refImg, refImg, kernel, 0, 0); - - // Load the reference image's features - refDescriptor = new Mat(); - refKeypoints = new MatOfKeyPoint(); - detector = SIFT.create(); - matcher = BFMatcher.create(BFMatcher.BRUTEFORCE, false); - detector.detectAndCompute(refImg, new Mat(), refKeypoints, refDescriptor); + init(obj, bitmap); } catch (Exception ex) { ex.printStackTrace(); } } + + private Bitmap convertBase64StrToBitmap(String base64Str) { + return convertByteArrayToBitmap(Base64.decode(base64Str.getBytes(), Base64.DEFAULT)); + } + + private Bitmap convertByteArrayToBitmap(final byte[] src) { + return BitmapFactory.decodeByteArray(src, 0, src.length); + } + + private void init(JSONObject obj, Bitmap bitmap) throws JSONException { + // Load the template image + refImg = new Mat(); + Utils.bitmapToMat(bitmap, refImg); + + if(refImg.height() > refImg.width()) { + Core.rotate(refImg, refImg, Core.ROTATE_90_COUNTERCLOCKWISE); + rotated = true; + } + + cvtColor(refImg, refImg, Imgproc.COLOR_RGB2GRAY); + this.rdtName = rdtName; + + // Pull data related to UI + viewFinderScaleH = obj.getDouble("VIEW_FINDER_SCALE"); + viewFinderScaleW = (viewFinderScaleH * (double)refImg.height()/(double)refImg.width())+Constants.VIEW_FINDER_SCALE_W_PADDING; + //viewFinderScaleW = obj.getDouble("VIEW_FINDER_SCALE_W"); + JSONArray rectTL = obj.getJSONArray("RESULT_WINDOW_TOP_LEFT"); + JSONArray rectBR = obj.getJSONArray("RESULT_WINDOW_BOTTOM_RIGHT"); + resultWindowRect = rotated ? new Rect(new Point(rectTL.getDouble(1), rectTL.getDouble(0)), + new Point(rectBR.getDouble(1), rectBR.getDouble(0))) : + new Rect(new Point(rectTL.getDouble(0), rectTL.getDouble(1)), + new Point(rectBR.getDouble(0), rectBR.getDouble(1))); + + // Pull data related to the result window + topLinePosition = rotated ? obj.getJSONArray("TOP_LINE_POSITION").getDouble(1) - resultWindowRect.x : obj.getJSONArray("TOP_LINE_POSITION").getDouble(0) - resultWindowRect.x; + middleLinePosition = rotated ? obj.getJSONArray("MIDDLE_LINE_POSITION").getDouble(1) - resultWindowRect.x: obj.getJSONArray("MIDDLE_LINE_POSITION").getDouble(0) - resultWindowRect.x; + bottomLinePosition = getBottomLinePosition(obj, rotated); + topLineName = obj.getString("TOP_LINE_NAME"); + middleLineName = obj.getString("MIDDLE_LINE_NAME"); + bottomLineName = obj.optString("BOTTOM_LINE_NAME"); + lineIntensity = obj.getInt("LINE_INTENSITY"); + lineSearchWidth = obj.has("LINE_SEARCH_WIDTH") ? obj.getInt("LINE_SEARCH_WIDTH") : + Math.max((int)((middleLinePosition-topLinePosition)/2.0),(int)((bottomLinePosition-middleLinePosition)/2.0)); + + checkGlare = obj.has("CHECK_GLARE") ? obj.getBoolean("CHECK_GLARE") : false; + + // Pull data related to fiducials + fiducials = obj.has("FIDUCIALS") ? obj.getJSONArray("FIDUCIALS") : new JSONArray(); + hasFiducial = fiducials.length() > 0; + distanctFromFiducialToResultWindow = 0; + + if (hasFiducial && fiducials.length() == 2) { + JSONArray trueFiducial1 = fiducials.getJSONArray(0); + Point trueFiducialTL1 = rotated + ? new Point(trueFiducial1.getJSONArray(0).getDouble(1), trueFiducial1.getJSONArray(0).getDouble(0)) + : new Point(trueFiducial1.getJSONArray(0).getDouble(0), trueFiducial1.getJSONArray(0).getDouble(1)); + Point trueFiducialBR1 = rotated + ? new Point(trueFiducial1.getJSONArray(1).getDouble(1), trueFiducial1.getJSONArray(1).getDouble(0)) + : new Point(trueFiducial1.getJSONArray(1).getDouble(0), trueFiducial1.getJSONArray(1).getDouble(1)); + + JSONArray trueFiducial2 = fiducials.getJSONArray(1); + Point trueFiducialTL2 = rotated + ? new Point(trueFiducial2.getJSONArray(0).getDouble(1), trueFiducial2.getJSONArray(0).getDouble(0)) + : new Point(trueFiducial2.getJSONArray(0).getDouble(0), trueFiducial2.getJSONArray(0).getDouble(1)); + Point trueFiducialBR2 = rotated + ? new Point(trueFiducial2.getJSONArray(1).getDouble(1), trueFiducial2.getJSONArray(1).getDouble(0)) + : new Point(trueFiducial2.getJSONArray(1).getDouble(0), trueFiducial2.getJSONArray(1).getDouble(1)); + + fiducialRects = new ArrayList<>(); + + fiducialRects.add(new Rect(trueFiducialTL1, trueFiducialBR1)); + fiducialRects.add(new Rect(trueFiducialTL2, trueFiducialBR2)); + + distanctFromFiducialToResultWindow = resultWindowRect.x - (trueFiducialBR2.x + trueFiducialBR1.x)/2.0; + } + + // Store the reference's sharpness + Size kernel = new Size(SHARPNESS_GAUSSIAN_BLUR_WINDOW, + SHARPNESS_GAUSSIAN_BLUR_WINDOW); + Imgproc.GaussianBlur(refImg, refImg, kernel, 0, 0); + + // Load the reference image's features + refDescriptor = new Mat(); + refKeypoints = new MatOfKeyPoint(); + detector = SIFT.create(); + matcher = BFMatcher.create(BFMatcher.BRUTEFORCE, false); + detector.detectAndCompute(refImg, new Mat(), refKeypoints, refDescriptor); + } + + private double getBottomLinePosition(JSONObject rdtConfig, boolean rotated) throws JSONException { + return rdtConfig.optJSONArray("BOTTOM_LINE_POSITION") == null ? 0 + : rotated ? rdtConfig.getJSONArray("BOTTOM_LINE_POSITION").getDouble(1) - resultWindowRect.x + : rdtConfig.getJSONArray("BOTTOM_LINE_POSITION").getDouble(0) - resultWindowRect.x; + } } \ No newline at end of file diff --git a/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/views/ImageQualityView.java b/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/views/ImageQualityView.java index dc575c95..aa7f1bc4 100644 --- a/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/views/ImageQualityView.java +++ b/lib/src/main/java/edu/washington/cs/ubicomplab/rdt_reader/views/ImageQualityView.java @@ -52,6 +52,7 @@ import android.widget.TextView; import android.widget.Toast; +import org.json.JSONObject; import org.opencv.android.BaseLoaderCallback; import org.opencv.android.LoaderCallbackInterface; import org.opencv.core.Mat; @@ -96,6 +97,7 @@ public class ImageQualityView extends LinearLayout implements View.OnClickListen private boolean showViewport; private boolean showFeedback; private AutoFitTextureView mTextureView; + private JSONObject rdtJsonConfig; // Image processing variables private ImageProcessor processor; @@ -258,7 +260,8 @@ public void onManagerConnected(int status) { switch (status) { case LoaderCallbackInterface.SUCCESS: { Log.i(TAG, "OpenCV loaded successfully"); - processor = ImageProcessor.getInstance(mActivity, rdtName); + processor = getRdtJsonConfig() == null ? ImageProcessor.getInstance(mActivity, rdtName) + : ImageProcessor.getInstance(getRdtJsonConfig()); ViewportUsingBitmap viewport = findViewById(R.id.img_quality_check_viewport); viewport.hScale = (float) processor.mRDT.viewFinderScaleH; viewport.wScale = (float) processor.mRDT.viewFinderScaleW; @@ -1060,4 +1063,12 @@ private boolean continueProcessingImg(Image image) { } return true; } + + public JSONObject getRdtJsonConfig() { + return rdtJsonConfig; + } + + public void setRdtJsonConfig(JSONObject rdtJsonConfig) { + this.rdtJsonConfig = rdtJsonConfig; + } }