diff --git a/dvis.egg-info/PKG-INFO b/dvis.egg-info/PKG-INFO new file mode 100644 index 0000000..6ce402f --- /dev/null +++ b/dvis.egg-info/PKG-INFO @@ -0,0 +1,145 @@ +Metadata-Version: 2.1 +Name: dvis +Version: 0.9.0.2 +Summary: The best web-based visualizer +Home-page: https://github.com/SirWyver/dvis +Author: Norman Müller +Author-email: norman.mueller@tum.de +License: UNKNOWN +Platform: UNKNOWN +Classifier: Programming Language :: Python :: 3 +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Requires-Python: >=3.6 +Description-Content-Type: text/markdown + +

+

DVIS: 3D Visualizations made easy

+

Visualize your data with just one line of code

+

Python -> Browser

+ + +

+ + + + +

+ +# 💻 Usage +```python +from dvis import dvis + +dvis("mesh.obj") # load file +dvis(point_cloud, vs=0.03) # point cloud with specific voxel size +dvis(bboxes,'bbox', c=3, name='my_boxes') # show colored boxes +dvis(np.array([0,0,0,1,2,3]), 'vec') # vector from origin to (1,2,3) +dvis(transform, 'transform') # display transformation +dvis(img, 'img') # display an image using visdom +``` +Check out more examples in `./examples` +``` +python examples/meshes.py +python examples/point_clouds.py +python examples/... +``` + + +# News +* 0.8.8.0: Bumped dependencies version, custom port support + ``` + dvis(port=) # set port the client is sending on + dvis(vis_port=) # set the port for visdom + ``` + +* 0.8.7.0: CLI for server: To start the server, use run + ``` + dvis-server [--no_visdom] + ``` +* 0.8.6.0: Histogram support using plotly + ``` + dvis(array, "hist", mi=0.1,ma=0.8, nbins=10, name="Example histogram") + ``` + +* 0.8.4.0: Label and range image support, auto-format for img + ``` + dvis(label_img [fmt='xyl']) # visualizses img of labels + dvis(depth_map [fmt='xyr', cm='jet']) # visualizes an image of continuous values using cv2 color map + dvis(heat_map [fmt='xyr', cm='hot']) + ``` + +# 🚀 Getting started + +## 1. Install the `dvis` package: +Via pypi: +``` +pip install dvis +``` +or from source: +``` +git clone git@github.com:SirWyver/dvis.git +cd dvis +pip install . +``` +## 2. Start the web server +``` +# From the dvis repository folder: +dvis-server +``` +or manually +``` +cd server +python server.py +``` + +Verify you can open http://localhost:5001/ and see something like this: + +

+ +

+ +Optionally, also start [visdom](https://github.com/fossasia/visdom) to display images/videos/charts: +``` +visdom -p 4999 +``` +The visdom server should be accessible at http://localhost:4999/. + +Try out the client +```python +import numpy as np +from dvis import dvis +dvis(np.random.rand(1000,6), s=0.03) # sends randomly colored 1000x3 point cloud to the 3d server +dvis("static/icon.png","img") # sends an image to the 2d server + +``` +Verify you can see a colored point cloud + +# 📖 Documentation +For an overview of available commands check out the [documentation](https://sirwyver.github.io/dvis_docu/) + +## Shotcuts +| Shortcut | Description | +|----------|---------------------------| +| **Editor** | | +| w | Translate | +| e | Rotate | +| r | Scale | +| z | Undo | +| f | Focus | +| **DVIS** | | +| v | Show/hide selected object | +| 1-5 | Toggle layer 1-5 | +| 0 | Toggle all layers | +| Shift + 0-5 | Show layer 0-5 add. | +| g | Show/hide grid & axes helper | +| n | Next keyframe | +| b | Previous keyframe | +| . | Next frame | +| , | Previous frame | +| t | Switch camera | +| [ | Download screenshot | + + + + + diff --git a/dvis.egg-info/SOURCES.txt b/dvis.egg-info/SOURCES.txt new file mode 100644 index 0000000..fa67633 --- /dev/null +++ b/dvis.egg-info/SOURCES.txt @@ -0,0 +1,14 @@ +README.md +setup.py +dvis/__init__.py +dvis/dvis.py +dvis/dvis_cli.py +dvis/dvis_client.py +dvis/dvis_client_old.py +dvis/utils.py +dvis.egg-info/PKG-INFO +dvis.egg-info/SOURCES.txt +dvis.egg-info/dependency_links.txt +dvis.egg-info/entry_points.txt +dvis.egg-info/requires.txt +dvis.egg-info/top_level.txt \ No newline at end of file diff --git a/dvis.egg-info/dependency_links.txt b/dvis.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/dvis.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/dvis.egg-info/entry_points.txt b/dvis.egg-info/entry_points.txt new file mode 100644 index 0000000..8ec4b8a --- /dev/null +++ b/dvis.egg-info/entry_points.txt @@ -0,0 +1,4 @@ +[console_scripts] +dvis = dvis.dvis_cli:dvis_cli +dvis-server = dvis.dvis_cli:dvis_server_cli + diff --git a/dvis.egg-info/requires.txt b/dvis.egg-info/requires.txt new file mode 100644 index 0000000..9f5fce9 --- /dev/null +++ b/dvis.egg-info/requires.txt @@ -0,0 +1,9 @@ +numpy +pillow>=10.0 +trimesh +simple-websocket>=0.2.0 +python-socketio>=4.6.1 +eventlet +jinja2>=3.0.2 +werkzeug>=2.0.3 +opencv-python diff --git a/dvis.egg-info/top_level.txt b/dvis.egg-info/top_level.txt new file mode 100644 index 0000000..bc52aea --- /dev/null +++ b/dvis.egg-info/top_level.txt @@ -0,0 +1 @@ +dvis diff --git a/dvis/__pycache__/__init__.cpython-38.pyc b/dvis/__pycache__/__init__.cpython-38.pyc index 8e3ac9c..b350e38 100644 Binary files a/dvis/__pycache__/__init__.cpython-38.pyc and b/dvis/__pycache__/__init__.cpython-38.pyc differ diff --git a/dvis/__pycache__/dvis.cpython-38.pyc b/dvis/__pycache__/dvis.cpython-38.pyc index a489595..0f20b48 100644 Binary files a/dvis/__pycache__/dvis.cpython-38.pyc and b/dvis/__pycache__/dvis.cpython-38.pyc differ diff --git a/dvis/__pycache__/dvis_client.cpython-38.pyc b/dvis/__pycache__/dvis_client.cpython-38.pyc index 3d99ff9..64340af 100644 Binary files a/dvis/__pycache__/dvis_client.cpython-38.pyc and b/dvis/__pycache__/dvis_client.cpython-38.pyc differ diff --git a/dvis/__pycache__/utils.cpython-38.pyc b/dvis/__pycache__/utils.cpython-38.pyc index b88913f..19f2102 100644 Binary files a/dvis/__pycache__/utils.cpython-38.pyc and b/dvis/__pycache__/utils.cpython-38.pyc differ diff --git a/dvis/dvis.py b/dvis/dvis.py index 38e9b68..6ea90b6 100644 --- a/dvis/dvis.py +++ b/dvis/dvis.py @@ -15,7 +15,7 @@ torch_float = None torch_double = None -from .dvis_client import send2server, send_payload2server +from .dvis_client import send2server, send_payload2server, send_visdom_command from .dvis_client import send_plotly, sendMesh2server, send_clear, send_config, sendTrack2server, sendCmd2server,sendPose2server,sendInject2server, send_objectKFState, sendCamImage2server from .dvis_client import set_port, set_vis_port import trimesh @@ -50,7 +50,12 @@ def convert_to_nd(data): return np.array(data) except: pass - raise Exception("Data type not understood") + try: + data = np.array(data) + print(f"Converted to np.ndarray: {data.shape}") + return data + except: + raise Exception("Data type not understood") def matplot2PIL(fig): @@ -367,7 +372,7 @@ def dvis_mesh_pc(data, vs=1, c=0, l=0, t=None, name=None, meta=None, ms=None, vi shape=shape, ) -def _image_size(img, s, is_bool=False): +def _image_resize(img, s, is_bool=False): if s!=1: if isinstance(s, (int,float)): if s < 10: @@ -401,11 +406,11 @@ def dvis_img(data, vs=1, c=0, l=[0], t=None, name=None, meta=None, vis_conf=None fn =f"tmp.{suffix}" Connection(hostname).get(remote_path,fn) img = Image.open(fn) - data = np.array(_image_size(img)) + data = np.array(_image_resize(img,s=s)) os.remove(fn) else: img = Image.open(fn) - data = np.array(_image_size(img)) + data = np.array(_image_resize(img,s=s)) if meta is None: meta = {} meta["obj_path"] = fn @@ -447,7 +452,7 @@ def dvis_img(data, vs=1, c=0, l=[0], t=None, name=None, meta=None, vis_conf=None data = (data[...,:3]*255).astype(np.uint8) if s!=1: img = Image.fromarray(data) - img = _image_size(img,s, is_bool=is_bool) + img = _image_resize(img,s, is_bool=is_bool) data = np.array(img) if is_bool: data = data.astype(np.uint8) @@ -768,7 +773,7 @@ def dvis_transform(data, s=1, c=0, l=[0], t=0, name="transform", meta=None, vis_ compression="pkl", ) -def dvis_seq(data, vs=1, c=0, l=[0], t=None, name=None, meta=None, vis_conf=None, cm='default', fmt='seq', mi=None, ma=None): +def dvis_seq(data, vs=1, c=0, l=[0], t=None, name=None, meta=None, vis_conf=None, cm='default', fmt='seq', mi=None, ma=None,s=1): data = convert_to_nd(data) sub_format = None if len(data.shape)==3 or data.shape[-1] == 1: @@ -780,7 +785,7 @@ def dvis_seq(data, vs=1, c=0, l=[0], t=None, name=None, meta=None, vis_conf=None elif sub_format == 'xyr': # range image with # remap default to jet - data = np.stack(visualize_range(data[i], cm=("jet" if cm=='default' else cm), mi=mi, ma=ma) for i in range(data.shape[0])) + data = np.stack(visualize_range(data[i], cm=("jet" if cm=='default' else cm), mi=mi, ma=ma,verbose=i==0) for i in range(data.shape[0])) if data.shape[-1] in [3,4]: # T H W C data = data.transpose(0,3,1,2) if name is None: @@ -806,6 +811,17 @@ def dvis_seq(data, vs=1, c=0, l=[0], t=None, name=None, meta=None, vis_conf=None data[...,:3] = data[...,:3] * alpha_mask + (1.0 - c) * (1-alpha_mask) data = (data[...,:3]*255).astype(np.uint8) data = data.transpose(0,3,1,2) + + if s!=1: + is_bool = data.dtype == bool + resized_data = [] + for img in data: + img = Image.fromarray(img.transpose(1,2,0)) + img = _image_resize(img,s, is_bool=is_bool) + resized_data.append(np.array(img).transpose(2,0,1)) + data = np.stack(resized_data) + if is_bool: + data = data.astype(np.uint8) send2server(data=data, data_format="seq", size=vs, color=c, layers=l, t=t, name=name, meta_data=meta, vis_conf=vis_conf, sub_format=sub_format) @@ -824,7 +840,8 @@ def _infer_format(data): or (hasattr(matplotlib, "figure") and isinstance(data, matplotlib.figure.Figure)) ): fmt = "img" - elif isinstance(data, str): + elif isinstance(data, (Path, str)): + data = str(data) suffix = data.split(".")[-1] if suffix in ["jpeg", "jpg", "png", "gif"]: fmt = "img" @@ -849,7 +866,7 @@ def _infer_format(data): # fmt = "cwhl" else: raise IOError("Data format %s not understood" % str(data.shape)) - elif len(data.shape) == 3: + if len(data.shape) == 3: if (data.shape[0] == 3 or data.shape[2] ==3) or (data.shape[0] == 4 or data.shape[2] ==4): # assume image for convenience fmt = "img" @@ -1093,6 +1110,10 @@ def dvis( if "vis_port" in kwargs: set_vis_port(kwargs["vis_port"]) return + # special commands + if isinstance(data, str) and data in ["clear", "clean", "close"]: + send_visdom_command("close") + return if isinstance(l, int): l = [l] @@ -1134,7 +1155,7 @@ def dvis( elif fmt == "cam": dvis_cam(data, name) elif fmt == "seq": - dvis_seq(data, vs, c, l, t, name, meta, vis_conf, fmt=fmt, cm=kwargs.get("cm",'default'), mi=kwargs.get('mi'), ma=kwargs.get('ma')) + dvis_seq(data, vs, c, l, t, name, meta, vis_conf, fmt=fmt, cm=kwargs.get("cm",'default'), mi=kwargs.get('mi'), ma=kwargs.get('ma'),s=s) elif fmt == "cmd": dvis_cmd(data) diff --git a/dvis/dvis_client.py b/dvis/dvis_client.py index aa559be..2444570 100644 --- a/dvis/dvis_client.py +++ b/dvis/dvis_client.py @@ -13,6 +13,8 @@ PORT = 5001 VIS_PORT = 4999 +visdom_instance = None + def set_port(port): global PORT PORT = port @@ -26,6 +28,12 @@ def encode_to_base64(x): return x return base64.b64encode(x).decode("utf8") +def _get_visdom_instance(): + global visdom_instance + if visdom_instance is None: + visdom_instance = visdom.Visdom(port=VIS_PORT) + return visdom_instance + def send2server(data, data_format, size, color, layers, t, name="", meta_data=None, vis_conf=None, shape="v", compression="gzip", sub_format=None): if data is not None: @@ -38,7 +46,8 @@ def send2server(data, data_format, size, color, layers, t, name="", meta_data=No else: print("Sending group") if data_format in ["hwc", "img", "seq"]: - vis = visdom.Visdom(port=VIS_PORT) + vis = _get_visdom_instance() + if data_format == "seq": for img in data: vis.image(img, opts=dict(store_history=True), win=name) @@ -49,7 +58,7 @@ def send2server(data, data_format, size, color, layers, t, name="", meta_data=No data[np.all(data == 255, 2)] = np.array(color) vis.image(data.transpose(2, 0, 1), opts={"caption": name}) elif data_format in ["gif"]: - vis = visdom.Visdom(port=VIS_PORT) + vis = _get_visdom_instance() vis.text(f'', opts={"caption": name}) else: if compression == "pkl": @@ -58,7 +67,7 @@ def send2server(data, data_format, size, color, layers, t, name="", meta_data=No else: data = pickle.dumps(data) elif compression == "gzip": - data = gzip.compress(data.astype(np.float32).copy(order="C")) + data = gzip.compress(data.astype(np.float32).copy(order="C"), mtime=0) elif compression in ["glb", "obj"]: # meshes tm = data if compression == "glb": @@ -97,7 +106,7 @@ def send2server(data, data_format, size, color, layers, t, name="", meta_data=No def send_plotly(data, layout): - vis = visdom.Visdom(port=VIS_PORT) + vis = _get_visdom_instance() print(f"Sending plolty {data['type']}") vis._send({"data": [data], "layout": layout}) @@ -125,6 +134,10 @@ def send_payload2server( "shape": shape, }, ) +def send_visdom_command(cmd): + vis = _get_visdom_instance() + if cmd == "close": + vis.close() def img_to_base64(image): diff --git a/dvis/utils.py b/dvis/utils.py index 041963a..bdfa20e 100644 --- a/dvis/utils.py +++ b/dvis/utils.py @@ -110,7 +110,7 @@ def get_color_batched(col_vals, cm=None): ) -def visualize_range(cont_label, img_ijs=None, H=None, W=None, cm="jet", mi=None, ma=None): +def visualize_range(cont_label, img_ijs=None, H=None, W=None, cm="jet", mi=None, ma=None,verbose=True): """ cont_label: (H, W) or (N,) or (H,W,1) """ @@ -131,7 +131,7 @@ def visualize_range(cont_label, img_ijs=None, H=None, W=None, cm="jet", mi=None, # convert invalid cont_label vals to 0 x[np.isinf(x)] = 0 x[np.isneginf(x)] = 0 - use_auto_range = ((mi is None) or (ma is None)) + use_auto_range = ((mi is None) and (ma is None)) if mi is None: mi = np.min(x) # get minimum cont_label if ma is None: @@ -139,7 +139,8 @@ def visualize_range(cont_label, img_ijs=None, H=None, W=None, cm="jet", mi=None, if use_auto_range and (mi >= 0 and ma <= 1): mi, ma = 0.0, 1.0 - print(f"Set range: {mi} - {ma}") + if verbose: + print(f"Set range: {mi} - {ma}") x = np.clip(x, mi, ma) x = (x - mi) / max(ma - mi, 1e-8) # normalize to 0~1 x = np.clip((255 * x).astype(np.uint8), 0, 255) diff --git a/requirements.txt b/requirements.txt index c91085a..9b1a9e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,12 +9,11 @@ transforms3d flask>=2.3.2 flask_socketio>=5.3.4 numpy -pillow +pillow>=10.0.0 trimesh simple-websocket>=0.2.0 python-socketio>=4.6.1 eventlet jinja2>=3.0.2 -itsdangerous==2.0.1 -werkzeug==2.0.3 +werkzeug>=2.3.6 opencv-python diff --git a/server/__pycache__/register_mirror.cpython-38.pyc b/server/__pycache__/register_mirror.cpython-38.pyc index 06c7425..11590af 100644 Binary files a/server/__pycache__/register_mirror.cpython-38.pyc and b/server/__pycache__/register_mirror.cpython-38.pyc differ diff --git a/setup.py b/setup.py index f9af9eb..ec78aff 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="dvis", - version="0.8.8.1", + version="0.9.0.3", author="Norman Müller", author_email="norman.mueller@tum.de", url="https://github.com/SirWyver/dvis", @@ -24,10 +24,10 @@ "dvis-server=dvis.dvis_cli:dvis_server_cli"], }, install_requires=[ - "flask>=2.3.2", - "flask_socketio>=5.3.4", + #"Flask>=2.1.2", + #"flask_socketio>=5.3.4", "numpy", - "pillow", + "pillow>=10.0", "trimesh", "simple-websocket>=0.2.0", "python-socketio>=4.6.1", diff --git a/test.py b/test.py index bbb68ef..7e14996 100644 --- a/test.py +++ b/test.py @@ -1,12 +1,26 @@ import numpy as np from dvis import dvis +from PIL import Image +from pathlib import Path +dvis(port=5010) +dvis(vis_port=5011) + + +dvis(Path("static/camera_path.gif")) +dvis("static/dvis_ui.png") +dvis(np.random.randint(0,55,(300,3))) + + +dvis(Image.fromarray(np.random.randint(0,255,size=(300,200,3)).astype(np.uint8))) + +dvis(np.random.rand(1,1,3,100,100)) imgseq = np.random.rand(4,200,200,1) +dvis(imgseq,'seq',s=100) dvis(imgseq, "hist", mi=0.1,ma=0.8, nbins=10, name="lol", c=2) # layout={"title": {"text": "lol"}}) -dvis(imgseq,'seq') dvis([x for x in imgseq]) dvis(imgseq, name='kaka')