Fluxtream Capture Photo Architecture

Overview

Fluxtream Capture is an iOS app for recording heart rate and location data and uploading to Fluxtream.  It also features the ability to upload photos taken either in the app or with the device's built in camera.  This document describes the server-side architecture for supporting Fluxtream Capture photo uploads and views.

Photo Storage

Photos are stored in two places: the original, high-res version is stored in the filesystem, while thumbnail versions of the photo are stored in the database.  The com.fluxtream.connectors.fluxtream_capture.FluxtreamCapturePhotoStore class  handles storage and retrieval of photos.

High Resolution Image

The original, high-res version of each uploaded photo is stored on disk in a key-value store analogous to the datastore (and having the same root directory).  The photo store id is of the form:

<USER_ID>.<DEVICE_NAME>.<CHANNEL_NAME>.<YYYYDDD>.<CAPTURE_TIME_IN_MILLIS>_<SHA-256_HASH_OF_PHOTO_BYTES>

For this implementation, the device is "FluxtreamCapture" and the channel is "photo".  The YYYYDDD field is the 4-digit year and a (zero-padded, if necessary) 3-digit day-of-year.  This maps to a path such as:

/1/FluxtreamCapture/photo/2012347/1355311332000_0dae72b9ee44a61c1a6efe24814cb713026d58fb5af92f91e88cd2a2bf24d41c.val

The  org.bodytrack.datastore.FilesystemKeyValueStore class handles storage and retrieval of photos from the filesystem key-value store.

Thumbnails

We currently generate 2 thumbnails upon upload, one 150px and the other 300px.  Both are stored in the database in the Facet_FluxtreamCapturePhoto table, as are the thumbnail dimensions.  The upload handler also reads the photo's EXIF data, looking for geolocation data and orientation data.  If the EXIF orientation is specified, and not "1", then the thumbnail engine rotates the thumbnail appropriately.  The geolocation and orientation data (if present) are also stored in the database. The motivation for storing thumbnails in the database and the full-res photos in the filesystem is that databases are great for small data like thumbnails, and will do a good job at retrieving contiguous blocks of thumbnails like we'll need for timeline views, but databases perform poorly for megabyte-size data like the original his-res photos.

Upload Handling

The point of entry for photo uploads is the /photoUpload API method which is handled by the handlePhotoUpload() method in com.fluxtream.api.BodyTrackController (for more info, see the BodyTrack server APIs).  That method does some basic error checking, and, if successful, it then ensures that the Fluxtream Capture connector is added to the user's set of connectors.  The user cannot manually add the Fluxtream Capture connector in the Add Connector dialog.  Instead, the connector is added automatically upon first upload. The handlePhotoUpload() method then delegates to the saveOrUpdatePhoto() method in com.fluxtream.connectors.fluxtream_capture.FluxtreamCapturePhotoStore which does the heavy validation and, if everything is valid, inserts the photo into the key-value store and the thumbnails into the database.

The handlePhotoUpload() method returns a javax.ws.rs.core.Response instance which has a JSON body (provided by com.fluxtream.mvc.models.StatusModel) which allows us to set the HTTP status code appropriately for the JSON response.

Example:

curl -v -u username:password --form 'metadata={"capture_time_secs_utc":1355314332.000}' --form photo=@photo.jpg http://localhost:8080/api/bodytrack/photoUpload?connector_name=fluxtream_capture;

Retrieving Photos

There are two API methods for retrieving Fluxtream Capture photos–one for the original, full-res photo and another for the thumbnail:

/api/bodytrack/photo/{PhotoStoreId}

/api/bodytrack/photoThumbnail/{UID}/{PhotoId}/{ThumbnailIndex}

The full-res photos are referenced by the photo's photo store ID.  Thumbnails are referenced by the given photo ID and thumbnail index.  The photo ID for a thumbnail is simply the record ID in the database's photo table.  The thumbnail index is used to specify which thumbnail is desired.  We currently support only two thumbnail sizes: a value of "0" will return the small (150px) thumbnail and "1" will return the large (300px) thumbnail.  Any value in the URL for ThumbnailIndex other than "1" will return the "0" (small) thumbnail.

Examples:

Returns a full-res photo:
http://localhost:8080/api/bodytrack/photo/1.FluxtreamCapture.photo.2012347.1355328332000_30868cb13d29fa0f44703b6222d3e04c0dab27601f175b971af99185ba3a9521

Returns a small thumbnail (150px max size):
http://localhost:8080/api/bodytrack/photoThumbnail/1/31/0

Returns a large thumbnail (300px max size):
http://localhost:8080/api/bodytrack/photoThumbnail/1/31/1