Rank One Computing is excited to announce the availability of a new “ROC Web API” offering!

The ROC Web API differs from solutions by Microsoft or Amazon, which require users to upload face images to their servers with little-to-no transparency as to how those images are processed. Instead, the ROC Web API enables integrators to host their own Web service on hardware that they control. Thus, Rank One is able to provide this Web API offering without ever receiving access to the face imagery processed by the system. ROC integrators can configure a wide range of system workflows while ensuring that their users can fully explain where their images are (and are not) being transmitted and stored.

Technical Details

The ROC Web API interface facilitates the development of applications where computation happens remotely (server-side). Client-side applications need only integrate a single source file that defines the communication protocol, and do not need a license to the ROC SDK.

Currently in “beta”, the Web API consists of a provided server-side application for translating HTTP requests into ROC SDK function calls, and client-side single-source-file definitions of the communication protocol for C++, C#, Java, JavaScript, Objective-C, PHP, Python, and Ruby. The primary use cases supported by the Web API are template generation, 1:1 comparison, gallery enrollment, and 1:N search.

Developers familiar with our long-standing “remote galleries” feature will find that the Web API utilizes the same underlying software stack, but with the communication protocol exposed via ProtoBuf and messages wrapped in HTTP. The current implementation adheres to the following principle: one server process equals one gallery file equals one network port. Thus multiple gallery files are cleanly enabled by hosting separate server processes on different network ports.

Rank One developed proof-of-concept Web APIs using JSON RPC 2.0, FlatBuffers and Protocol Buffers, before ultimately settling on ProtoBuf due to the following considerations. While supported everywhere, JSON objects are inefficient for transmitting images, and their lack of a well-defined schema makes it difficult to document an API. On the opposite end of the spectrum, FlatBuffers are extremely efficient, but the asymmetry between how objects are read versus written makes them cumbersome for client-side applications using the output of one API call as the input to another (as one often wants to do). Protocol Buffers were “just right”, offering a well-defined schema that is both clean and efficient.

The Protobuf protocol implementation consists of two root message types: request and response. The root message types have subtypes that mirror each ROC SDK function call. For example, roc_at() decomposes into Request.At which expects a template index, and Response.At which contains a template.

Code Examples

We will now provide several examples illustrating how the client communicates with the server.

First, the server is initialized as follows:

$ ./roc-represent -k 5 --thumbnail ../data/roc.jpg roc.t
$ ./roc-serve 8080 --gallery roc.t --http --log-stdout

Server-side, the above commands construct a gallery of faces from a single image and then start the Web API on port 8080. Client devices can in turn communicate with the web server in C++, C#, Java, JavaScript, Objective-C, PHP, Python, or Ruby.

The following code snippets for Python, Java, and JavaScript demonstrate how to establish a connection with the Web API server, generate templates from face images (or video frames), and search those templates against a gallery.

Connecting with the Server

Python

def rpc(request):
    server = httplib.HTTPConnection(serverURL)
    server.request("POST", "/", request.SerializeToString(), { "Content-Type" : "application/octet-stream" })
    response = roc.Response()
    response.ParseFromString(server.getresponse().read())
    return response

Java

public static Roc.Response rpc(Roc.Request request) throws IOException {
    final URL url = new URL(serverUrl);
    HttpURLConnection con = (HttpURLConnection) url.openConnection();
    con.setRequestMethod("POST");
    con.setRequestProperty("Content-Type", "application/octet-stream");
    con.setDoOutput(true);
    con.getOutputStream().write(request.toByteArray());
   if (con.getResponseCode() ==  HttpURLConnection.HTTP_OK) {
       Roc.Response response = Roc.Response.parseFrom(con.getInputStream());
       if (response.hasError())
           throw new IOException(response.getError().getError());
       return response;
   } else {
       throw new IOException(String.valueOf(con.getResponseCode()));
   }
}

Template Generation

Python

def represent(imageFile):
    request = roc.Request()
    with open(imageFile, "r") as f:
        request.represent.image = f.read()
    request.represent.algorithm_id = roc.AlgorithmOptions.ROC_FRONTAL | roc.AlgorithmOptions.ROC_FR | roc.AlgorithmOptions.ROC_THUMBNAIL
    request.represent.min_size = 20
    request.represent.k = -1
    request.represent.false_detection_rate = 0.02
    request.represent.min_quality = -4
    response = rpc(request)
    return response.represent.templates

Java

public static java.util.List represent(String imageFile) throws IOException {
    final ByteString image = ByteString.readFrom(new FileInputStream(imageFile));
    final Roc.Request request = Roc.Request.newBuilder().setRepresent(Roc.Request.Represent.newBuilder()
                                    .setImage(image)
                                    .setAlgorithmIdValue(Roc.AlgorithmOptions.ROC_FRONTAL_VALUE | Roc.AlgorithmOptions.ROC_FR_VALUE | Roc.AlgorithmOptions.ROC_THUMBNAIL_VALUE)
                                    .setMinSize(20)
                                    .setK(-1)
                                    .setFalseDetectionRate(0.02f)
                                    .setMinQuality(-4.f)
                                    .build()
                                ).build();
    final Roc.Response response = rpc(request);
    return response.getRepresent().getTemplatesList();
}

Template Search

Python

def search(probeTemplate):
    request = roc.Request()
    request.search.probe.CopyFrom(probeTemplate)
    request.search.k = 3
    request.search.min_similarity = 0
    response = rpc(request)
    return response.search.candidates

Java

public static java.util.List search(Roc.Template probeTemplate) throws IOException {
    final Roc.Request request = Roc.Request.newBuilder().setSearch(Roc.Request.Search.newBuilder()
                                    .setProbe(probeTemplate)
                                    .setK(3)
                                    .setMinSimilarity(0.f)
                                    .build()
                                ).build();
    final Roc.Response response = rpc(request);
    return response.getSearch().getCandidatesList();
}

Complete HTML+JavaScript Example

Let’s now take a look at an example of the Web API using HTML+JavaScript to create a webpage that enables a user to execute a face recognition search

<!DOCTYPE HTML>
<html>
<body>
  <script_ src="js-browserify/roc.js"></script_>

<script_>
function rpc(requestProto, responseHandler) {
  const xhr = new XMLHttpRequest()
  xhr.responseType = "arraybuffer";
  xhr.onreadystatechange = function() {
    if (xhr.readyState == XMLHttpRequest.DONE) {
      if (xhr.response.byteLength == 0) {
        responseHandler(null, "No response from server!")
      } else {
        const response = proto.roc.Response.deserializeBinary(new Uint8Array(xhr.response))
        if (response.getResponsesCase() == proto.roc.Response.ResponsesCase.RESPONSES_NOT_SET) {
          responseHandler(null, "Invalid response from server!")
        } else if (response.getResponsesCase() == proto.roc.Response.ResponsesCase.ERROR) {
          responseHandler(null, response.getError().getError())
        } else {
          responseHandler(response, null)
        }
      }
    }
  }
  xhr.open("POST", "http://localhost:8080", true)
  xhr.setRequestHeader("Content-Type", "application/octet-stream");
  try {
    xhr.send(requestProto.serializeBinary())
  } catch (err) {
    responseHandler(null, err.message)
    throw(err)
  }
}

function at(index, thumbnailSetter, metadataSetter) {
  rpc((new proto.roc.Request()).setAt((new proto.roc.Request.At()).setIndex(index)),
      function(response, err) {
        template = response?.getAt().getTemplate()
        thumbnailSetter?.(err ?? ("data:image/jpeg;base64," + template.getTn_asB64()))
        metadataSetter?.(err ?? template.getMd())
      })
}

function search(probe) {
  rpc((new proto.roc.Request()).setSearch((new proto.roc.Request.Search()).setProbe(probe)
                                                                          .setK(3)
                                                                          .setMinSimilarity(0)),
      function(response, err) {
        if (err) {
          document.getElementById("candidate_thumbnail").src = err
          document.getElementById("candidate_similarity").innerHTML = err
        } else {
          const candidate = response.getSearch().getCandidatesList()[0]
          at(parseInt(candidate.getIndex()),
             tn => document.getElementById("candidate_thumbnail").src = tn)
          document.getElementById("candidate_similarity").innerHTML = "Similarity: <b>" + candidate.getSimilarity().toFixed(3) + "</b>"
        }
      })
}

function enroll(id, templateCallback) {
  const fileReader = new FileReader()
  fileReader.onload = function(e) {
    rpc((new proto.roc.Request()).setRepresent((new proto.roc.Request.Represent()).setImage(new Uint8Array(fileReader.result))
                                                                                  .setAlgorithmId(proto.roc.AlgorithmOptions.ROC_FRONTAL | proto.roc.AlgorithmOptions.ROC_FR | proto.roc.AlgorithmOptions.ROC_THUMBNAIL)
                                                                                  .setMinSize(20)
                                                                                  .setK(-1)
                                                                                  .setFalseDetectionRate(0.02)
                                                                                  .setMinQuality(-4)),
        function(response, err) {
          templateCallback(response?.getRepresent().getTemplatesList()[0], err)
        })
  }
  fileReader.readAsArrayBuffer(document.getElementById(id).files[0])
}

function enrollProbe() {
  enroll("probe_image",
         (template, err) => {
           document.getElementById("probe_thumbnail").src = err ?? ("data:image/jpeg;base64," +  template.getTn_asB64())
           search(template)
         })
}

</script_>

  <h1>Search</h1>
  <p>Probe Image <input id="probe_image" type="file" onchange="enrollProbe()"></p>
  <table>
    <tr>
      <td><img id="probe_thumbnail"></td>
      <td><img id="candidate_thumbnail"></td>
    </tr>
    <tr>
      <td></td>
      <td id="candidate_similarity"></td>
    </tr>
  </table>
  
</body>
</html>

In the above example, the face recognition search decomposes into three API calls:

  1. “represent” to generate the probe face template from the input image.
  2. “search” to compare the probe template against the gallery and obtain the highest matching candidates.
  3. “at” to retrieve the highest matching candidate for displaying.

Some of the key takeaways from this web-page example are:

  • js-browserify/roc.js is the single-source-file definition of the communication protocol.
  • The HTTP request is constructed using a platform-standard library, in this case XMLHttpRequest, with the body of the request being the ROC Protobuf message requestProto.serializeBinary().
  • After checking for an error response from the server, response.getResponsesCase() == proto.roc.response.ResponsesCase.ERROR, responses are casted to their expected type based on the request.
  • Otherwise this code should look quite familiar to current users of our SDK!

If you are interested in testing and building solution using the ROC Web API, please contact us today to begin!

Popular articles: 

Share This