├── MANIFEST.in ├── Makefile ├── README.md ├── pypotree.py ├── requirements.txt ├── setup.py └── src └── potree.colab.js /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include requirements.txt 3 | include Makefile 4 | include pypotree.py 5 | include src/* 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -g -O3 -DNDEBUG -DDONT_USE_TEST_MAIN 2 | CPPFLAGS = -g -O3 -fpermissive -DNDEBUG -DDONT_USE_TEST_MAIN 3 | 4 | default: binpot 5 | 6 | 7 | HERE=$(realpath ./) 8 | 9 | 10 | binpot: 11 | git clone https://github.com/cmla/PotreeConverter.git src/potreeconverter 12 | cd src/potreeconverter && make PotreeConverter 13 | ## patch for colab 14 | cp $(HERE)/src/potree.colab.js src/potreeconverter/PotreeConverter/resources/page_template/libs/potree/ 15 | ## copy to bin 16 | mkdir -p $(HERE)/bin/ 17 | cp $(HERE)/src/potreeconverter/build/PotreeConverter/PotreeConverter $(HERE)/bin/ 18 | cp -r $(HERE)/src/potreeconverter/PotreeConverter/resources $(HERE)/bin/ 19 | 20 | 21 | clean: 22 | -rm -f -r bin 23 | cd src/potreeconverter && make clean 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyPotree (potree for jupyter notebooks and colab) 2 | 3 | Allows to insert potree cells into jupyter and colab notebooks 4 | 5 | Gabriele Facciolo, CMLA 2019 6 | 7 | [![Try it on Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/1It3EbWy9W8Xf65ikP-_tpkVdJRmvwTQT) 8 | 9 | 10 | # Installation and dependencies 11 | 12 | The main source code repository for this project is https://github.com/cmla/pypotree 13 | It is written in Python and contains Potree and PotreeConverter. It was tested with Python 3.5 and 3.6. 14 | 15 | `pypotree` requires C and C++ developement tools. 16 | 17 | `pypotree` can be installed with `pip`: 18 | 19 | pip install pypotree 20 | 21 | # Usage 22 | 23 | In Colab: 24 | 25 | import pypotree 26 | import numpy as np 27 | xyz = np.random.random((100000,3)) 28 | cloudpath = pypotree.generate_cloud_for_display(xyz) 29 | pypotree.display_cloud_colab(cloudpath) 30 | 31 | 32 | In a Jupyter notebook: 33 | 34 | import pypotree 35 | import numpy as np 36 | xyz = np.random.random((100000,3)) 37 | cloudpath = pypotree.generate_cloud_for_display(xyz) 38 | pypotree.display_cloud(cloudpath) 39 | -------------------------------------------------------------------------------- /pypotree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # vim: set fileencoding=utf-8 3 | # pylint: disable=C0103 4 | 5 | """ 6 | Module to insert potree in jupyter notebooks and colab 7 | 8 | Copyright (C) 2019, Gabriele Facciolo 9 | """ 10 | 11 | from os import path 12 | BIN = path.dirname(__file__)+'/bin' 13 | 14 | 15 | # this function displays a 3D point cloud using the potree viewer 16 | def generate_cloud_for_display(xyz): 17 | """ 18 | Display a point cloud inside a jupyter IFrame 19 | 20 | Arguments: 21 | xyz: a Nx3 matrix containing the 3D positions of N points 22 | """ 23 | import os 24 | import shutil 25 | import numpy as np 26 | 27 | # note: if you want to add color intensities to your points, use a Nx4 array and then change 28 | # the "-parse" option below to xyzi. Similarly for RGB color, save an Nx6 array 29 | 30 | # clear output dir 31 | #try: 32 | # shutil.rmtree('point_clouds') 33 | #except FileNotFoundError: 34 | # pass 35 | import uuid 36 | unique_dirname = str(uuid.uuid4())[:6] 37 | 38 | # dump data and convert 39 | np.savetxt(".tmp.txt", xyz) 40 | print("{BIN}/PotreeConverter .tmp.txt -f xyz -o point_clouds -p {idd} --material ELEVATION --edl-enabled --overwrite".format(BIN=BIN, idd=unique_dirname)) 41 | os.system("{BIN}/PotreeConverter .tmp.txt -f xyz -o point_clouds -p {idd} --material ELEVATION --edl-enabled --overwrite".format(BIN=BIN, idd=unique_dirname)) 42 | 43 | 44 | return ('{idd}'.format(idd=unique_dirname)) 45 | 46 | 47 | 48 | 49 | 50 | def display_cloud(path, width=980, height=800): 51 | """ 52 | Display a point cloud in the path generated by generate_cloud_for_display 53 | Arguments: 54 | xyz: a Nx3 matrix containing the 3D positions of N points 55 | width=980, height=800: size of the canvas 56 | """ 57 | from IPython.display import IFrame 58 | return IFrame('point_clouds/'+path+'.html', width=width, height=height) 59 | 60 | 61 | 62 | 63 | port = 0 64 | 65 | def display_cloud_colab(xyz): 66 | """ 67 | Display a point cloud in the path generated by generate_cloud_for_display 68 | 69 | Arguments: 70 | xyz: a Nx3 matrix containing the 3D positions of N points 71 | """ 72 | 73 | global port 74 | 75 | if port == 0: 76 | import portpicker 77 | import threading 78 | import socket 79 | import IPython 80 | 81 | from six.moves import socketserver 82 | from six.moves import SimpleHTTPServer 83 | 84 | import time 85 | 86 | class V6Server(socketserver.TCPServer): 87 | address_family = socket.AF_INET6 88 | 89 | class Handler(SimpleHTTPServer.SimpleHTTPRequestHandler): 90 | def do_GET(self): 91 | #super().do_GET() 92 | self.send_response(200) 93 | # If the response should not be cached in the notebook for 94 | # offline access: 95 | self.send_header('x-colab-notebook-cache-control', 'no-cache') 96 | self.end_headers() 97 | 98 | #self.wfile.write(b'''hola!''') 99 | self.wfile.write(b''' 100 | document.querySelector('#output-area').appendChild(document.createTextNode('Script result!')); 101 | ''') 102 | #global port 103 | port = portpicker.pick_unused_port() 104 | 105 | ### SERVING ALL THE FILES 106 | Handler = SimpleHTTPServer.SimpleHTTPRequestHandler 107 | 108 | def server_entry(): 109 | httpd = V6Server(('::', port), Handler) 110 | # Handle a single request then exit the thread. 111 | httpd.serve_forever() 112 | 113 | thread = threading.Thread(target=server_entry) 114 | thread.start() 115 | 116 | print ("server on port {}: thread {} ".format( port, thread ) ) 117 | 118 | 119 | text = open('point_clouds/{}.html'.format(xyz) ).read() 120 | 121 | pointcloudpath='https://localhost:{port}/point_clouds/pointclouds/{xyz}'.format(port=port, xyz=xyz) 122 | 123 | print (pointcloudpath) 124 | 125 | newtext = text.replace('src="', 'src="https://localhost:{port}/point_clouds/'.format(port=port)) 126 | newtext = newtext.replace('href="', 'href="https://localhost:{port}/point_clouds/'.format(port=port)) 127 | #newtext = newtext.replace('"pointclouds/', '"https://localhost:{port}/pointclouds/'.format(port=port)) 128 | newtext = newtext.replace('pointclouds/{}'.format(xyz), pointcloudpath) 129 | 130 | newtext = newtext.replace('width: 100%; height: 100%;', 'width: 100%; height: 500px;') 131 | newtext = newtext.replace('libs/potree/potree.js', 'libs/potree/potree.colab.js' ) 132 | 133 | import IPython 134 | 135 | return IPython.display.HTML(newtext) 136 | 137 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | from codecs import open 4 | from setuptools import setup 5 | from setuptools.command import develop, build_py 6 | 7 | 8 | def readme(): 9 | with open('README.md', 'r', 'utf-8') as f: 10 | return f.read() 11 | 12 | 13 | class CustomDevelop(develop.develop, object): 14 | """ 15 | Class needed for "pip install -e ." 16 | """ 17 | def run(self): 18 | subprocess.check_call("make", shell=True) 19 | super(CustomDevelop, self).run() 20 | 21 | 22 | class CustomBuildPy(build_py.build_py, object): 23 | """ 24 | Class needed for "pip install srtm4" 25 | """ 26 | def run(self): 27 | super(CustomBuildPy, self).run() 28 | subprocess.check_call("make", shell=True) 29 | subprocess.check_call("cp -r bin build/lib/", shell=True) 30 | 31 | 32 | requirements = [ 'numpy', 33 | ] 34 | 35 | 36 | setup(name="pypotree", 37 | version="1.0.5", 38 | description='Potree visualization for jypyter notebooks', 39 | long_description=readme(), 40 | long_description_content_type='text/markdown', 41 | url='https://github.com/cmla/pypotree', 42 | author='Gabriele Facciolo', 43 | author_email='gfacciol@gmail.com', 44 | py_modules=['pypotree'], 45 | install_requires=requirements, 46 | cmdclass={'develop': CustomDevelop, 47 | 'build_py': CustomBuildPy}, 48 | include_package_data=True, 49 | python_requires='>=2.7', 50 | zip_safe=False) 51 | --------------------------------------------------------------------------------