Skip to content

Commit

Permalink
raster merging APIs with GUIs
Browse files Browse the repository at this point in the history
  • Loading branch information
jiqicn committed Oct 19, 2021
1 parent a929dc2 commit 3997023
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 35 deletions.
5 changes: 4 additions & 1 deletion meteovis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from .dataset import *
from .view import *
from .control import *
import warnings

import os

Expand All @@ -15,4 +16,6 @@
if not os.path.exists(CACHE_DIR):
os.mkdir(CACHE_DIR)
if not os.path.exists(TEMP_DIR):
os.mkdir(TEMP_DIR)
os.mkdir(TEMP_DIR)

warnings.filterwarnings("ignore")
25 changes: 24 additions & 1 deletion meteovis/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,27 @@ def load(self, img_name):
self._queue.append(img_name)
self[img_name] = img_buf

return img_buf
return img_buf


class OpacityController(object):
def __init__(self, views):
"""
Opacity control widget
"""
self.opactl = widgets.FloatSlider(
value=1,
min=0,
max=1,
)

def change_opactl(e=None):
for v in views:
v.raster.opacity = e["new"]
self.opactl.observe(change_opactl, names="value")

def get_controler(self):
return widgets.HBox([
widgets.HTML('<b>Opacity&nbsp</b>'),
self.opactl
])
18 changes: 13 additions & 5 deletions meteovis/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ def remove(self):
"""
os.remove(self.dataset_path)

def merge(self, mode, *datasets):
def merge(self, mode, datasets, name, desc):
"""
merge two or more datasets into one
Expand All @@ -339,10 +339,18 @@ def merge(self, mode, *datasets):
# initialize dataset
self.id = str(uuid.uuid4())
self.dataset_path = os.path.join(DATASET_DIR, self.id + ".h5")
self.name = "COMB "
for ds in datasets:
self.name += ds.id + " "
self.desc = self.name
if name == "":
self.name = "COMB ["
for ds in datasets:
self.name += ds.name + ", "
self.name = self.name[:-2]
self.name += "]"
else:
self.name = name
if desc == "":
self.desc = ""
else:
self.desc = self.name
self.src = datasets[0].src # we assume that only same product needs to be merged
self.crs = datasets[0].crs
self.cmap = datasets[0].cmap # since same product, cmap would also be the same
Expand Down
102 changes: 87 additions & 15 deletions meteovis/meteovis.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""
from .dataset import DatasetGenerator, Dataset, DATASET_DIR
from .view import View
from .control import AnimePlayer
from .control import AnimePlayer, OpacityController

import ipywidgets as widgets
from ipyfilechooser import FileChooser
Expand All @@ -14,6 +14,9 @@
import h5py
import json
import os
import time
import shutil
import uuid


def process_data():
Expand Down Expand Up @@ -189,6 +192,7 @@ def operate_datasets(dir_path=DATASET_DIR):
w_tabs = widgets.Tab()
w_tabs.set_title(0, "General")
w_tabs.set_title(1, "Visualization")
w_tabs.set_title(2, "Merging")

# General tab
w_refresh = widgets.Button(
Expand All @@ -197,22 +201,25 @@ def operate_datasets(dir_path=DATASET_DIR):
w_remove = widgets.Button(
description="Remove"
)
w_copy = widgets.Button(
description="Copy"
)
w_tabs.children += (
widgets.VBox([
widgets.HTML('<b>Select ONE dataset for the operations below.</b>'),
widgets.HBox([
w_refresh,
w_remove,
w_remove,
w_copy
]),
]),
)

# Visualization tab
# visualization tab
w_select_vis = widgets.Button(
description="Confirm Selection"
)
w_vis_output = widgets.Output()
# w_view_box = widgets.HBox() # where to add views
w_view_box = widgets.GridBox(
children=[],
layout=widgets.Layout(grid_template_columns="100%")
Expand All @@ -221,9 +228,7 @@ def operate_datasets(dir_path=DATASET_DIR):
w_tabs.children += (
widgets.VBox([
widgets.HTML(
'<p><b>Select ONE or TWO dataset(s) for visualization.</b></p>' +
'<p>If you select two datasets, they will be presented side-by-side.</p>' +
'<p>If you select more than two datasets, only the first two will be visualized.<p>' +
'<p><b>Select dataset(s) for visualization.</b></p>' +
'<p>Press the button below to confirm your selection.</p>'
),
w_select_vis,
Expand All @@ -233,6 +238,27 @@ def operate_datasets(dir_path=DATASET_DIR):
]),
)

# merging tab
w_merge = widgets.Button(description="Merge")
w_merge_method = widgets.Dropdown(
description="Method",
options=["avg", "max", "min"],
value="avg"
)
w_merge_name = widgets.Text(description="Name")
w_merge_desc = widgets.Text(description="Description")
w_merge_output = widgets.Output()
w_tabs.children += (
widgets.VBox([
widgets.HTML("<p><b>Select datasets for merging.</b><p>"),
w_merge_method,
w_merge_name,
w_merge_desc,
w_merge,
w_merge_output,
]),
)

oper_gui = widgets.VBox([
w_title,
w_table,
Expand All @@ -249,14 +275,13 @@ def refresh_on_click(b=None):
if not dn.startswith('.'):
dp = os.path.join(dir_path, dn)
ds = Dataset(dp)
ctime = time.ctime(os.path.getctime(dp))
dataset_info.append([
ds.id,
ds.name,
ds.desc,
ds.src,
ds.timeline[0],
ds.timeline[-1],
json.dumps(ds.options)
ctime
])
dataset_info = pd.DataFrame(
dataset_info,
Expand All @@ -265,9 +290,7 @@ def refresh_on_click(b=None):
"Name",
"Description",
"Data Source",
"Start Time",
"End Time",
"Metainfo"
"Create Time",
]
)
dataset_info.set_index("ID", inplace=True) # set ID as index column
Expand All @@ -287,6 +310,26 @@ def remove_on_click(b=None):
refresh_on_click()
w_remove.on_click(remove_on_click)

def copy_on_click(b=None):
"""
copy an existing dataset to file
"""
selection = w_table.get_selected_df()
ids_to_copy = selection.index.values
for id_old in ids_to_copy:
src_path = os.path.join(dir_path, id_old + ".h5")
id_new = str(uuid.uuid4())
dst_path = os.path.join(dir_path, id_new+".h5")
shutil.copyfile(src_path, dst_path)

# change information in the new dataset file
with h5py.File(dst_path, "r+") as f:
f["meta"].attrs.modify("id", id_new)
old_name = f["meta"].attrs["name"]
f["meta"].attrs.modify("name", "COPY [" + old_name + "]")
refresh_on_click()
w_copy.on_click(copy_on_click)

def select_vis_on_click(b=None):
"""
show views of the selected dataset(s)
Expand All @@ -312,6 +355,7 @@ def select_vis_on_click(b=None):
# initialize controls and views
ap = AnimePlayer(views)
ap.init_views()
oc = OpacityController(views)

# display maps and controls
# gridbox with maximum 2 cols
Expand All @@ -321,10 +365,38 @@ def select_vis_on_click(b=None):
w_view_box.layout = widgets.Layout(grid_template_columns="50% 50%")
for v in views:
w_view_box.children += (v.map, )
w_ctrl_box.children += (ap.get_player(), )

w_ctrl_box.children += (
ap.get_player(),
oc.get_controler(),
)
w_select_vis.on_click(select_vis_on_click)

def merge_on_click(b=None):
"""
merge the datasets selected
"""
ds_m = Dataset() # dataset object for the merging result
selection = w_table.get_selected_df()
ids_to_merge = selection.index.values
method = w_merge_method.value
datasets = []
for dn in ids_to_merge:
dn = dn + ".h5"
dp = os.path.join(dir_path, dn)
datasets.append(Dataset(dp))

w_merge_output.clear_output()
with w_merge_output:
ds_m.merge(
method,
datasets,
w_merge_name.value,
w_merge_desc.value
)

refresh_on_click() # refresh the list view of datasets
w_merge.on_click(merge_on_click)

# init the dataset list and dataframe of dataset information
refresh_on_click()

Expand Down
42 changes: 41 additions & 1 deletion meteovis/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ class of view, as well as other relevant classes
import multiprocessing as mp
import h5py
import os
from ipyleaflet import Map, basemaps, FullScreenControl, ImageOverlay
from ipyleaflet import Map, basemaps, FullScreenControl, ImageOverlay, WidgetControl
import base64
import io
import numpy as np


CACHE_DIR = os.getcwd() + "/cache"
Expand All @@ -34,6 +37,43 @@ def __init__(self, dataset):
)
self.map.add_control(FullScreenControl())

# colorbar widget
cbar_buf = io.BytesIO()
cmap = dataset.cmap[0]
vmin = dataset.cmap[1]
vmax = dataset.cmap[2]
plt.figure(figsize=(3, 0.5))
plt.imshow(
np.array([[vmin, vmax]]),
cmap=cmap
)
plt.gca().set_visible(False)
plt.colorbar(
cax=plt.axes([0.1, 0.2, 0.8, 0.3]),
orientation="horizontal",
extend="both"
)
plt.savefig(
cbar_buf,
format="png",
transparent=True,
bbox_inches="tight",
pad_inches=0
)
plt.close()
cbar_buf.seek(0)
cbar_b64 = base64.b64encode(cbar_buf.read()).decode('ascii')
img_str = '<div style="padding-left: 10px; padding-right: 10px">'
img_str += '<p><b>Dataset: %s</b></p>' % dataset.name
img_str += '<img src="data:image/png;base64, %s"/></div>' % cbar_b64
w_cbar = widgets.HTML(
img_str,
layout=widgets.Layout(padding='20 20 20 20')
)
self.map.add_control(
WidgetControl(widget=w_cbar, position='topright')
)

# raster layer
self.raster = ImageOverlay(
url=EMPTY_IMAGE,
Expand Down
12 changes: 6 additions & 6 deletions test_apis.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"cell_type": "code",
"execution_count": 1,
"id": "eb36d4c3",
"id": "9b0a661a",
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -13,7 +13,7 @@
{
"cell_type": "code",
"execution_count": 2,
"id": "e2c275e5",
"id": "58969dda",
"metadata": {},
"outputs": [
{
Expand All @@ -35,13 +35,13 @@
"ds1 = mv.Dataset(ds1_path)\n",
"ds2 = mv.Dataset(ds2_path)\n",
"ds_m = mv.Dataset()\n",
"ds_m.merge(\"max\", ds1, ds2)"
"ds_m.merge(\"max\", [ds1, ds2])"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "7aa4397c",
"id": "476c98a6",
"metadata": {},
"outputs": [
{
Expand All @@ -65,7 +65,7 @@
{
"cell_type": "code",
"execution_count": 3,
"id": "970facc2",
"id": "5d77850b",
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -80,7 +80,7 @@
{
"cell_type": "code",
"execution_count": 9,
"id": "157f375e",
"id": "65585fe5",
"metadata": {},
"outputs": [],
"source": [
Expand Down
Loading

0 comments on commit 3997023

Please sign in to comment.