package org.jactr.modules.pm.visual.scene; /* * default logging */ import java.util.Arrays; import java.util.Collection; import javolution.util.FastList; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.commonreality.agents.IAgent; import org.commonreality.object.manager.IAfferentObjectManager; import org.commonreality.object.manager.event.IAfferentListener; import org.jactr.core.buffer.IActivationBuffer; import org.jactr.core.chunktype.IChunkType; import org.jactr.core.concurrent.ExecutorServices; import org.jactr.core.event.IParameterEvent; import org.jactr.core.extensions.IExtension; import org.jactr.core.model.IModel; import org.jactr.core.model.event.IModelListener; import org.jactr.core.model.event.ModelEvent; import org.jactr.core.model.event.ModelListenerAdaptor; import org.jactr.core.production.request.IRequest; import org.jactr.core.queue.ITimedEvent; import org.jactr.core.queue.timedevents.AbstractTimedEvent; import org.jactr.core.runtime.ACTRRuntime; import org.jactr.core.slot.BasicSlot; import org.jactr.core.slot.IMutableSlot; import org.jactr.core.utils.parameter.ParameterHandler; import org.jactr.modules.pm.common.buffer.AbstractRequestDelegate; import org.jactr.modules.pm.common.event.IPerceptualMemoryModuleEvent; import org.jactr.modules.pm.common.memory.map.IFeatureMap; import org.jactr.modules.pm.visual.IVisualModule; import org.jactr.modules.pm.visual.buffer.IVisualActivationBuffer; import org.jactr.modules.pm.visual.event.IVisualModuleListener; import org.jactr.modules.pm.visual.event.VisualModuleEvent; /** * Provides scene change detection functionality without touching the core * distribution. This adds two new status slots to the visual module * scene-change and scene-change-value. scene-change-value is a proportion of * objects that have changed in the visual scene since the last time the * scene-change mechanism was reset (visual-onset-duration since last trigger, * or explicit reset with +visual> isa clear-scene-change).<br> * <br> * ?visual> scene-change =value will be true or false depending on whether or * not the scene-change-value is greater than SceneChangeThreshold (default * 0.25). <br> * <br> * This is all accomplished by attaching listeners to the visual module, and * either the individual {@link IVisualFeatureMap}s or {@link IAgent}'s * {@link IAfferentObjectManager}, depending upon whether accuracy or speed is * more important. Setting the AcceleratedDetectionEnabled to true will use the * faster ( {@link IAfferentListener} version).<br> * <br> * Injection of the chunktype clear-scene-change is handled by the * {@link SceneChangeParticipant} and the extension point in the bundle * manifest. * * @author harrison */ public class SceneChangeExtension implements IExtension { /** * Logger definition */ static private final transient Log LOGGER = LogFactory .getLog(SceneChangeExtension.class); static final public String CLEAR_CHUNK_TYPE = "clear-scene-change"; static final public String SCENE_CHANGED_SLOT = "scene-change"; static final public String SCENE_CHANGED_VALUE_SLOT = "scene-change-value"; static final public String SCENE_CHANGE_THRESHOLD_PARAM = "SceneChangeThreshold"; static final public String ACCELERATED_DETECTION_PARAM = "AcceleratedDetectionEnabled"; private IVisualModule _visualModule; private IModel _model; private SceneChangeListener _sceneChangeListener; private double _changeThreshold = 0.25; private boolean _useAfferentObjectListener = false; private ITimedEvent _resetTimedEvent; private IMutableSlot _sceneChangeFlagSlot; private IMutableSlot _sceneChangeValueSlot; public IModel getModel() { return _model; } public String getName() { return "SceneChange"; } public void install(IModel model) { if (_model != null) throw new IllegalStateException("Can only be installed for one model"); _model = model; _visualModule = (IVisualModule) _model.getModule(IVisualModule.class); if (_visualModule == null) throw new IllegalStateException( "IVisualModule must be installed for this extension"); /* * when the visual system is reset explicitly, we reset the scene change too */ _visualModule.addListener(new IVisualModuleListener() { public void trackedObjectMoved(VisualModuleEvent event) { // noop } public void trackingObjectStarted(VisualModuleEvent event) { // noop } public void trackingObjectStopped(VisualModuleEvent event) { // noop } public void parameterChanged(IParameterEvent pe) { // noop } public void moduleReset(IPerceptualMemoryModuleEvent event) { reset(); } public void perceptAttended(IPerceptualMemoryModuleEvent event) { // TODO Auto-generated method stub } public void perceptIndexFound(IPerceptualMemoryModuleEvent event) { // TODO Auto-generated method stub } }, ExecutorServices.INLINE_EXECUTOR); /* * we need to be sure the clear-scene-change chunktype is installed. */ IChunkType clearChunkType = null; try { clearChunkType = _model.getDeclarativeModule().getChunkType( CLEAR_CHUNK_TYPE).get(); if (clearChunkType == null) throw new NullPointerException(); } catch (Exception e) { throw new IllegalStateException("Could not get reference to " + CLEAR_CHUNK_TYPE + " chunktype.", e); } _sceneChangeListener = new SceneChangeListener(); /* * we can use the feature map listener in one of two ways.. */ if (!_useAfferentObjectListener) { /* * we attach the listener to all the feature maps and handle the events * inline with the visual processing */ FastList<IFeatureMap> featureMaps = FastList.newInstance(); _visualModule.getVisualMemory().getFeatureMaps(featureMaps); for (IFeatureMap featureMap : featureMaps) featureMap.addListener(_sceneChangeListener, ExecutorServices.INLINE_EXECUTOR); FastList.recycle(featureMaps); } /* * but regardless, we need a model listener so that we can check for changes * at the top of each cycle. If we are using the afferent object listener, * we will also install it on connect */ IModelListener installer = new ModelListenerAdaptor() { /** * at the top of each cycle, we check for scene change * * @param event * @see org.jactr.core.model.event.ModelListenerAdaptor#cycleStarted(org.jactr.core.model.event.ModelEvent) */ @Override public void cycleStarted(ModelEvent event) { checkForChange(); } @Override public void modelConnected(ModelEvent event) { if (_useAfferentObjectListener) { /** * in order to listen to afferent objects, we need to attach to * IAgent, which isn't available until after the model has connected * to common reality.. so, we need to defer install/uninstall until a * specific model event */ IAgent agent = ACTRRuntime.getRuntime().getConnector().getAgent( _model); /* * attach the listener to the afferent object manager. we use the CR * executor because the object manager operates on the IO thread, * which we should never sit on. */ agent.getAfferentObjectManager().addListener(_sceneChangeListener, _visualModule.getCommonRealityExecutor()); } } @Override public void modelDisconnected(ModelEvent event) { // no need to remove the feature listener as the agent will be disposed // and remove ourselves _model.removeListener(this); } }; // install the listener and we need to be notified immediately _model.addListener(installer, ExecutorServices.INLINE_EXECUTOR); /* * now we need to add a status slot for scene-change so that queries will * work */ IVisualActivationBuffer buffer = _visualModule.getVisualActivationBuffer(); buffer.addSlot(new BasicSlot(SCENE_CHANGED_SLOT, false)); buffer.addSlot(new BasicSlot(SCENE_CHANGED_VALUE_SLOT, 0.0)); /* * addSlot should probably return the actual slot added, but instead we have * to do this.. we are snagging the persistent slots so that we don't have * to query the buffer during updates */ _sceneChangeFlagSlot = (IMutableSlot) buffer.getSlot(SCENE_CHANGED_SLOT); _sceneChangeValueSlot = (IMutableSlot) buffer .getSlot(SCENE_CHANGED_VALUE_SLOT); /* * now we need to add a request delegate to deal with the explicit reset */ AbstractRequestDelegate delegate = new AbstractRequestDelegate( clearChunkType) { @Override protected Object startRequest(IRequest request, IActivationBuffer buffer, double requestTime) { // Noop return null; } @Override protected void finishRequest(IRequest request, IActivationBuffer buffer, Object startValue) { reset(); } @Override protected boolean isValid(IRequest request, IActivationBuffer buffer) throws IllegalArgumentException { return true; } }; buffer.addRequestDelegate(delegate); /* * finally, we need to insert a listener so that */ } public void uninstall(IModel model) { if (_model != model) return; /* * if we didn't install like this, it doesn't matter, removing a * non-existant listener doesn't do anything. */ FastList<IFeatureMap> featureMaps = FastList.newInstance(); _visualModule.getVisualMemory().getFeatureMaps(featureMaps); for (IFeatureMap featureMap : featureMaps) featureMap.removeListener(_sceneChangeListener); FastList.recycle(featureMaps); _model = null; _visualModule = null; _sceneChangeListener = null; } /** * @throws Exception * @see org.jactr.core.utils.IInitializable#initialize() */ public void initialize() throws Exception { reset(); } /** * check to see if we should set the scene-change flag */ protected void checkForChange() { double changeRatio = _sceneChangeListener.check(); _sceneChangeValueSlot.setValue(changeRatio); /* * if the ratio exceeds threshold, and hasn't already, set the flag */ if (_resetTimedEvent == null && changeRatio >= getSceneChangeThreshold()) { _sceneChangeFlagSlot.setValue(Boolean.TRUE); /* * queue up the reset. We want to reset the scene-changed flag after * visual onset duration. */ double now = ACTRRuntime.getRuntime().getClock(_model).getTime(); double resetTime = _visualModule.getVisualMemory().getNewFINSTOnsetDuration(); _resetTimedEvent = new AbstractTimedEvent(now, now + resetTime) { @Override public void fire(double currentTime) { super.fire(currentTime); clearFlag(); } @Override public void abort() { super.abort(); clearFlag(); } }; // queue it up _model.getTimedEventQueue().enqueue(_resetTimedEvent); } } private void clearFlag() { _sceneChangeFlagSlot.setValue(Boolean.FALSE); _sceneChangeValueSlot.setValue(0.0); _sceneChangeListener.reset(); _resetTimedEvent = null; } /** * explicitly reset the scene-change flag, regardless of whether or not its * been triggered */ protected void reset() { if (_resetTimedEvent != null && !_resetTimedEvent.hasAborted() && !_resetTimedEvent.hasFired()) _resetTimedEvent.abort(); clearFlag(); } public double getSceneChangeThreshold() { return _changeThreshold; } public void setSceneChangeThreshold(double threshold) { _changeThreshold = threshold; } public String getParameter(String key) { if (ACCELERATED_DETECTION_PARAM.equalsIgnoreCase(key)) return "" + _useAfferentObjectListener; if (SCENE_CHANGE_THRESHOLD_PARAM.equalsIgnoreCase(key)) return "" + getSceneChangeThreshold(); return null; } public Collection<String> getPossibleParameters() { return Arrays.asList(SCENE_CHANGE_THRESHOLD_PARAM, ACCELERATED_DETECTION_PARAM); } public Collection<String> getSetableParameters() { return getPossibleParameters(); } public void setParameter(String key, String value) { if (ACCELERATED_DETECTION_PARAM.equalsIgnoreCase(key)) _useAfferentObjectListener = ParameterHandler.booleanInstance().coerce( value); else if (SCENE_CHANGE_THRESHOLD_PARAM.equalsIgnoreCase(key)) setSceneChangeThreshold(ParameterHandler.numberInstance().coerce(value) .doubleValue()); } }