package com.shootoff.camera.shotdetection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.shootoff.camera.CameraManager;
import com.shootoff.camera.CameraView;
import com.shootoff.camera.Shot;
import com.shootoff.camera.shot.BoundsShot;
import com.shootoff.camera.shot.DisplayShot;
import com.shootoff.camera.shot.ShotColor;
import com.shootoff.config.Configuration;
import javafx.geometry.Bounds;
/**
* This interface is implemented by classes that act as the entry point to some
* implementation of shot detection.
*/
public abstract class ShotDetector {
private static final Logger logger = LoggerFactory.getLogger(ShotDetector.class);
private final CameraManager cameraManager;
private final Configuration config = Configuration.getConfig();
private final CameraView cameraView;
public static boolean isSystemSupported() {
return false;
}
public ShotDetector(final CameraManager cameraManager, final CameraView cameraView) {
this.cameraManager = cameraManager;
this.cameraView = cameraView;
}
public void reset() {}
/**
* Notify the shot detector of the dimensions of webcam frames (e.g. the
* webcam's resolution). This method may be called at any time if the
* webcam's resolution is changed at runtime.
*
* @param width
* the width of frames in pixels
* @param height
* the height of frames in pixels
*/
public abstract void setFrameSize(final int width, final int height);
/**
* Alert the canvas tied to the camera the instantiation of this detector is
* tied to of a new shot. This method preprocesses the shot by ensuring it
* is not of an ignored color, it has the appropriate translation for
* projectors if it's a shot on the arena, it has the appropriate
* translation if the display resolution differs from the camera resolution,
* and it is not a duplicate shot.
*
* @param color
* the color of the detected shot (red or green)
* @param x
* the exact x coordinate of the shot in the video frame it was
* detected in
* @param y
* the exact y coordinate of the shot in the video frame it was
* detected in
* @param timestamp
* the timestamp of the shot not adjusted for the shot timer
* @param scaleShot
* <code>true</code> if the shot needs to be scaled if the
* display resolution differs from the webcam's resolution. This
* is always <code>false</code> for click-to-shoot.
* @return <code>true</code> if the shot wasn't rejected during
* preprocessing
*/
public boolean addShot(ShotColor color, double x, double y, long timestamp, boolean scaleShot) {
if (!checkIgnoreColor(color)) return false;
final Shot shot = new Shot(color, x, y, cameraManager.cameraTimeToShotTime(timestamp),
cameraManager.getFrameCount());
if (config.isAdjustingPOI())
{
if (logger.isTraceEnabled())
{
logger.trace("POI Adjustment: x {} y {}", config.getPOIAdjustmentX().get(), config.getPOIAdjustmentY().get());
logger.trace("Adjusting offset via POI setting, coords were {} {} now {} {}", x, y, x+config.getPOIAdjustmentX().get(), y+config.getPOIAdjustmentY().get());
}
shot.adjustPOI(config.getPOIAdjustmentX().get(), config.getPOIAdjustmentY().get());
}
BoundsShot bShot = new BoundsShot(shot);
if (scaleShot && (cameraManager.isLimitingDetectionToProjection() || cameraManager.isCroppingFeedToProjection())
&& cameraManager.getProjectionBounds().isPresent()) {
final Bounds b = cameraManager.getProjectionBounds().get();
if (handlesBounds()) {
bShot.adjustBounds(b.getMinX(), b.getMinY());
} else {
if (cameraManager.isLimitingDetectionToProjection() && !b.contains(x, y)) return false;
}
}
DisplayShot dShot = new DisplayShot(bShot, config.getMarkerRadius());
// If the shot didn't come from click to shoot (cameFromCanvas) and the
// resolution of the display and feed differ, translate shot coordinates
if (scaleShot && (config.getDisplayWidth() != cameraManager.getFeedWidth()
|| config.getDisplayHeight() != cameraManager.getFeedHeight())) {
dShot.setDisplayVals(config.getDisplayWidth(), config.getDisplayHeight(), cameraManager.getFeedWidth(),
cameraManager.getFeedHeight());
}
if (!checkDuplicate(dShot)) return false;
submitShot(dShot);
return true;
}
protected void submitShot(final DisplayShot shot) {
if (logger.isInfoEnabled()) logger.info("Suspected shot accepted: Center ({}, {}), cl {} fr {}", shot.getX(),
shot.getY(), shot.getColor(), cameraManager.getFrameCount());
// Notify of new shot on a non-shot detection thread because most
// training exercises do shot processing on whatever thread submits
// the shot
new Thread(() -> cameraView.addShot(shot, false), "Shot Notifier").start();
}
protected boolean checkDuplicate(final Shot shot) {
if (!cameraManager.getDeduplicationProcessor().processShot(shot)) {
if (logger.isDebugEnabled()) logger.debug("Processing Shot: Shot Rejected By {}",
cameraManager.getDeduplicationProcessor().getClass().getName());
return false;
}
return true;
}
protected boolean checkIgnoreColor(ShotColor color) {
if (config.ignoreLaserColor() && config.getIgnoreLaserColor().isPresent()
&& Shot.colorMap.get(color).equals(config.getIgnoreLaserColor().get())) {
if (logger.isDebugEnabled()) logger.debug("Processing Shot: Shot rejected by ignoreLaserColor {}",
config.getIgnoreLaserColor().get());
return false;
}
return true;
}
/**
*
* @return True if this shot detector only returns shots in bounds and
* offset within the bounds according to the settings in
* CameraManager
*/
protected abstract boolean handlesBounds();
}