/*
* Artcodes recognises a different marker scheme that allows the
* creation of aesthetically pleasing, even beautiful, codes.
* Copyright (C) 2013-2016 The University of Nottingham
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package uk.ac.horizon.artcodes.detect.marker;
import android.content.Context;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import uk.ac.horizon.artcodes.detect.handler.MarkerDetectionHandler;
import uk.ac.horizon.artcodes.model.Experience;
import uk.ac.horizon.artcodes.process.ImageProcessor;
import uk.ac.horizon.artcodes.process.ImageProcessorFactory;
/**
* <p>This class detects Artcodes with an embedded checksum region.</p>
*/
public class MarkerEmbeddedChecksumDetector extends MarkerDetector
{
public static class Factory implements ImageProcessorFactory
{
public String getName()
{
return "detectEmbedded";
}
public ImageProcessor create(Context context, Experience experience, MarkerDetectionHandler handler, Map<String, String> args)
{
return new MarkerEmbeddedChecksumDetector(experience, handler, args!=null&&args.containsKey("embeddedOnly"), args!=null&&args.containsKey("relaxed"));
}
}
private boolean embeddedChecksumRequired;
private boolean relaxedEmbeddedChecksumIgnoreNonHollowDots;
private boolean relaxedEmbeddedChecksumIgnoreMultipleHollowSegments;
public MarkerEmbeddedChecksumDetector(Experience experience, MarkerDetectionHandler handler, boolean embeddedChecksumRequired, boolean relaxed)
{
super(experience, handler);
this.embeddedChecksumRequired = embeddedChecksumRequired;
this.relaxedEmbeddedChecksumIgnoreNonHollowDots = this.relaxedEmbeddedChecksumIgnoreMultipleHollowSegments = relaxed;
}
protected Marker createMarkerForNode(int nodeIndex, List<MatOfPoint> contours, Mat hierarchy)
{
List<MarkerRegion> regions = null;
MarkerRegion checksumRegion = null;
for (int currentNodeIndex = (int) hierarchy.get(0, nodeIndex)[FIRST_NODE]; currentNodeIndex >= 0; currentNodeIndex = (int) hierarchy.get(0, currentNodeIndex)[NEXT_NODE])
{
final MarkerRegion region = createRegionForNode(currentNodeIndex, contours, hierarchy);
if (region != null)
{
if (this.ignoreEmptyRegions && region.value==0)
{
continue;
}
else if (regions == null)
{
regions = new ArrayList<>();
}
else if (regions.size() >= maxRegions)
{
return null;
}
regions.add(region);
}
else if (checksumRegion == null)
{
checksumRegion = getChecksumRegionAtNode(currentNodeIndex, hierarchy);
if (checksumRegion == null)
{
return null;
}
}
else
{
return null;
}
}
if (regions!=null)
{
Marker marker = new MarkerWithEmbeddedChecksum(nodeIndex, regions, checksumRegion);
sortCode(marker);
if (isValidRegionList(marker))
{
return marker;
}
}
return null;
}
protected MarkerRegion getChecksumRegionAtNode(int regionIndex, Mat hierarchy)
{
// Find the first dot index:
double[] nodes = hierarchy.get(0, regionIndex);
int currentDotIndex = (int) nodes[FIRST_NODE];
if (currentDotIndex < 0)
{
return null; // There are no dots in this region.
}
// Count all the dots and check if they are leaf nodes in the hierarchy:
int dotCount = 0;
while (currentDotIndex >= 0)
{
if (isValidHollowDot(currentDotIndex, hierarchy))
{
dotCount++;
}
else if (!(this.relaxedEmbeddedChecksumIgnoreNonHollowDots && this.isValidDot(currentDotIndex, hierarchy)))
{
return null; // Dot is not a leaf in the hierarchy.
}
// Get next dot node:
nodes = hierarchy.get(0, currentDotIndex);
currentDotIndex = (int) nodes[NEXT_NODE];
}
return new MarkerRegion(regionIndex, dotCount);
}
private boolean isValidHollowDot(int nodeIndex, Mat hierarchy)
{
double[] nodes = hierarchy.get(0, nodeIndex);
return nodes[FIRST_NODE] >= 0 && // has a child node, and
(hierarchy.get(0, (int) nodes[FIRST_NODE])[NEXT_NODE] < 0 || this.relaxedEmbeddedChecksumIgnoreMultipleHollowSegments) && //the child has no siblings, and
isValidDot((int) nodes[FIRST_NODE], hierarchy);// the child is a leaf
}
@Override
protected boolean hasValidChecksum(Marker marker)
{
if (marker instanceof MarkerWithEmbeddedChecksum)
{
// If the detected Artcodes has an embedded checksum validate it
MarkerWithEmbeddedChecksum markerEc = (MarkerWithEmbeddedChecksum) marker;
if (markerEc.checksumRegion != null)
{
// Find weighted sum of code, e.g. 1:1:2:4:4 -> 1*1 + 1*2 + 2*3 + 4*4 + 4*5 = 45
// Although do not use weights/values divisible by 7
// e.g. transform values 1,2,3,4,5,6,7,8, 9,10,11,12,13,14,15... to
// 1,2,3,4,5,6,8,9,10,11,12,13,15,16,17
int embeddedChecksumModValue = 7;
int weightedSum = 0;
int weight = 1;
for (int i = 0; i < markerEc.regions.size(); ++i)
{
int value = markerEc.regions.get(i).value;
value += (value+value/embeddedChecksumModValue)/embeddedChecksumModValue;
if (weight%embeddedChecksumModValue==0)
{
++weight;
}
weightedSum += value * weight++;
}
return markerEc.checksumRegion.value == (weightedSum - 1) % 7 + 1;
}
}
if (!this.embeddedChecksumRequired)
{
// If the detected Artcode does not have an embedded checksum and embedded checksum is
// not required, delegate to super class
return super.hasValidChecksum(marker);
}
else
{
// If the detected Artcode does not have an embedded checksum and embedded checksum is
// required this is not a valid Artcode.
return false;
}
}
}