Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions gui/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Example Usage

For a step by step example check out this [guide](./example.md)

## Import CSV file as a new layer

To instantiate a new layer of points and the corresponding probability values, simply expand the right sidebar by clicking on the top-right button and select "new layer".
Expand Down
262 changes: 207 additions & 55 deletions gui/backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@
import re
from typing import Set


from promis import ProMis, StaRMap
from promis.geo import PolarLocation, CartesianLocation, CartesianRasterBand, CartesianMap
from promis.geo import PolarLocation, CartesianRasterBand, PolarMap, PolarRoute, PolarPolygon
from promis.loaders import OsmLoader

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, ValidationError
from .models.Config import Config
from pydantic import ValidationError
from .models.Config import Config, DynamicLayer
from .models.LocationTypeTable import LocationTypeTable
from .models.Line import Line
from .models.Polygon import Polygon
from .models.RunRequest import RunRequest

class Item(BaseModel):
source: str
origin: tuple[float, float]
dimensions: tuple[int, int]
resolutions: tuple[int, int]
location_types: dict[str, str]


app = FastAPI()
Expand All @@ -28,7 +27,6 @@ class Item(BaseModel):
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Expand All @@ -48,71 +46,195 @@ def myHash(text:str):
hash = ( hash*281 ^ ord(ch)*997) & 0xFFFFFFFF
return hash

@app.post("/runpromis")
def run_new(req: Item):
# We center a 1km^2 mission landscape over TU Darmstadt
mission_center = PolarLocation(latitude=req.origin[0], longitude=req.origin[1])
dimensions = req.dimensions
width, height = dimensions
def _get_config():
config = None
try:
with open('./config/config.json', 'r') as f:
config = f.read()
try:
config = Config.model_validate_json(config)
except ValidationError as e:
print(e)
raise HTTPException(status_code=500, detail="fail to parse config file")
except FileNotFoundError:
config = Config(layers=[], markers=[])
return config

# We load geographic features from OpenStreetMap using a range of filters
location_types = req.location_types
def _get_location_type_table():
location_type_table = None
try:
with open('./config/location_type_table.json', 'r') as f:
location_type_table = f.read()
try:
location_type_table = LocationTypeTable.model_validate_json(location_type_table)
except ValidationError as e:
print(e)
raise HTTPException(status_code=500, detail="fail to parse config file")
except FileNotFoundError:
location_type_table = LocationTypeTable(table=[])
return location_type_table

def create_hash_uam(req: RunRequest):
# prepare for hash
reqDict = req.model_dump()
del reqDict["source"]
del reqDict["resolutions"]
del reqDict["support_resolutions"]
del reqDict["sample_size"]
del reqDict["interpolation"]
locations_to_remove = []
for location_type, filter in reqDict["location_types"].items():
if filter == "":
locations_to_remove.append(location_type)
for loc_type in locations_to_remove:
del reqDict["location_types"][loc_type]

return myHash(repr(reqDict))

def create_hash_starmap(req: RunRequest, dynamic_layers: DynamicLayer, hashVal: int):
# prepare for hash
reqDict = req.model_dump()
del reqDict["source"]
reqDict["hashVal"] = hashVal
reqDict["dynamic_layers"] = dynamic_layers

return myHash(repr(reqDict))

@app.post("/loadmapdata")
def load_map_data(req: RunRequest):
mission_center = PolarLocation(latitude=req.origin[0], longitude=req.origin[1])
width, height = req.dimensions

# We load geographic features from OpenStreetMap using a range of filters
feature_description = req.location_types

hashVal = myHash(repr(reqDict))
# prepare for hash
hashVal = create_hash_uam(req)
# load the cache info
try:
with open(f"./cache/uam_{hashVal}.pickle", 'rb') as f:
uam = CartesianMap.load(f"./cache/uam_{hashVal}.pickle")
uam = PolarMap.load(f"./cache/uam_{hashVal}.pickle")
has_cache = True
print("found cache")
print("found cache map data in loadmapdata")
except FileNotFoundError:
has_cache = False

if (not has_cache):
osm_loader = OsmLoader(mission_center, dimensions)
for name, osm_filter in location_types.items():
osm_loader.load_routes(osm_filter, name)
osm_loader.load_polygons(osm_filter, name)
uam = OsmLoader(mission_center, (width, height), feature_description).to_polar_map()

uam.save(f"./cache/uam_{hashVal}.pickle")

return hashVal

@app.post("/starmap/{hashVal}")
def calculate_star_map(req: RunRequest, hashVal: int):
mission_center = PolarLocation(latitude=req.origin[0], longitude=req.origin[1])
dimensions = req.dimensions
width, height = dimensions
sample_size = req.sample_size
interpolation = req.interpolation
logic = req.source

# We load geographic features from OpenStreetMap using a range of filters
location_types = req.location_types
# load the cache info
try:
with open(f"./cache/uam_{hashVal}.pickle", 'rb') as f:
polar_map = PolarMap.load(f"./cache/uam_{hashVal}.pickle")
print("found cache map data in starmap")
except FileNotFoundError:
raise HTTPException(status_code=404, detail="No map data found on this request.")

# get dynamic markers, line and polygons
map_config = _get_config()
markers = map_config.markers
polylines = map_config.polylines
polygons = map_config.polygons

# We now convert the data into an Uncertainty Annotated Map
uam = osm_loader.to_cartesian_map()
dynamic_layers = DynamicLayer(markers=markers, polylines=polylines, polygons=polygons)

# We can add extra information, e.g., from background knowledge or other sensors
# Here, we place the drone operator at the center of the map
uam.features.append(CartesianLocation(0, 0, location_type="operator"))
star_map_hash_val = create_hash_starmap(req, dynamic_layers, hashVal)

# Annotate the same level of uncertainty on all features
uam.apply_covariance(10.0 * eye(2))

uam.save(f"./cache/uam_{hashVal}.pickle")
# load the cache info
#try:
# with open(f"./cache/starmap_{star_map_hash_val}.pickle", 'rb') as f:
# print("found cache star map in starmap")
#except FileNotFoundError:
# create polar map

# create all markers, lines and polygons to uam
for marker in markers:
if marker.location_type == "":
continue
polar_map.features.append(PolarLocation(marker.latlng[1], marker.latlng[0], location_type=marker.location_type))
for polyline in polylines:
if polyline.location_type == "":
continue
locations = []
for location in polyline.latlngs:
locations.append(PolarLocation(location[1], location[0]))
polyline_feature = PolarRoute(locations, location_type=polyline.location_type)
polar_map.features.append(polyline_feature)
for polygon in polygons:
if polygon.location_type == "":
continue
locations = []
for location in polygon.latlngs:
locations.append(PolarLocation(location[1], location[0]))
polygon_feature = PolarPolygon(locations, location_type=polygon.location_type)
polar_map.features.append(polygon_feature)

uam = polar_map.to_cartesian()

# apply covariance
loc_type_table = _get_location_type_table()
loc_to_uncertainty = dict()
for entry in loc_type_table.table:
loc_to_uncertainty[entry.location_type] = entry.uncertainty * eye(2)

uam.apply_covariance(loc_to_uncertainty)
# We create a statistical relational map (StaR Map) to represent the
# stochastic relationships in the environment, computing a raster of 1000 x 1000 points
# using linear interpolation of a sample set
target_resolution = req.resolutions
target = CartesianRasterBand(mission_center, target_resolution, width, height)
star_map = StaRMap(target, uam, list(location_types.keys()), "linear")
star_map = StaRMap(target, uam, method=interpolation)

# The sample points for which the relations will be computed directly
support_resolution = target_resolution
support = CartesianRasterBand(mission_center, support_resolution, width, height)
support_resolutions = req.support_resolutions
support = CartesianRasterBand(mission_center, support_resolutions, width, height)

# We now compute the Distance and Over relationships for the selected points
# For this, we take 25 random samples from generated/possible map variations
star_map.add_support_points(support, 25)
star_map.initialize(support, sample_size, logic)

#star_map.save(f"./cache/starmap_{star_map_hash_val}.pickle")

app.star_map = star_map

return star_map_hash_val

# In ProMis, we define the constraints of the mission
# as hybrid probabilistic first-order logic program
@app.post("/inference/{hashVal}")
def inference(req: RunRequest, hashVal: int):
# load the cache info
#try:
# with open(f"./cache/starmap_{hashVal}.pickle", 'rb') as f:
# star_map = StaRMap.load(f"./cache/starmap_{hashVal}.pickle")
# print("found cache star map in inference")
#except FileNotFoundError:
# raise HTTPException(status_code=404, detail="No star map found on this request.")

star_map = app.star_map

logic = req.source
mission_center = PolarLocation(latitude=req.origin[0], longitude=req.origin[1])
dimensions = req.dimensions
width, height = dimensions
support_resolutions = req.support_resolutions
support = CartesianRasterBand(mission_center, support_resolutions, width, height)

# Solve mission constraints using StaRMap parameters and multiprocessing
promis = ProMis(star_map)
landscape = promis.solve(logic, n_jobs=4, batch_size=15)
landscape = promis.solve(support, logic, n_jobs=4, batch_size=15)

polar_pml = landscape.to_polar()
data = []
Expand All @@ -126,23 +248,53 @@ def run_new(req: Item):
data.append([lat, long, val])
return data


@app.post("/config")
def update_config(config: Config):
config_old = _get_config()

if config_old:
config.polygons = config_old.polygons
config.polylines = config_old.polylines
with open('./config/config.json', 'w', encoding='utf-8') as f:
f.write(config.model_dump_json(indent=2))

@app.post("/config_polylines")
def update_config_polylines(polylines: list[Line]):
config = _get_config()
config.polylines = polylines
with open('./config/config.json', 'w', encoding='utf-8') as f:
f.write(config.model_dump_json(indent=2))

@app.post("/config_polygons")
def update_config_polygons(polygons: list[Polygon]):
config = _get_config()
config.polygons = polygons
with open('./config/config.json', 'w', encoding='utf-8') as f:
f.write(config.model_dump_json(indent=2))

@app.post("/config_dynamic_layers")
def update_config_dynamic(dynamic_layers: DynamicLayer):
markers = dynamic_layers.markers
polylines = dynamic_layers.polylines
polygons = dynamic_layers.polygons

config = _get_config()
config.markers = markers
config.polylines = polylines
config.polygons = polygons

with open('./config/config.json', 'w', encoding='utf-8') as f:
f.write(config.model_dump_json(indent=2))

@app.post("/location_type_table")
def update_location_type_table(location_type_table: LocationTypeTable):
with open('./config/location_type_table.json', 'w', encoding='utf-8') as f:
f.write(location_type_table.model_dump_json(indent=2))


@app.get("/config")
def get_config():
config = None
try:
with open('./config/config.json', 'r') as f:
config = f.read()
try:
config = Config.model_validate_json(config)
except ValidationError as e:
print(e)
raise HTTPException(status_code=500, detail="fail to parse config file")
except FileNotFoundError:
config = Config(layers=[], markers=[])

return config
config = _get_config()
location_type_table = _get_location_type_table()
return config, location_type_table
11 changes: 10 additions & 1 deletion gui/backend/models/Config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
from pydantic import BaseModel
from .Layer import Layer
from .Marker import Marker
from .Line import Line
from .Polygon import Polygon

class Config(BaseModel):
layers: list[Layer]
markers: list[Marker]
markers: list[Marker]
polylines: list[Line] = []
polygons: list[Polygon] = []

class DynamicLayer(BaseModel):
markers: list[Marker]
polylines: list[Line]
polygons: list[Polygon]
1 change: 1 addition & 0 deletions gui/backend/models/Layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Layer(BaseModel):
colorMenuExpanded: bool
hue: int
opacity: float
renderMode: str = "HEATMAP_RECT"
radius: float
valueRange: tuple[float, float]
markersValMinMax: tuple[float, float]
Expand Down
6 changes: 6 additions & 0 deletions gui/backend/models/Line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from pydantic import BaseModel

class Line(BaseModel):
latlngs: list[tuple[float, float]]
location_type: str = ""
color: str = "#000000"
11 changes: 11 additions & 0 deletions gui/backend/models/LocationTypeTable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from pydantic import BaseModel

class LocationTypeEntry(BaseModel):
id: int
location_type: str
filter: str = ""
color: str = "#3388ff"
uncertainty: int | float = 10

class LocationTypeTable(BaseModel):
table: list[LocationTypeEntry]
4 changes: 3 additions & 1 deletion gui/backend/models/Marker.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
class Marker(BaseModel):
latlng: tuple[float, float]
shape: str
name: str
name: str
location_type: str = ""
color: str = "#000000"
Loading