Links
Comment on page

Python examples

Using the REST API with Python 3.8
Here you can find some Python sample scripts that document how to use the REST API.
Substitute the Developer Token with your own token which you can find from the Developer Portal

Login

import requests
import json
def Login(url, username, password):
complete_url = url + '/login'
data = {
"login": username,
"password": password
}
json_data = json.dumps(data)
r = requests.post(complete_url, data=json_data)
print(r.text)
Login('https://api.immersal.com', '[email protected]', 'mypassword')
# Successful login returns the developer token and number of workspaces/image banks
{
"error": "none",
"token": "081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2",
"banks": 1
}
# Token authentication error
{
"error": "auth"
}

Account status

Get basic information about the account, e.g. map size limits.
import requests
import json
def AccountStatus(url, token):
complete_url = url + '/status'
data = {
"token": token,
"bank" : 0, # default workspace/image bank
}
json_data = json.dumps(data)
r = requests.post(complete_url, data=json_data)
print(r.text)
AccountStatus('https://api.immersal.com', '081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2')
# Success
{
"error": "none",
"imageCount": 8, # number of images in the workspace
"bankMax": 1,
"imageMax": 250,
"eulaAccepted": true
}
# Token authentication error
{
"error": "auth"
}

Clear the workspace/image bank

The Immersal Mapper app uses workspaces/image banks to store the data before you submit it for construction.
You can exit the app and continue adding images to the workspace later. When you're done with mapping, there's a function to clear the workspace before starting a new mapping session.
import requests
import json
def ClearWorkspace(url, token, deleteAnchorImage):
complete_url = url + '/clear'
data = {
"token" : token,
"bank" : 0, # default workspace/image bank
"anchor" : deleteAnchorImage
}
json_data = json.dumps(data)
r = requests.post(complete_url, data=json_data)
print(r.text)
ClearWorkspace('https://api.immersal.com', '081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2', True)
# Success
{
"error": "none"
}
# Token authentication error
{
"error": "auth"
}

Restore map to workspace/image bank

You can also restore all images from previous maps to the workspace. This allows you to extend or update mapped locations.
import requests
import json
def RestoreMap(url, token, mapId):
complete_url = url + '/restore'
data = {
"token" : token,
"id" : mapId
}
json_data = json.dumps(data)
r = requests.post(complete_url, data=json_data)
print(r.text)
RestoreMap('https://api.immersal.com', '081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2', 1234)
# Success
{
"error": "none"
}
# Token authentication error
{
"error": "auth"
}
# Incorrect map id error
{
"error": "job"
}

List account maps/jobs

Two variants. One for listing all user's maps and one for searching nearby maps.
You can leave the token string empty to search for public maps
import requests
import json
def ListJobs(url, token):
complete_url = url + '/list'
data = {
"token": token,
"bank": 0 # default workspace/image bank
}
json_data = json.dumps(data)
r = requests.post(complete_url, data=json_data)
print(r.text)
ListJobs('https://api.immersal.com', '081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2')
import requests
import json
def ListJobsGPS(url, token, latitude, longitude, radius):
complete_url = url + '/geolist'
data = {
"token": token,
"bank": 0, # default workspace/image bank
"latitude": latitude,
"longitude": longitude,
"radius": radius
}
json_data = json.dumps(data)
r = requests.post(complete_url, data=json_data)
print(r.text)
ListJobsGPS('https://api.immersal.com', '081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2', 60.8978, 26.9193, 200.0)
# Success
{
"error": "none",
"count": 75,
"jobs": [{
"id": 9386,
"version": "1.8.0",
"creator": "[email protected]",
"bank": 0,
"size": 5,
"work": "/im/s00/job/FjCSK9",
"status": "done",
"privacy": "0",
"server": "172.31.39.104",
"name": "5img",
"latitude": 60.897810474,
"longitude": 26.919315117999997,
"altitude": 122.15941162109375,
"created": "2020-09-11 14:42:56",
"modified": "2020-09-11 14:43:05",
"sha256_al": "e7c12d8a23a21702e129ec05af1423c37f708975cc82c9e05b90111ddcd19d22",
"sha256_sparse": "fb988633c42610bb34f403a816a2602047ddffa461072b2af9721d6fab708ecc",
"sha256_dense": "0efd9b336af155126afe544b52ea6095abce65517a01aeceba9b4add427365fe",
"sha256_tex": "d86c09d4ba792a2df7367368cb94e1b51ced08c63d2761dcf74ed260c51667fd"
}, {
"id": 9384,
"version": "1.8.0",
"creator": "[email protected]",
"bank": 0,
"size": 9,
"work": "/im/s00/job/tZU7IO",
"status": "done",
"privacy": "0",
"server": "172.31.0.163",
"name": "18test",
"latitude": 60.897835266666661,
"longitude": 26.91930419111111,
"altitude": 119.58823649088542,
"created": "2020-09-11 14:06:02",
"modified": "2020-09-11 14:06:21",
"sha256_al": "11d7ed2099c9b454d2c733261abd7642fdb7462259cf016e175b115bbbbf0aa6",
"sha256_sparse": "b53125dfa9449ed27bb8931b7defab6d725369249a7bf0752641522271d4249c",
"sha256_dense": "d1b942aa1fce7b7db641c9e4a151fe82832195f7a92f8e858510569cbf411c03",
"sha256_tex": "7284d11838d37a0faeee204e71f8085a11cf453c59c41488526e2d2d15c03d41"
}
...
}
# Token authentication error
{
"error": "auth"
}

Submit an image to workspace/image bank

To construct a map, you need to submit images from the location to the workspace.
You need to know the camera intrinsics for map construction and also provide camera position for accurate metric scale and orientation for up-axis alignment.
import requests
import json
import base64
def ConvertToBase64(src_filepath):
with open(src_filepath, 'rb') as imageFileAsBinary:
fileContent = imageFileAsBinary.read()
b64_encoded_img = base64.b64encode(fileContent)
return b64_encoded_img
def SubmitImage(url, token, imagePath):
complete_url = url + '/captureb64'
data = {
"token": token,
"bank": 0, # default workspace/image bank
"run": 0, # a running integer for the tracker session. Increment if tracking is lost or image is from a different session
"index": 0, # running index for images
"anchor": False, # flag for the image used as an anchor/map origin
"px": 0.0, # camera x position from the tracker
"py": 0.0, # camera y position from the tracker
"pz": 0.0, # camera z position from the tracker
"r00": 1.0, # camera orientation as a 3x3 matrix
"r01": 0.0,
"r02": 0.0,
"r10": 0.0,
"r11": 1.0,
"r12": 0.0,
"r20": 0.0,
"r21": 0.0,
"r22": 1.0,
"fx": 1455.0, # image focal length in pixels on x axis
"fy": 1455.0, # image focal length in pixels on y axis
"ox": 960.0, # image principal point on x axis
"oy": 720.0, # image principal point on y axis
"b64": str(ConvertToBase64(imagePath), 'utf-8') # base64 encoded .png image
}
json_data = json.dumps(data)
# print(json_data)
r = requests.post(complete_url, data=json_data)
print(r.text)
SubmitImage('https://api.immersal.com', '081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2', 'test.png')
# Success
{
"error": "none",
"path": "/im/s00/img/34/hxyz_0_0_0_0_0_44b5e000_44b5e000_44700000_44340000_80000000_0_80000000_0_0_0_FmlNLb.png"
}
# Token authentication error
{
"error": "auth"
}

Start map construction

Submits data from the workspace and starts the map construction process.
def StartMapConstruction(url, token, mapName, windowSize):
complete_url = url + '/construct'
data = {
"token": token,
"bank": 0,
"name": mapName,
# If the images are shot in sequence like a video stream, this optional parameter can be used to limit
# image matching to x previous and following frames.
# This can decrease map construction times and help constructing maps in repetitive environments.
# A value of 0 disables the feature.
"window_size" : windowSize
}
json_data = json.dumps(data)
r = requests.post(complete_url, data=json_data)
print(r.text)
StartMapConstruction('https://api.immersal.com', '081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2', 'myFirstMap', 0)
# Success
{
"error": "none",
"id": 1234,
"size": 8 # number of images in the map
}
# Token authentication error
{
"error": "auth"
}

Stitch maps together

def StitchMaps(url, token, mapName, mapIds):
complete_url = url + '/fuse'
data = {
"token" : token,
"name" : mapName,
"mapIds": mapIds
}
json_data = json.dumps(data)
r = requests.post(complete_url, data=json_data)
print(r.text)
StitchMaps('https://api.immersal.com', '081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2', 'stitchedMap', [1234, 5678])
# Success
{
"error": "none",
"id": 91011,
"size": 2
}

Download a map

You can leave the token string empty to download a public map
import requests
import json
import base64
def DownloadMap(url, token, mapId):
complete_url = url + '/mapb64'
data = {
"token": token,
"id": mapId
}
json_data = json.dumps(data)
r = requests.post(complete_url, data=json_data)
# print(r.text)
mapFile = base64.b64decode(json.loads(r.text)['b64'])
open(str(mapId) + '.bytes', 'wb').write(mapFile)
DownloadMap('https://api.immersal.com', '', 4054)
# Success
{
"error": "none",
"sha256_al": "d829ebc9138bb4cb68fe5009b39bf8639998684947052248cfeb10a6fc75ce6b",
"b64": "XQAAAAGSnQEAKUg ... +eFcJ1//88dco"
}
# incorrect map id or invalid token
{
"error": "not found"
}

Server localization

In addition to localizing on the device, you can use on-server localization. You can localize to multiple maps at once.
test.png
test.png
398KB
Image
Test image for map id 4054
You can leave the token string empty to localize against public maps
import requests
import json
import base64
def ConvertToBase64(src_filepath):
with open(src_filepath, 'rb') as imageFileAsBinary:
fileContent = imageFileAsBinary.read()
b64_encoded_img = base64.b64encode(fileContent)
return b64_encoded_img
def ServerLocalize(url, token, imagePath):
complete_url = url + '/localizeb64'
data = {
"token": token,
"fx": 1455.738159, # image focal length in pixels on x axis
"fy": 1455.738159, # image focal length in pixels on y axis
"ox": 962.615967, # image principal point on x axis
"oy": 694.292175, # image principal point on y axis
"b64": str(ConvertToBase64(imagePath), 'utf-8'), # base64 encoded .png image
"mapIds": [{"id": 4054}, {"id": 7576}] # a list of map ids to localize against
}
json_data = json.dumps(data)
r = requests.post(complete_url, data=json_data)
print(r.text)
ServerLocalize('https://api.immersal.com', '', 'test.png')
# Successful localization
{
"error": "none",
"success": true,
"map": 4054,
"px": 35.025848388671875,
"py": -6.5219945907592773,
"pz": -7.8264961242675781,
"r00": 0.064100831747055054,
"r01": -0.98219907283782959,
"r02": -0.17656733095645905,
"r10": 0.98891913890838623,
"r11": 0.038778573274612427,
"r12": 0.14330090582370758,
"r20": -0.13390298187732697,
"r21": -0.18379652500152588,
"r22": 0.97380125522613525
}
# Failed localization
{
"error": "none",
"success": false,
"map": -1,
"px": 0,
"py": 0,
"pz": 0,
"r00": 0,
"r01": 0,
"r02": 0,
"r10": 0,
"r11": 0,
"r12": 0,
"r20": 0,
"r21": 0,
"r22": 0
}
# Token authentication error
{
"error": "auth"
}
# Incorrect map id error, no valid maps to localize against
{
"error": "map count"
}

Get map local to global ECEF conversion matrix

To convert the found pose from local map coordinates to global coordinates, you can get the conversion matrix from the server.
The map needs to have GPS coordinates to have the conversion matrix.
You can leave the token string empty to get ECEF for a public map
import requests
import json
def ServerECEF(url, token, mapId):
complete_url = url + '/ecef'
data = {
"token": token,
"id": mapId
}
json_data = json.dumps(data)
r = requests.post(complete_url, data=json_data)
print(r.text)
ServerECEF('https://api.immersal.com', '', 4054)
# Success
{
"error": "none",
"ecef": [2884373.6879106648, 1340999.0750837622, 5509844.3036541771, -0.45108634233474731, -0.786507248878479, 0.42181453108787537, -0.20971845090389252, 0.55280953645706177, 0.80648607015609741, -0.86749023199081421, 0.27533257007598877, -0.41430991888046265, 1]
}
# incorrect map id, invalid token or conversion matrix unavailable
{
"error": "not found"
}

On-server localization and Visual GPS example

test.png
test.png
398KB
Image
Test image for map id 4054
This example code shows how to combine on-server localization and ECEF conversion matrix for a complete Visual GPS pipeline
It localizes the test.png test image against a public map (4054) from Helsinki streets, converts the local map position to ECEF and WGS84 and then displays the result on Google Maps.
import requests
import json
import base64
import numpy as np
import webbrowser
def ConvertToBase64(src_filepath):
with open(src_filepath, 'rb') as imageFileAsBinary:
fileContent = imageFileAsBinary.read()
b64_encoded_img = base64.b64encode(fileContent)
return b64_encoded_img
def ServerLocalize(url, token, mapId, imagePath):
complete_url = url + '/localizeb64'
data = {
"token": token,
"fx": 727.87, # hard-coded camera intrinsics for the test image
"fy": 727.87,
"ox": 481.44,
"oy": 347.15,
"b64": str(ConvertToBase64(imagePath), 'utf-8'),
"mapIds": [{"id": mapId}]
}
json_data = json.dumps(data)
r = requests.post(complete_url, data=json_data)
print(r.text)
return r.text
def ServerECEF(url, token, mapId):
complete_url = url + '/ecef'
data = {
"token": token,
"id": mapId
}
json_data = json.dumps(data)
r = requests.post(complete_url, data=json_data)
print(r.text)
return r.text
def EcefToWGS84(ecefPos):
a = np.double(6378137.0)
e = np.double(8.1819190842622e-2)
asq = np.power(a, 2)
esq = np.power(e, 2)
x = np.double(ecefPos[0])
y = np.double(ecefPos[1])
z = np.double(ecefPos[2])
b = np.sqrt(asq * (1.0 - esq))
bsq = np.power(b, 2)
ep = np.sqrt((asq - bsq) / bsq)
p = np.sqrt(np.power(x, 2) + np.power(y, 2))
th = np.arctan2(a * z, b * p)
lon = np.arctan2(y, x)
lat = np.arctan2((z + np.power(ep, 2) * b * np.power(np.sin(th), 3)), (p - esq * a * np.power(np.cos(th), 3)))
n = a / (np.sqrt(1.0 - esq * np.power(np.sin(lat), 2)))
alt = p / np.cos(lat) - n
lon = lon % (2 * np.pi)
lat = np.rad2deg(lat)
lon = np.rad2deg(lon)
if(lon > 180.0):
lon = lon - 360.0
return np.array([lat, lon, alt])
def MapPosToEcef(mapPos, m2e):
mp = np.array([ -mapPos[1], -mapPos[0], mapPos[2], 1.0 ])
S = m2e[12]
m = np.array([ [S*m2e[3], S*m2e[6], S*m2e[9], 0.0],
[S*m2e[4], S*m2e[7], S*m2e[10], 0.0],
[S*m2e[5], S*m2e[8], S*m2e[11], 0.0],
[ m2e[0], m2e[1], m2e[2], 1.0] ])
q = mp.dot(m)
return q
def Localize(url, token, mapId, imagePath):
localizationResult = json.loads(ServerLocalize(url, token, mapId, imagePath))
mapPos = np.array([localizationResult['px'], localizationResult['py'], localizationResult['pz'], 1.0])
ecefResult = json.loads(ServerECEF(url, token, mapId))
mapToEcef = np.array(ecefResult['ecef'])
ecefPos = MapPosToEcef(mapPos, mapToEcef)
wgs84 = EcefToWGS84(ecefPos)
print('lat: ' + str(wgs84[0].astype(float)),
'long: ' + str(wgs84[1].astype(float)),
'alt: ' + str(wgs84[2].astype(float)))
# Open location in Google Maps
webbrowser.open_new_tab(f'https://www.google.com/maps/search/{str(wgs84[0])},{str(wgs84[1])}')
Localize('https://api.immersal.com', '', 4054, 'test.png')