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

1
import requests
2
import json
3
​
4
def Login(url, username, password):
5
complete_url = url + '/login'
6
​
7
data = {
8
"login": username,
9
"password": password
10
}
11
​
12
json_data = json.dumps(data)
13
​
14
r = requests.post(complete_url, data=json_data)
15
print(r.text)
16
​
17
Login('https://api.immersal.com', '[email protected]', 'mypassword')
Copied!
1
# Successful login returns the developer token and number of workspaces/image banks
2
{
3
"error": "none",
4
"token": "081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2",
5
"banks": 1
6
}
7
​
8
# Token authentication error
9
{
10
"error": "auth"
11
}
Copied!

Account status

Get basic information about the account, e.g. map size limits.
1
import requests
2
import json
3
​
4
def AccountStatus(url, token):
5
complete_url = url + '/status'
6
​
7
data = {
8
"token": token,
9
"bank" : 0, # default workspace/image bank
10
}
11
​
12
json_data = json.dumps(data)
13
​
14
r = requests.post(complete_url, data=json_data)
15
print(r.text)
16
​
17
AccountStatus('https://api.immersal.com', '081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2')
Copied!
1
# Success
2
{
3
"error": "none",
4
"imageCount": 8, # number of images in the workspace
5
"bankMax": 1,
6
"imageMax": 250,
7
"eulaAccepted": true
8
}
9
​
10
# Token authentication error
11
{
12
"error": "auth"
13
}
Copied!

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.
1
import requests
2
import json
3
​
4
def ClearWorkspace(url, token, deleteAnchorImage):
5
complete_url = url + '/clear'
6
​
7
data = {
8
"token" : token,
9
"bank" : 0, # default workspace/image bank
10
"anchor" : deleteAnchorImage
11
}
12
​
13
json_data = json.dumps(data)
14
​
15
r = requests.post(complete_url, data=json_data)
16
print(r.text)
17
18
ClearWorkspace('https://api.immersal.com', '081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2', True)
Copied!
1
# Success
2
{
3
"error": "none"
4
}
5
​
6
# Token authentication error
7
{
8
"error": "auth"
9
}
Copied!

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.
1
import requests
2
import json
3
​
4
def RestoreMap(url, token, mapId):
5
complete_url = url + '/restore'
6
​
7
data = {
8
"token" : token,
9
"id" : mapId
10
}
11
​
12
json_data = json.dumps(data)
13
​
14
r = requests.post(complete_url, data=json_data)
15
print(r.text)
16
​
17
RestoreMap('https://api.immersal.com', '081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2', 1234)
Copied!
1
# Success
2
{
3
"error": "none"
4
}
5
​
6
# Token authentication error
7
{
8
"error": "auth"
9
}
10
​
11
# Incorrect map id error
12
{
13
"error": "job"
14
}
Copied!

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
1
import requests
2
import json
3
​
4
def ListJobs(url, token):
5
complete_url = url + '/list'
6
​
7
data = {
8
"token": token,
9
"bank": 0 # default workspace/image bank
10
}
11
​
12
json_data = json.dumps(data)
13
​
14
r = requests.post(complete_url, data=json_data)
15
print(r.text)
16
​
17
ListJobs('https://api.immersal.com', '081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2')
Copied!
1
import requests
2
import json
3
​
4
def ListJobsGPS(url, token, latitude, longitude, radius):
5
complete_url = url + '/geolist'
6
​
7
data = {
8
"token": token,
9
"bank": 0, # default workspace/image bank
10
"latitude": latitude,
11
"longitude": longitude,
12
"radius": radius
13
}
14
​
15
json_data = json.dumps(data)
16
​
17
r = requests.post(complete_url, data=json_data)
18
print(r.text)
19
​
20
ListJobsGPS('https://api.immersal.com', '081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2', 60.8978, 26.9193, 200.0)
Copied!
1
# Success
2
{
3
"error": "none",
4
"count": 75,
5
"jobs": [{
6
"id": 9386,
7
"version": "1.8.0",
8
"creator": "[email protected]",
9
"bank": 0,
10
"size": 5,
11
"work": "/im/s00/job/FjCSK9",
12
"status": "done",
13
"privacy": "0",
14
"server": "172.31.39.104",
15
"name": "5img",
16
"latitude": 60.897810474,
17
"longitude": 26.919315117999997,
18
"altitude": 122.15941162109375,
19
"created": "2020-09-11 14:42:56",
20
"modified": "2020-09-11 14:43:05",
21
"sha256_al": "e7c12d8a23a21702e129ec05af1423c37f708975cc82c9e05b90111ddcd19d22",
22
"sha256_sparse": "fb988633c42610bb34f403a816a2602047ddffa461072b2af9721d6fab708ecc",
23
"sha256_dense": "0efd9b336af155126afe544b52ea6095abce65517a01aeceba9b4add427365fe",
24
"sha256_tex": "d86c09d4ba792a2df7367368cb94e1b51ced08c63d2761dcf74ed260c51667fd"
25
}, {
26
"id": 9384,
27
"version": "1.8.0",
28
"creator": "[email protected]",
29
"bank": 0,
30
"size": 9,
31
"work": "/im/s00/job/tZU7IO",
32
"status": "done",
33
"privacy": "0",
34
"server": "172.31.0.163",
35
"name": "18test",
36
"latitude": 60.897835266666661,
37
"longitude": 26.91930419111111,
38
"altitude": 119.58823649088542,
39
"created": "2020-09-11 14:06:02",
40
"modified": "2020-09-11 14:06:21",
41
"sha256_al": "11d7ed2099c9b454d2c733261abd7642fdb7462259cf016e175b115bbbbf0aa6",
42
"sha256_sparse": "b53125dfa9449ed27bb8931b7defab6d725369249a7bf0752641522271d4249c",
43
"sha256_dense": "d1b942aa1fce7b7db641c9e4a151fe82832195f7a92f8e858510569cbf411c03",
44
"sha256_tex": "7284d11838d37a0faeee204e71f8085a11cf453c59c41488526e2d2d15c03d41"
45
}
46
47
...
48
}
49
​
50
# Token authentication error
51
{
52
"error": "auth"
53
}
Copied!

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.
1
import requests
2
import json
3
import base64
4
​
5
def ConvertToBase64(src_filepath):
6
with open(src_filepath, 'rb') as imageFileAsBinary:
7
fileContent = imageFileAsBinary.read()
8
b64_encoded_img = base64.b64encode(fileContent)
9
​
10
return b64_encoded_img
11
​
12
def SubmitImage(url, token, imagePath):
13
complete_url = url + '/captureb64'
14
​
15
data = {
16
"token": token,
17
"bank": 0, # default workspace/image bank
18
"run": 0, # a running integer for the tracker session. Increment if tracking is lost or image is from a different session
19
"index": 0, # running index for images
20
"anchor": False, # flag for the image used as an anchor/map origin
21
"px": 0.0, # camera x position from the tracker
22
"py": 0.0, # camera y position from the tracker
23
"pz": 0.0, # camera z position from the tracker
24
"r00": 1.0, # camera orientation as a 3x3 matrix
25
"r01": 0.0,
26
"r02": 0.0,
27
"r10": 0.0,
28
"r11": 1.0,
29
"r12": 0.0,
30
"r20": 0.0,
31
"r21": 0.0,
32
"r22": 1.0,
33
"fx": 1455.0, # image focal length in pixels on x axis
34
"fy": 1455.0, # image focal length in pixels on y axis
35
"ox": 960.0, # image principal point on x axis
36
"oy": 720.0, # image principal point on y axis
37
"b64": str(ConvertToBase64(imagePath), 'utf-8') # base64 encoded .png image
38
}
39
​
40
json_data = json.dumps(data)
41
# print(json_data)
42
​
43
r = requests.post(complete_url, data=json_data)
44
print(r.text)
45
​
46
SubmitImage('https://api.immersal.com', '081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2', 'test.png')
Copied!
1
# Success
2
{
3
"error": "none",
4
"path": "/im/s00/img/34/hxyz_0_0_0_0_0_44b5e000_44b5e000_44700000_44340000_80000000_0_80000000_0_0_0_FmlNLb.png"
5
}
6
​
7
# Token authentication error
8
{
9
"error": "auth"
10
}
Copied!

Start map construction

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

Stitch maps together

1
def StitchMaps(url, token, mapName, mapIds):
2
complete_url = url + '/fuse'
3
​
4
data = {
5
"token" : token,
6
"name" : mapName,
7
"mapIds": mapIds
8
}
9
​
10
json_data = json.dumps(data)
11
​
12
r = requests.post(complete_url, data=json_data)
13
print(r.text)
14
​
15
StitchMaps('https://api.immersal.com', '081df5eeacde56ce0958660bdbbfa4a61e23ffb0790ae41943d64f95c1a60de2', 'stitchedMap', [1234, 5678])
Copied!
1
# Success
2
{
3
"error": "none",
4
"id": 91011,
5
"size": 2
6
}
Copied!

Download a map

You can leave the token string empty to download a public map
1
import requests
2
import json
3
import base64
4
​
5
def DownloadMap(url, token, mapId):
6
complete_url = url + '/mapb64'
7
​
8
data = {
9
"token": token,
10
"id": mapId
11
}
12
​
13
json_data = json.dumps(data)
14
​
15
r = requests.post(complete_url, data=json_data)
16
# print(r.text)
17
mapFile = base64.b64decode(json.loads(r.text)['b64'])
18
open(str(mapId) + '.bytes', 'wb').write(mapFile)
19
​
20
DownloadMap('https://api.immersal.com', '', 4054)
Copied!
1
# Success
2
{
3
"error": "none",
4
"sha256_al": "d829ebc9138bb4cb68fe5009b39bf8639998684947052248cfeb10a6fc75ce6b",
5
"b64": "XQAAAAGSnQEAKUg ... +eFcJ1//88dco"
6
}
7
​
8
# incorrect map id or invalid token
9
{
10
"error": "not found"
11
}
Copied!

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
1
import requests
2
import json
3
import base64
4
​
5
def ConvertToBase64(src_filepath):
6
with open(src_filepath, 'rb') as imageFileAsBinary:
7
fileContent = imageFileAsBinary.read()
8
b64_encoded_img = base64.b64encode(fileContent)
9
​
10
return b64_encoded_img
11
​
12
def ServerLocalize(url, token, imagePath):
13
complete_url = url + '/localizeb64'
14
​
15
data = {
16
"token": token,
17
"fx": 1455.738159, # image focal length in pixels on x axis
18
"fy": 1455.738159, # image focal length in pixels on y axis
19
"ox": 962.615967, # image principal point on x axis
20
"oy": 694.292175, # image principal point on y axis
21
"b64": str(ConvertToBase64(imagePath), 'utf-8'), # base64 encoded .png image
22
"mapIds": [{"id": 4054}, {"id": 7576}] # a list of map ids to localize against
23
}
24
​
25
json_data = json.dumps(data)
26
​
27
r = requests.post(complete_url, data=json_data)
28
print(r.text)
29
​
30
ServerLocalize('https://api.immersal.com', '', 'test.png')
Copied!
1
# Successful localization
2
{
3
"error": "none",
4
"success": true,
5
"map": 4054,
6
"px": 35.025848388671875,
7
"py": -6.5219945907592773,
8
"pz": -7.8264961242675781,
9
"r00": 0.064100831747055054,
10
"r01": -0.98219907283782959,
11
"r02": -0.17656733095645905,
12
"r10": 0.98891913890838623,
13
"r11": 0.038778573274612427,
14
"r12": 0.14330090582370758,
15
"r20": -0.13390298187732697,
16
"r21": -0.18379652500152588,
17
"r22": 0.97380125522613525
18
}
19
​
20
# Failed localization
21
{
22
"error": "none",
23
"success": false,
24
"map": -1,
25
"px": 0,
26
"py": 0,
27
"pz": 0,
28
"r00": 0,
29
"r01": 0,
30
"r02": 0,
31
"r10": 0,
32
"r11": 0,
33
"r12": 0,
34
"r20": 0,
35
"r21": 0,
36
"r22": 0
37
}
38
​
39
# Token authentication error
40
{
41
"error": "auth"
42
}
43
​
44
# Incorrect map id error, no valid maps to localize against
45
{
46
"error": "map count"
47
}
Copied!

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
1
import requests
2
import json
3
​
4
def ServerECEF(url, token, mapId):
5
complete_url = url + '/ecef'
6
​
7
data = {
8
"token": token,
9
"id": mapId
10
}
11
​
12
json_data = json.dumps(data)
13
​
14
r = requests.post(complete_url, data=json_data)
15
print(r.text)
16
​
17
ServerECEF('https://api.immersal.com', '', 4054)
Copied!
1
# Success
2
{
3
"error": "none",
4
"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]
5
}
6
​
7
# incorrect map id, invalid token or conversion matrix unavailable
8
{
9
"error": "not found"
10
}
Copied!

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.
1
import requests
2
import json
3
import base64
4
import numpy as np
5
import webbrowser
6
​
7
def ConvertToBase64(src_filepath):
8
with open(src_filepath, 'rb') as imageFileAsBinary:
9
fileContent = imageFileAsBinary.read()
10
b64_encoded_img = base64.b64encode(fileContent)
11
​
12
return b64_encoded_img
13
​
14
def ServerLocalize(url, token, mapId, imagePath):
15
complete_url = url + '/localizeb64'
16
​
17
data = {
18
"token": token,
19
"fx": 727.87, # hard-coded camera intrinsics for the test image
20
"fy": 727.87,
21
"ox": 481.44,
22
"oy": 347.15,
23
"b64": str(ConvertToBase64(imagePath), 'utf-8'),
24
"mapIds": [{"id": mapId}]
25
}
26
​
27
json_data = json.dumps(data)
28
​
29
r = requests.post(complete_url, data=json_data)
30
print(r.text)
31
​
32
return r.text
33
​
34
def ServerECEF(url, token, mapId):
35
complete_url = url + '/ecef'
36
​
37
data = {
38
"token": token,
39
"id": mapId
40
}
41
​
42
json_data = json.dumps(data)
43
​
44
r = requests.post(complete_url, data=json_data)
45
print(r.text)
46
​
47
return r.text
48
​
49
def EcefToWGS84(ecefPos):
50
a = np.double(6378137.0)
51
e = np.double(8.1819190842622e-2)
52
​
53
asq = np.power(a, 2)
54
esq = np.power(e, 2)
55
​
56
x = np.double(ecefPos[0])
57
y = np.double(ecefPos[1])
58
z = np.double(ecefPos[2])
59
​
60
b = np.sqrt(asq * (1.0 - esq))
61
bsq = np.power(b, 2)
62
ep = np.sqrt((asq - bsq) / bsq)
63
p = np.sqrt(np.power(x, 2) + np.power(y, 2))
64
th = np.arctan2(a * z, b * p)
65
​
66
lon = np.arctan2(y, x)
67
lat = np.arctan2((z + np.power(ep, 2) * b * np.power(np.sin(th), 3)), (p - esq * a * np.power(np.cos(th), 3)))
68
n = a / (np.sqrt(1.0 - esq * np.power(np.sin(lat), 2)))
69
alt = p / np.cos(lat) - n
70
​
71
lon = lon % (2 * np.pi)
72
​
73
lat = np.rad2deg(lat)
74
lon = np.rad2deg(lon)
75
76
if(lon > 180.0):
77
lon = lon - 360.0
78
​
79
return np.array([lat, lon, alt])
80
​
81
def MapPosToEcef(mapPos, m2e):
82
mp = np.array([ -mapPos[1], -mapPos[0], mapPos[2], 1.0 ])
83
S = m2e[12]
84
m = np.array([ [S*m2e[3], S*m2e[6], S*m2e[9], 0.0],
85
[S*m2e[4], S*m2e[7], S*m2e[10], 0.0],
86
[S*m2e[5], S*m2e[8], S*m2e[11], 0.0],
87
[ m2e[0], m2e[1], m2e[2], 1.0] ])
88
q = mp.dot(m)
89
return q
90
​
91
def Localize(url, token, mapId, imagePath):
92
​
93
localizationResult = json.loads(ServerLocalize(url, token, mapId, imagePath))
94
mapPos = np.array([localizationResult['px'], localizationResult['py'], localizationResult['pz'], 1.0])
95
​
96
ecefResult = json.loads(ServerECEF(url, token, mapId))
97
mapToEcef = np.array(ecefResult['ecef'])
98
ecefPos = MapPosToEcef(mapPos, mapToEcef)
99
wgs84 = EcefToWGS84(ecefPos)
100
​
101
print('lat: ' + str(wgs84[0].astype(float)),
102
'long: ' + str(wgs84[1].astype(float)),
103
'alt: ' + str(wgs84[2].astype(float)))
104
​
105
# Open location in Google Maps
106
webbrowser.open_new_tab(f'https://www.google.com/maps/search/{str(wgs84[0])},{str(wgs84[1])}')
107
​
108
Localize('https://api.immersal.com', '', 4054, 'test.png')
Copied!