This document serves as an overview of the anatomy of an App Inventor TensorFlow.JS extension. The extension can be viewed primarily as two key parts: app.js, the JavaScript portion responsible for model setup and evaluation; and TensorflowTemplate.java, the Java side that App Inventor users interact with to build an app that serves as a bridge between the blocks language and the sandboxed JavaScript environment of app.js.
app.js represents the bulk of the model setup, invocation, and processing. As it is currently architected, the model evaluation is done in runClassifier
, with the remainder of the functions used for setup, configuration, and teardown.
setupCamera
- Configures the browser's camera for videoloadVideo
- Loads and starts the video streamrunClassifier
- Starts running the neural network on the video streamclassifyFrame
- Classifies a single frame of the stream and schedules itself for the next animation frame
loadModel
- Loads the neural network modelstartVideo
- Runs the model by setting up the video stream and then running the classifierstopVideo
- Stops running the modelsetCameraFacingMode
- Sets whether to use the front facing or rear facing camera
The code at the very end of the file is where the work is initiated:
loadModel().then(model => {
net = model;
TensorflowTemplate.ready();
});
This loads the model and then signals the extension's Ready
event.
Note: TensorflowTemplate
is a placeholder. When the extension is created by appinventor-tfjs, it is replaced with the name of the extension class, e.g., LookExtension
.
The TensorflowTemplate.java file serves as the base of the App Inventor extension. It is the bridge between the blocks language/YAIL and the JavaScript environment where the model evaluation occurs. It provides implementations for WebView interfaces to intercept and process requests from the JavaScript side of the extension to keep the model encapsulated in the extension. Lastly, it provides the API for the JavaScript code to raise events in the blocks language so they can be handled by the app.
Initialize
- Called by runtime.scm after the component has been created and configured, sets a flag to allow the rest of the functionality in the extension to work- Properties
Enabled
- Sets whether to run the model or not (default: True)WebViewer
- Sets the WebViewer component, the WebView of which will be used to run the modelUseCamera
- Sets whether to use the front facing or back facing camera
- Methods
- N/A
- Events
ModelReady
- Runs when the model has been loaded in the WebView and is ready to runError
- Runs when an error occurs in the JavaScriptGotResult
- Runs when the model has results to report to the user
ready
- Called from JS once the TFJS model has loadedreportResult
- Called from JS when the TFJS model has results to reporterror
- Called from JS when an error occurs
- WebView setup
configureWebView
- Sets up the WebView associated with a WebViewer component to support the TFJS extensionrequestHardwareAcceleration
- Asks the Android system for hardware acceleration supportassertWebView
- Asserts the presence of a configured WebView and throws an IllegalStateException if not properly configured
- Activity lifecycle management
onDelete
- Tears down the TFJS extension infrastructure when the component is deletedonPause
- Pauses the camera in the WebViewonResume
- Resumes the camera in the WebViewonStop
- Tears down the TFJS extension infrastructure when the Activity is stopped
This example assumes a LookExtension created with the Mobilenet TFJFS model. The numbering of the event loops is to representing the ordering of events. There may be other events that happen between these iterations of the event loop, but they are not relevant to the execution of the extension. Identation roughly correspondes to call stack depth.
- Components are created (WebViewer1, LookExtension1)
- LookExtension1 is configured with the following properties:
- Enabled = True
- UseCamera = FRONT_CAMERA
- WebViewer = WebViewer1
- WebViewer1's view (WebView) is set up via
configureWebView
and the loading of the extension's index.html is initiated.
- WebViewer1's view (WebView) is set up via
- Initialize is called on LookExtension1
- Initialize is called on Screen1
- WebView loads index.html and its corresponding JS files
loadModel
initiates asynchronous loading of the mobilenet model
- Model finishes loading and calls back on the
AppInventorTFJS.ready
method, queuing the event
ModelReady
event is run in blocks- If
Enabled
is True,UseCamera
is called with the configured camerasetCameraFacingMode
is called with a flag indicating the direction of camera desiredsetCameraFacingMode
sets theforwardCamera
flag and queues a request tostartVideo
on the next animation frame
startVideo
runs and attempts to get access to the video device- If the user has already granted permission previous, jump to Seventh event loop
- Otherwise, a callback is made to
WebChromeClient.onPermissionRequest
(defined inconfigureWebView
), which prompts the user for CAMERA permission
- If the user denies permission, a PermissionDenied event is raised on Screen1 (END EXECUTION)
- Otherwise, the PermissionRequest callback is invoked to inform the WebView the permission has been granted
- The
video
object in the WebView is configured and aclassifyFrame
invocation of thenet
using thevideo
object is queued for the next frame
- The
net
is used to classify the currentvideo
frame AppInventorTFJS.reportResult
is invoked from the JS side to report the results to the Java side passing a JSON representation of the result- The results are parsed on the Java side of the bridge
- The
GotResult
event is queued for the next run loop
classifyFrame
is queued for the next frame
- The
GotResult
event is run in the blocks with the results of thenet
classification