Sighthound Cloud API - Vehicle Analytics - Docker
The Sighthound Cloud API returns detection coordinates and annotations for all vehicles and license plates detected in an image. For vehicles, the annotations include make, model, color, and generation. For license plates, the annotations include the characters from the plate and region (e.g., Florida, Germany, etc.).
This guide contains instructions on how to run Sighthound Cloud API using Docker Compose. If you prefer to use your own docker run
command or other orchestration methods, please refer to the docker-compose.yml
for the necessary environment variables, configuration files, and volume mounting examples. Contact support@sighthound.com with any questions.
Initial Setup
Docker
Ensure your system has a recent version of Docker Engine and Docker Compose. If you need to install Docker, you can follow the directions on Docker's site, or you can run the convenience script that Docker provides:
Cloud API Files
Sighthound will provide you with two zip files to run the Cloud API service. Please email support@sighthound.com if you do not receive them.
sighthound-cloud-api.zip
- Contains the Docker Compose and config files needed to run the Cloud API service in Docker.
- Extract the zip to a location of your choice on the computer where the Cloud API will be running. In the rest of this document, we will refer to this location as
./sighthound-cloud-api
.
sighthound-secrets.zip
- Contains sensitive files specific to your account that should be protected.
sighthound-keyfile.json
is used to gain access to our private Docker repository.sighthound-license.json
is your license and is required for the Cloud API to run.- Extract the two files and move them to the
./sighthound-cloud-api/config/
folder, overwriting the existingsighthound-license.json
in theconfig
folder.
NVIDIA GPU
Running the Cloud API on a computer with an NVIDIA GPU is optional but highly recommended for increased performance. Depending on the complexity of the scene and the number of objects detected, a system with an NVIDIA GPU can result in a 2x-15x speed increase.
If you are running Cloud API on a computer without an NVIDIA GPU, you can skip this section and proceed to the Running Cloud API section.
If your system has an NVIDIA GPU installed, install the latest drivers and install the NVIDIA Container Toolkit to give the Docker access to the GPU. You can confirm that the system is configured correctly by running the following command:
Next, enable GPU acceleration in the Sighthound Cloud API docker-compose.yml
file (located in the ./sighthound-cloud-api
folder) by uncommenting the runtime: nvidia
line for the main
service. The service will run in the slower CPU mode if you don't perform this step.
Running Cloud API
Note
Depending on your machine's configuration, you may need to append sudo before each docker command in this document. Additionally, you may need to run docker-compose
instead of docker compose
if you have an older version.
Pull the Docker Image
To pull the Docker image from our private repository, you must log in to Docker using the sighthound-keyfile.json
.
Open a terminal, cd
into the ./sighthound-cloud-api/config/
folder, and run the following command. You should only need to do this once per machine where the Cloud API service will be running.
Run this command to pull the Docker image:
Start Cloud API Service
Open a terminal, cd
into the sighthound-cloud-api
folder, and run the following command to start the service:
View Service Logs
Run the following command from within the sighthound-cloud-api
folder to follow the service logs:
Upon first launch, the application will perform an initial health check and download the necessary models. The service is ready to accept requests when you see the initial health test passed, server ready
message:
23/05/19-15:14:59.952 INFO [main] running initial health test ...
23/05/19-15:15:06.436 INFO [main] initial health test passed, server ready
Note
If you have an NVIDIA GPU, the first launch will start a process that builds the NVIDIA GPU model cache as part of the initial health test. This may take several minutes to complete depending on your specific hardware configuration. During this time, API requests will not be served. Once it finishes, the initial health test will continue and log initial health test passed, server ready
when complete. This process should only happen once, and subsequent service restarts will not need to rebuild the cache unless your hardware changes or Sighthound's models are updated in future releases.
Request Format
Headers
Body
There are two ways to send images to the Cloud API: by using a URI or base64-encoded content. See the image
object in the examples below.
Only JPG and PNG images are supported at this time. If your use case requires a different format, please contact us.
Image URI
{
"requests": [
{
"image": {
"source": {
"imageUri": "https://d1mfcqjbhp6mmy.cloudfront.net/vehicles/audi-sports-car-r8-marlene.jpeg"
}
},
"features": [
{
"type": "VEHICLE_DETECTION"
},
{
"type": "LICENSE_PLATE_DETECTION"
}
]
}
]
}
Base64 Content
{
"requests": [
{
"image": {
"content": "/9j/4QAYR...truncatedBase64String...ZUcQP/2Q=="
},
"features": [
{
"type": "VEHICLE_DETECTION"
},
{
"type": "LICENSE_PLATE_DETECTION"
}
]
}
]
}
Examples
Note
The examples below assume the Cloud API service is running on http://localhost:8383
. If the service runs on a different machine or you previously changed the port in the docker-compose.yml file, you must update the examples accordingly.
Request
curl --location --request POST 'http://localhost:8383/v11/images:annotate' \
--header 'Content-Type: application/json' \
--data-raw '{
"requests": [
{
"image": {
"source": {
"imageUri": "https://d1mfcqjbhp6mmy.cloudfront.net/vehicles/audi-sports-car-r8-marlene.jpeg"
}
},
"features": [
{
"type": "VEHICLE_DETECTION"
},
{
"type": "LICENSE_PLATE_DETECTION"
}
]}
]}'
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Text.Json;
namespace Sighthound {
public class Feature {
public string type { get; set; }
}
public class Source {
public string imageUri { get; set; }
}
public class Image {
public Source source { get; set; }
}
public class Request {
public Image image { get; set; }
public List<Feature> features { get; set; }
}
public class BatchRequest {
public List<Request> requests { get; set; }
}
public class Example {
public static void Main() {
var apiUrl = "http://localhost:8080/v11/images:annotate";
var batchRequest = new BatchRequest {
requests = new List<Request>(
new Request[] {
new Request {
image = new Image {
source = new Source {
imageUri = "https://d1mfcqjbhp6mmy.cloudfront.net/vehicles/audi-sports-car-r8-marlene.jpeg"
}
},
features = new List<Feature>(
new Feature[] {
new Feature {
type = "VEHICLE_DETECTION"
},
new Feature {
type = "LICENSE_PLATE_DETECTION"
}
}
)
}
}
)
};
string requestJson = JsonSerializer.Serialize(batchRequest);
byte[] requestData = Encoding.UTF8.GetBytes(requestJson);
var webRequest = (HttpWebRequest)WebRequest.Create(apiUrl);
webRequest.Method = "POST";
webRequest.Accept = "application/json";
webRequest.ContentType = "application/json";
webRequest.ContentLength = requestData.Length;
webRequest.GetRequestStream().Write(requestData, 0, requestData.Length);
using var webResponse = webRequest.GetResponse();
using var webStream = webResponse.GetResponseStream();
using var responseReader = new StreamReader(webStream);
var responseJson = responseReader.ReadToEnd();
var responseData = JsonDocument.Parse(responseJson);
var response = responseData.RootElement.GetProperty("responses")[0];
Console.WriteLine("detected " +
response.GetProperty("vehicleAnnotations").GetArrayLength() + " vehicles and " +
response.GetProperty("licenseplateAnnotations").GetArrayLength() + " license plates");
}
}
}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
type Source struct {
ImageUri string `json:"imageUri"`
}
type Image struct {
Source Source `json:"source"`
}
type Feature struct {
Type string `json:"type"`
}
type Request struct {
Image Image `json:"image"`
Features []Feature `json:"features"`
}
type BatchRequest struct {
Requests []Request `json:"requests"`
}
type ExampleResponse struct {
VehicleAnnotations []interface{} `json:"vehicleAnnotations"`
LicenseplateAnnotations []interface{} `json:"licenseplateAnnotations"`
}
type ExampleBatchResponse struct {
Responses []ExampleResponse `json:"responses"`
}
var apiUrl = "http://localhost:8080/v11/images:annotate"
func main() {
batchRequest := BatchRequest{
Requests: []Request{
{
Image: Image{
Source: Source{
ImageUri: "https://d1mfcqjbhp6mmy.cloudfront.net/vehicles/audi-sports-car-r8-marlene.jpeg",
},
},
Features: []Feature{
{
Type: "VEHICLE_DETECTION",
},
{
Type: "LICENSE_PLATE_DETECTION",
},
},
},
},
}
httpRequestBody, _ := json.Marshal(batchRequest)
httpRequest, _ := http.NewRequest("POST", apiUrl, bytes.NewBuffer(httpRequestBody))
httpRequest.Header.Set("Content-Type", "application/json")
httpRequest.Header.Set("Accept", "application/json")
httpClient := &http.Client{}
httpResponse, error := httpClient.Do(httpRequest)
if error != nil {
fmt.Println("request failed:", error)
return
}
defer httpResponse.Body.Close()
if httpResponse.Status != "200 OK" {
fmt.Println("response error:", httpResponse.Status)
return
}
responseBody, _ := ioutil.ReadAll(httpResponse.Body)
var batchResponse ExampleBatchResponse
err := json.Unmarshal(responseBody, &batchResponse)
if err != nil {
fmt.Println("parse error:", err)
return
}
fmt.Println("detected",
len(batchResponse.Responses[0].VehicleAnnotations), "vehicles and",
len(batchResponse.Responses[0].LicenseplateAnnotations), "license plates")
}
import java.net.*;
import java.net.http.*;
import org.json.*;
class Example {
public static void main(String[] args) throws Exception {
System.setProperty("java.net.preferIPv6Addresses","true"); // for correct "localhost" resolution
var imageSource = new JSONObject();
imageSource.put("imageUri", "https://d1mfcqjbhp6mmy.cloudfront.net/vehicles/audi-sports-car-r8-marlene.jpeg");
var image = new JSONObject();
image.put("source", imageSource);
var featureVehicleDetection = new JSONObject();
featureVehicleDetection.put("type", "VEHICLE_DETECTION");
var featureLicenseplateDetection = new JSONObject();
featureLicenseplateDetection.put("type", "LICENSE_PLATE_DETECTION");
var features = new JSONArray();
features.put(featureVehicleDetection);
features.put(featureLicenseplateDetection);
var request = new JSONObject();
request.put("image", image);
request.put("features", features);
var requests = new JSONArray();
requests.put(request);
var batchRequest = new JSONObject();
batchRequest.put("requests", requests);
var apiUrl = "http://localhost:8080/v11/images:annotate";
var httpRequest = HttpRequest.newBuilder()
.uri(URI.create(apiUrl))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(batchRequest.toString()))
.build();
var httpClient = HttpClient.newHttpClient();
var httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
if (httpResponse.statusCode() == 200) {
var batchResponse = new JSONObject(httpResponse.body());
var response = batchResponse.getJSONArray("responses").getJSONObject(0);
System.out.printf("detected %d vehicles and %d license plates%n",
response.getJSONArray("vehicleAnnotations").length(),
response.getJSONArray("licenseplateAnnotations").length());
}
}
}
const apiUrl = "http://localhost:8080/v11/images:annotate";
const batchRequest = {
requests: [
{
image: {
source: {
imageUri: "https://d1mfcqjbhp6mmy.cloudfront.net/vehicles/audi-sports-car-r8-marlene.jpeg"
}
},
features: [
{
type: "VEHICLE_DETECTION"
},
{
type: "LICENSE_PLATE_DETECTION"
}
]
}
]
};
const fetchResponse = await fetch(apiUrl, {
body: JSON.stringify(batchRequest),
headers: { "Content-Type": "application/json" },
method: "POST"
});
const batchResponse = await fetchResponse.json();
const response = batchResponse.responses[0];
console.log("detected",
response.vehicleAnnotations.length, "vehicles and",
response.licenseplateAnnotations.length, "license plates");
<?php
$apiUrl = "http://localhost:8080/v11/images:annotate";
$batchRequest = [
"requests" => [
[
"image" => [
"source" => [
"imageUri" => "https://d1mfcqjbhp6mmy.cloudfront.net/vehicles/audi-sports-car-r8-marlene.jpeg"
]
],
"features" => [
[
"type" => "VEHICLE_DETECTION"
],
[
"type" => "LICENSE_PLATE_DETECTION"
]
]
]
]
];
$post = array(
'http' => array(
'method' => 'POST',
'content' => json_encode($batchRequest),
'header'=> "Content-Type: application/json\r\n" .
"Accept: application/json\r\n"
)
);
$context = stream_context_create($post);
$result = file_get_contents($apiUrl, false, $context);
$batchResponse = json_decode($result);
print 'detected ' .
count($batchResponse->{'responses'}[0]->{'vehicleAnnotations'}) . ' vehicles and ' .
count($batchResponse->{'responses'}[0]->{'licenseplateAnnotations'}) . ' license plates' .
PHP_EOL;
?>
import requests
apiUrl = "http://localhost:8080/v11/images:annotate"
batchRequest = {
"requests": [
{
"image": {
"source": {
"imageUri": "https://d1mfcqjbhp6mmy.cloudfront.net/vehicles/audi-sports-car-r8-marlene.jpeg"
}
},
"features": [
{
"type": "VEHICLE_DETECTION"
},
{
"type": "LICENSE_PLATE_DETECTION"
}
]
}
]
}
batchResponse = requests.post(apiUrl, json = batchRequest).json()
response = batchResponse['responses'][0]
print('detected %r vehicles and %r license plates' %(
len(response['vehicleAnnotations']),
len(response['licenseplateAnnotations'])
))
require 'json'
require 'net/http'
apiUrl = 'http://localhost:8080/v11/images:annotate'
batchRequest = {
requests: [
{
image: {
source: {
imageUri: 'https://d1mfcqjbhp6mmy.cloudfront.net/vehicles/audi-sports-car-r8-marlene.jpeg'
}
},
features: [
{
type: 'VEHICLE_DETECTION'
},
{
type: 'LICENSE_PLATE_DETECTION'
}
]
}
]
}
response = Net::HTTP.post URI(apiUrl), batchRequest.to_json, 'Content-Type' => 'application/json'
batchResponse = JSON.parse(response.body)
vehicleCount = batchResponse['responses'][0]['vehicleAnnotations'].length()
licenseplateCount = batchResponse['responses'][0]['licenseplateAnnotations'].length()
puts "detected #{vehicleCount} vehicles and #{licenseplateCount} license plates"
import Foundation
let done = DispatchSemaphore(value: 0)
let apiUrl = URL(string: "http://localhost:8080/v11/images:annotate")!
let batchRequest: [String: Any] = [
"requests": [
[
"image": [
"source": [
"imageUri": "https://d1mfcqjbhp6mmy.cloudfront.net/vehicles/audi-sports-car-r8-marlene.jpeg"
]
],
"features": [
[
"type": "VEHICLE_DETECTION"
],
[
"type": "LICENSE_PLATE_DETECTION"
]
]
]
]
]
let batchRequestJson = try? JSONSerialization.data(withJSONObject: batchRequest)
var httpRequest = URLRequest(url: apiUrl)
httpRequest.httpMethod = "POST"
httpRequest.httpBody = batchRequestJson;
httpRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
httpRequest.setValue("application/json", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: httpRequest) { (data: Data?, httpResponse, error) in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "unknown error")
return
}
let batchResponseJson = try? JSONSerialization.jsonObject(with: data, options: [])
let batchResponse = batchResponseJson as? [String: Any]
let responses = batchResponse!["responses"] as? [[String: [[String: Any]]]]
let response = responses![0]
print("detected \(response["vehicleAnnotations"]!.count) vehicles and \(response["licenseplateAnnotations"]!.count) license plates")
done.signal()
}
task.resume()
done.wait()
Response
{
"responses": [
{
"licenseplateAnnotations": [
{
"string": {
"value": "RTBB221",
"score": 0.86
},
"region": {
"value": "Germany",
"score": 0.86
},
"boundingPoly": {
"vertices": [
{
"x": 620,
"y": 450
},
{
"x": 738,
"y": 450
},
{
"x": 738,
"y": 478
},
{
"x": 620,
"y": 478
}
],
"normalizedVertices": [
{
"x": 0.6888888888888889,
"y": 0.7258064516129032
},
{
"x": 0.82,
"y": 0.7258064516129032
},
{
"x": 0.82,
"y": 0.7709677419354839
},
{
"x": 0.6888888888888889,
"y": 0.7709677419354839
}
]
},
"locale": "en-US",
"score": 0.86,
"mid": "/m/01jfm_",
"description": "License plate"
}
],
"vehicleAnnotations": [
{
"make": {
"value": "audi",
"score": 0.94
},
"model": {
"value": "r8",
"score": 0.94
},
"generation": {
"value": {
"start": 2007,
"end": 2014
},
"score": 0.94
},
"color": {
"value": "white",
"score": 0.94
},
"licenseplate": {
"$ref": "#/responses/0/licenseplateAnnotations/0"
},
"boundingPoly": {
"vertices": [
{
"x": 428,
"y": 287
},
{
"x": 833,
"y": 287
},
{
"x": 833,
"y": 556
},
{
"x": 428,
"y": 556
}
],
"normalizedVertices": [
{
"x": 0.47555555555555556,
"y": 0.4629032258064516
},
{
"x": 0.9255555555555556,
"y": 0.4629032258064516
},
{
"x": 0.9255555555555556,
"y": 0.896774193548387
},
{
"x": 0.47555555555555556,
"y": 0.896774193548387
}
]
},
"locale": "en-US",
"score": 0.94,
"mid": "/m/0k4j",
"description": "Car"
}
]
}
]
}