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

Visualization of a SAR image as a ground surface added #24

Merged
merged 7 commits into from
Apr 8, 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
28 changes: 28 additions & 0 deletions pybabylonjs/babylonjs.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,24 @@ def set_defaults(validator, properties, instance, schema):

mbrs_schema = deepcopy(core_schema)

ground_schema = {
"type": "object",
"properties": {
"inspector": {"type": "boolean", "default": False},
"image_type": {"type": "string", "default": "general"},
"width": {"type": "number", "default": 800},
"height": {"type": "number", "default": 600},
"z_scale": {"type": "number", "default": 1},
"wheel_precision": {"type": "number", "default": -1},
"xy_bbox": {"type": "array", "default": [0, 1, 0, 1]},
"band": {"type": "number", "default": 1},
"scale_factor": {"type": "number", "default": 1},
"img_width": {"type": "number"},
"img_height": {"type": "number"},
},
"required": ["xy_bbox", "img_width"],
}


class BabylonBase(DOMWidget):
_model_module = Unicode(module_name).tag(sync=True)
Expand Down Expand Up @@ -104,3 +122,13 @@ class BabylonMBRS(BabylonBase):
_view_name = Unicode("BabylonMBRSView").tag(sync=True)
value = Dict().tag(sync=True)
_schema = mbrs_schema


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

_model_name = Unicode("BabylonGroundModel").tag(sync=True)
_view_name = Unicode("BabylonGroundView").tag(sync=True)
value = Dict().tag(sync=True)
_schema = ground_schema
92 changes: 92 additions & 0 deletions pybabylonjs/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Copyright 2022 TileDB Inc.
# 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
import tiledb


def create_mbrs(array_uri: str):
"""Create a Dict to be passed on to BabylonMBRS to create MBRS outlines."""
fragments_info = tiledb.array_fragments(array_uri, include_mbrs=True)

df = pd.DataFrame()

f = 0
for fragment in fragments_info.mbrs:
f += 1
b = 0
for box in fragment:
b += 1
box_dict = {
"fragment": f,
"box": b,
"xmin": box[0][0],
"xmax": box[0][1],
"ymin": box[1][0],
"ymax": box[1][1],
"zmin": box[2][0],
"zmax": box[2][1],
}
box_df = pd.DataFrame([box_dict])
df = pd.concat([df, box_df], ignore_index=True)

data = {
"Xmin": df["xmin"],
"Xmax": df["xmax"],
"Ymin": df["ymin"],
"Ymax": df["ymax"],
"Zmin": df["zmin"],
"Zmax": df["zmax"],
}

extents = [
min(df["xmin"].tolist()),
max(df["xmax"].tolist()),
min(df["ymin"].tolist()),
max(df["ymax"].tolist()),
min(df["zmin"].tolist()),
max(df["zmax"].tolist()),
]

return dict(extents=extents, data=data)


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

Parameters:
array_uri: uri of the dense array
attribute: the attribute to load from the array
xy_bbox: ranges of x and y to slice data on [x1,x2,y1,y2]
band: band number to slice from the array
scale_factor: factor to scale the values in the image
"""

def numpy_to_binary(arr):
is_success, buffer = cv2.imencode(".png", arr)
io_buf = io.BytesIO(buffer)
return io_buf.read()

bbox = kwargs["xy_bbox"]
band = kwargs["band"]
image_type = kwargs["image_type"]
sar_scale_factor = 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 = ((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, img_width=img_width, img_height=img_height)
71 changes: 15 additions & 56 deletions pybabylonjs/visualize.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,14 @@
from IPython.display import display
from typing import Optional

import pandas as pd
import tiledb

from .babylonjs import BabylonPC, BabylonMBRS
from .babylonjs import BabylonPC, BabylonMBRS, BabylonGround
from .data import *


class PyBabylonJSError(Exception):
pass


def create_mbrs(array_name: str):
"""Create a Dict to be passed on to BabylonMBRS to create a 3D point cloud visualization."""
fragments_info = tiledb.array_fragments(array_name, include_mbrs=True)

df = pd.DataFrame()

f = 0
for fragment in fragments_info.mbrs:
f += 1
b = 0
for box in fragment:
b += 1
box_dict = {
"fragment": f,
"box": b,
"xmin": box[0][0],
"xmax": box[0][1],
"ymin": box[1][0],
"ymax": box[1][1],
"zmin": box[2][0],
"zmax": box[2][1],
}
box_df = pd.DataFrame([box_dict])
df = pd.concat([df, box_df], ignore_index=True)

data = {
"Xmin": df["xmin"],
"Xmax": df["xmax"],
"Ymin": df["ymin"],
"Ymax": df["ymax"],
"Zmin": df["zmin"],
"Zmax": df["zmax"],
}

extents = [
min(df["xmin"].tolist()),
max(df["xmax"].tolist()),
min(df["ymin"].tolist()),
max(df["ymax"].tolist()),
min(df["zmin"].tolist()),
max(df["zmax"].tolist()),
]

return dict(extents=extents, data=data)


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

Expand All @@ -77,23 +29,30 @@ def from_dict(self, data: dict, style: str, time: Optional[bool] = False, **kwar
if style == "pointcloud":
dataviz = BabylonPC()
d = {"time": time, "data": data}
d.update(kwargs)
dataviz.value = d
dataviz.value = {**d, **kwargs}
display(dataviz)
else:
raise PyBabylonJSError(f"Unsupported style {style}")

@classmethod
def from_array(self, array_uri: str, style: str, **kwargs):
def from_array(
self,
array_uri: str,
style: str,
**kwargs,
):
if style == "mbrs":
dataviz = BabylonMBRS()
d = create_mbrs(array_uri)
d.update(kwargs)
dataviz.value = d
display(dataviz)
if style == "ground":
dataviz = BabylonGround()
d = create_ground(array_uri, **kwargs)
else:
raise PyBabylonJSError(f"Unsupported style {style}")

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


class BabylonJS:
"""Legacy class for instantiating the widget"""
Expand Down
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
"dev": [
"matplotlib",
"python-pdal",
"pandas",
"cv2",
],
},
url=pkg_json["homepage"],
Expand Down
74 changes: 67 additions & 7 deletions src/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
import { MODULE_NAME, MODULE_VERSION } from './version';
import { ArcRotateCamera, Color3, Color4, Engine, PointsCloudSystem, Scene, SceneLoader, StandardMaterial,
SolidParticleSystem, MeshBuilder,
Vector3} from '@babylonjs/core';
Vector3,
Texture} from '@babylonjs/core';
import {AdvancedDynamicTexture, Control, StackPanel, Slider, TextBlock} from 'babylonjs-gui';
import "@babylonjs/loaders/glTF";
import "@babylonjs/core/Debug/debugLayer";
Expand Down Expand Up @@ -258,18 +259,17 @@ export class BabylonMBRSView extends BabylonBaseView {
const maxy = extents[3];
const minz = extents[4];
const maxz = extents[5];

const scale = this.zScale;

Copy link
Contributor

Choose a reason for hiding this comment

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

whitespace

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

var mat = new StandardMaterial('mt1', scene);
mat.alpha = 0.9;

const SPS = new SolidParticleSystem("SPS", scene);
const box = MeshBuilder.CreateBox("b", {height: 1, width: 1, depth: 1});
SPS.addShape(box, data.Xmin.length);
box.dispose(); //dispose of original model box
box.dispose();

SPS.buildMesh(); // finally builds and displays the SPS mesh
SPS.buildMesh();

SPS.initParticles = () => {
for (let p = 0; p < SPS.nbParticles; p++) {
Expand All @@ -285,15 +285,75 @@ export class BabylonMBRSView extends BabylonBaseView {
};

SPS.mesh.hasVertexAlpha = true;
SPS.initParticles(); //call the initialising function
SPS.setParticles(); //apply the properties and display the mesh
SPS.initParticles();
SPS.setParticles();
SPS.mesh.material = mat;

scene.createDefaultCameraOrLight(true, true, true);
let cam = scene.activeCamera as ArcRotateCamera;
cam.wheelPrecision = this.wheelPrecision;
cam.alpha += Math.PI;

return scene;
});
}
}

export class BabylonGroundModel extends BabylonBaseModel {
defaults(): any {
return {
...super.defaults(),
_model_name: BabylonGroundModel.model_name,
_model_module: BabylonGroundModel.model_module,
_model_module_version: BabylonGroundModel.model_module_version,
_view_name: BabylonGroundModel.view_name,
_view_module: BabylonGroundModel.view_module,
_view_module_version: BabylonGroundModel.view_module_version,
};
}

static model_name = 'BabylonGroundModel';
static view_name = 'BabylonGroundView';
}

export class BabylonGroundView extends BabylonBaseView {

protected async createScene(): Promise<Scene> {
return super.createScene().then( ( scene ) => {
const data = this.values.data;
const img_height = this.values.img_height;
const img_width = this.values.img_width;

scene.createDefaultCameraOrLight(true, true, true);
scene.clearColor = new Color4(0.95, 0.94, 0.92, 1);

var blob = new Blob([data]);
var url = URL.createObjectURL(blob);

const groundMaterial = new StandardMaterial("ground", scene);
groundMaterial.diffuseTexture = new Texture(url, scene);
groundMaterial.ambientTexture = new Texture(url, scene);
groundMaterial.ambientColor = new Color3(0.5, 0.5, 0.5);
groundMaterial.diffuseColor = new Color3(0.8, 0.8, 0.8);
groundMaterial.specularColor = new Color3(0.5, 0.5, 0.5);
groundMaterial.specularPower = 32;

const ground = MeshBuilder.CreateGround("ground", {height: img_height*0.005, width: img_width*0.005, subdivisions: 16}, scene);
ground.material = groundMaterial;

let camera = scene.activeCamera as ArcRotateCamera;
camera.panningAxis = new Vector3(1, 1, 0);
camera.upperBetaLimit = Math.PI / 2;
camera.panningSensibility = 1;
camera.panningInertia = 0.2;
camera._panningMouseButton = 0;

if (this.wheelPrecision > 0)
camera.wheelPrecision = this.wheelPrecision;

camera.alpha += Math.PI;
camera.attachControl(this.canvas, true);

return scene;
});
}
Expand Down