# 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', 'john.doe@immersal.com', '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)
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)

# 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.

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":	"teemu@immersal.com",
			"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":	"teemu@immersal.com",
			"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

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-img-for-map-id-4054.png

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.

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-img-for-map-id-4054.png

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')