Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mg/mapbox base layer #29

Merged
merged 9 commits into from
Jun 1, 2022
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: 1 addition & 1 deletion pybabylonjs/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
from pathlib import Path
from .visualize import BabylonJS, Show
from .show import BabylonJS, Show
from ._version import __version__

HERE = Path(__file__).parent.resolve()
Expand Down
30 changes: 16 additions & 14 deletions pybabylonjs/babylonjs.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,20 +57,22 @@ def set_defaults(validator, properties, instance, schema):
"classes": {"type": "boolean", "default": False},
"class_numbers": {"type": "array"},
"class_names": {"type": "array"},
"topo": {"type": "boolean", "default": False},
"topo_offset": {"type": "number", "default": 0},
"gltf_data": {"type": "string"},
"point_size": {"type": "number", "default": 1},
"data": {"type": "object", "properties": {}, "required": []},
},
"required": ["data"],
}

pc_schema = deepcopy(core_schema)
point_cloud_schema = deepcopy(core_schema)
attrs = ["X", "Y", "Z", "Red", "Green", "Blue"]
pc_schema["properties"]["data"]["required"].extend(attrs)
point_cloud_schema["properties"]["data"]["required"].extend(attrs)

mbrs_schema = deepcopy(core_schema)

ground_schema = {
image_schema = {
"type": "object",
"properties": {
"inspector": {"type": "boolean", "default": False},
Expand All @@ -85,18 +87,18 @@ def set_defaults(validator, properties, instance, schema):
"img_width": {"type": "number"},
"img_height": {"type": "number"},
},
"required": ["xy_bbox", "img_width"],
"required": ["xy_bbox"],
}


class BabylonBase(DOMWidget):
"""Base class for all Babylon derived widgets"""

_model_module = Unicode(module_name).tag(sync=True)
_model_module_version = Unicode(module_version).tag(sync=True)
_view_module_version = Unicode(module_version).tag(sync=True)
_view_module = Unicode(module_name).tag(sync=True)

"""Base class for all Babylon derived widgets"""

@validate("value")
def _validate_value(self, proposal):
try:
Expand All @@ -107,13 +109,13 @@ def _validate_value(self, proposal):


@register
class BabylonPC(BabylonBase):
class BabylonPointCloud(BabylonBase):
"""3D point cloud with BabylonJS"""

_model_name = Unicode("BabylonPCModel").tag(sync=True)
_view_name = Unicode("BabylonPCView").tag(sync=True)
_model_name = Unicode("BabylonPointCloudModel").tag(sync=True)
_view_name = Unicode("BabylonPointCloudView").tag(sync=True)
value = Dict().tag(sync=True)
_schema = pc_schema
_schema = point_cloud_schema


@register
Expand All @@ -127,10 +129,10 @@ class BabylonMBRS(BabylonBase):


@register
class BabylonGround(BabylonBase):
class BabylonImage(BabylonBase):
"""Ground surface as 2D array with BabylonJS"""

_model_name = Unicode("BabylonGroundModel").tag(sync=True)
_view_name = Unicode("BabylonGroundView").tag(sync=True)
_model_name = Unicode("BabylonImageModel").tag(sync=True)
_view_name = Unicode("BabylonImageView").tag(sync=True)
value = Dict().tag(sync=True)
_schema = ground_schema
_schema = image_schema
66 changes: 58 additions & 8 deletions pybabylonjs/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
# Licensed under the MIT License.
"""Functions to format data from the arrays to be used in the visualization."""

# from array import array
import io
import json
from datetime import datetime
import numpy as np
import pandas as pd
import cv2
Expand Down Expand Up @@ -58,7 +55,7 @@ def create_mbrs(array_uri: str):
return dict(extents=extents, data=data)


def create_ground(array_uri: str, **kwargs):
def create_image(array_uri: str, **kwargs):
"""Create a Dict to be passed on to BabylonGround containing images as blobs.

Parameters:
Expand All @@ -77,16 +74,69 @@ def numpy_to_binary(arr):
bbox = kwargs["xy_bbox"]
band = kwargs["band"]
image_type = kwargs["image_type"]
sar_scale_factor = kwargs["sar_scale_factor"]
sar_scale = kwargs["sar_scale_factor"]

with tiledb.open(array_uri, "r") as arr:
img = arr[band, bbox[0] : bbox[1], bbox[2] : bbox[3]][kwargs["attribute"]]

if image_type == "sar":
img = 20 * np.log10(img * sar_scale_factor)
img = 20 * np.log10(img * sar_scale)
img = ((img - np.min(img)) / (np.max(img) - np.min(img))) * 255
binary_image = numpy_to_binary(img)

[img_height, img_width] = np.shape(img)
return dict(data=binary_image)

return dict(data=binary_image, img_width=img_width, img_height=img_height)

def create_mapbox_image(data: dict, **kwargs):
"""Create a Dict with an additional topographic image from mapbox

Parameters:
"""
import requests
from rasterio.coords import BoundingBox
from rasterio.warp import transform_bounds

mbtoken = kwargs["mbtoken"]
style_id = kwargs["mbstyle"]
data_crs = kwargs["crs"]

dst_crs = {"init": "EPSG:4326"} # lat/lon

bbox = BoundingBox(
data["X"].min(), data["Y"].min(), data["X"].max(), data["Y"].max()
)

dst_bbox = transform_bounds(data_crs, dst_crs, *bbox)

w = bbox[2] - bbox[0]
h = bbox[3] - bbox[1]

if w > h:
ww = 1280
hh = int(h / w * 1280)
elif h > w:
hh = 1280
ww = int(w / h * 1280)

f = requests.get(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be doing this loading inside of the Typescript widget and use https://doc.babylonjs.com/divingDeeper/importers/assetManager Now we have multiple loading tasks (e.g GLTF) this will help and give display feedback to the user.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but probably not in this PR?

(
"https://api.mapbox.com/styles/v1/mapbox/"
+ style_id
+ "/static/["
+ str(dst_bbox[0])
+ ","
+ str(dst_bbox[1])
+ ","
+ str(dst_bbox[2])
+ ","
+ str(dst_bbox[3])
+ "]/"
+ str(ww)
+ "x"
+ str(hh)
+ "?access_token="
+ mbtoken
)
)

return f.content
103 changes: 103 additions & 0 deletions pybabylonjs/show.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Copyright 2022 TileDB Inc.
# Licensed under the MIT License.
"""Classes for setting up the visualization."""

from IPython.display import display
from typing import Optional

from .babylonjs import BabylonPointCloud, BabylonMBRS, BabylonImage
from .data import *


def create_dataviz(dataviz, d, **kwargs):
dataviz.value = {**d, **kwargs}
display(dataviz)


class PyBabylonJSError(Exception):
pass


class Show:
"""Create a N-D visualization.

Parameters:
...
"""

def __init__(self):
self._value = None
self._dataviz = None

@classmethod
def point_cloud(
self,
data: dict,
time: Optional[bool] = False,
classes: Optional[bool] = False,
topo: Optional[bool] = False,
**kwargs,
):
d = {"classes": classes, "time": time, "topo": topo, "data": data}
if topo:
img = create_mapbox_image(data, **kwargs)
d = {**d, "mapbox_img": img}
create_dataviz(BabylonPointCloud(), d, **kwargs)

@classmethod
def image(
self,
array_uri: str,
**kwargs,
):
d = create_image(array_uri, **kwargs)
create_dataviz(BabylonImage(), d, **kwargs)

@classmethod
def mbrs(
self,
array_uri: str,
**kwargs,
):
d = create_mbrs(array_uri)
create_dataviz(BabylonMBRS(), d, **kwargs)

@classmethod
def from_dict(
self,
data: dict,
time: Optional[bool] = False,
classes: Optional[bool] = False,
topo: Optional[bool] = False,
**kwargs,
):
d = {"classes": classes, "time": time, "topo": topo, "data": data}
if topo:
img = create_mapbox_image(data, **kwargs)
d = {**d, "mapbox_img": img}
create_dataviz(BabylonPointCloud(), d, **kwargs)


class BabylonJS:
"""Legacy class for instantiating the widget"""

def __init__(self):
self.value = None
self.z_scale = None
self.width = None
self.height = None

def _ipython_display_(self):
kwargs = {}

if self.z_scale:
kwargs["z_scale"] = self.z_scale

if self.width:
kwargs["width"] = self.width

if self.height:
kwargs["height"] = self.height

d = {"data": self.value}
create_dataviz(BabylonPointCloud(), d, **kwargs)
85 changes: 0 additions & 85 deletions pybabylonjs/visualize.py

This file was deleted.

Loading