-
Notifications
You must be signed in to change notification settings - Fork 14
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
add nerf viewer #40
base: main
Are you sure you want to change the base?
add nerf viewer #40
Changes from 2 commits
ab04da5
7a31afc
5c50bc3
1e4735b
bce618d
3198d9b
75e9ecf
af5a7df
4f7406d
d537654
5b76711
028f0f3
08a019e
0ffe214
caaf853
2f73d9d
cc9c561
e0641d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,85 @@ | ||||||
# Nerf Viewer Quick Start | ||||||
|
||||||
## 1 Client | ||||||
|
||||||
### 1.1 Install Dependencies | ||||||
|
||||||
#### 1.1.1 Install NVM | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NVM is not a requirement but a recommendation. Will it be better for users to choose their own method to install node.js? And Install node.js 18.15.
It's suggested to use `NVM` to install and manage `node.js` installations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The original version of README.md is for debug purpose. In the lastest doc, the installation guide has been replaced to: The |
||||||
|
||||||
Please follow the instructions in: | ||||||
|
||||||
https://juejin.cn/post/7120267871950733320/ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This a blog in Chinese not suitable for non-Chinese users and only for windows platform. For i18n, post official installation instructions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The original version of README.md is for debug purpose. In the lastest doc, the link for nvm installation guidelines has been replaced from https://juejin.cn/post/7120267871950733320/ to https://heynode.com/tutorial/install-nodejs-locally-nvm/. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have any installation instruction in English? Say https://github.com/nvm-sh/nvm#installing-and-updating. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The original version of README.md is for debug purpose. In the lastest doc, the installation instructions has been updated so that it is suitable for non-chinese users. |
||||||
|
||||||
#### 1.1.2 Install Node | ||||||
|
||||||
Once you have nvm correctly configured, install node 18.15.0 using the commands below: | ||||||
|
||||||
```shell | ||||||
# install node 18.15.0 | ||||||
nvm install 18.15.0 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nodejs 18.15 could be precise enough. Don't pin the patch version. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The patch version has been removed: |
||||||
|
||||||
# activate node 18.15.0 | ||||||
nvm use 18.15.0 | ||||||
``` | ||||||
|
||||||
#### 1.1.3 Install Node Packages | ||||||
|
||||||
|
||||||
```shell | ||||||
# make sure that your current working directory is 'client' | ||||||
cd ./client | ||||||
|
||||||
# install node packages using configuration in client/package.json | ||||||
npm install | ||||||
``` | ||||||
|
||||||
### 1.2 Start Viewer | ||||||
|
||||||
Build and run the viewer using: | ||||||
|
||||||
```shell | ||||||
# make sure that your current working directory is 'client' | ||||||
cd ./client | ||||||
|
||||||
# start the viewer | ||||||
npm start | ||||||
``` | ||||||
|
||||||
Then, check whether viewer is successfully compiled: | ||||||
|
||||||
 | ||||||
|
||||||
Visit http://localhost:3000/ in the browser: | ||||||
|
||||||
 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shall we replace it with new vierwer screenshot? The coordinate axis on upper right corner is tilted. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The original version of README.md is for debug purpose. In the lastest doc, the screenshots have been removed. |
||||||
|
||||||
## 2 Bridge Server | ||||||
|
||||||
### 2.1 Install Dependencies | ||||||
|
||||||
```shell | ||||||
|
||||||
cd ./bridge_server | ||||||
|
||||||
# create a virtual environment | ||||||
conda create -n BridgeServer python=3.7 | ||||||
|
||||||
conda activate BridgeServer | ||||||
|
||||||
# install dependencies | ||||||
pip install -r ./requirements.txt | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have a requirements folder in the root directory of the repository, where many different requirements files are named according to their functionality and placed here, such as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For requriements, the dependencies list of server has been moved from For paths, they are now relative to the root directory of the repository, e.g., Appreciate for the suggestion. |
||||||
``` | ||||||
|
||||||
#### 2.2 Start Server | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "###" would be enough There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry for the typo. The title hierarchy issue has been fixed. |
||||||
|
||||||
``` | ||||||
python ./run_viewer.py | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
We often put files with a main function program entry point in the |
||||||
``` | ||||||
|
||||||
Check whether server is successfully deployed: | ||||||
|
||||||
 | ||||||
|
||||||
Open the browser and visit http://localhost:3000/. Then, check whether the server is connected with the viewer: | ||||||
|
||||||
 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.idea | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if we merge this file with the .gitignore file in the root directory? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The original .gitignore file is for debug purpose only. It has been removed now. |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,23 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Actions are a series of keys that determine how data flows | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"""for viewer""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
UPDATE_CAMERA_TRANSLATION = "UPDATE_CAMERA_TRANSLATION" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
UPDATE_CAMERA_ROTATION = "UPDATE_CAMERA_ROTATION" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
UPDATE_CAMERA_FOV = "UPDATE_CAMERA_FOV" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
UPDATE_RESOLUTION = "UPDATE_RESOLUTION" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
UPDATE_RENDER_TYPE = "UPDATE_RENDER_TYPE" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"""for nerf backend""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# update the scene state | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
UPDATE_STATE = "UPDATE_STATE" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
UPDATE_RENDER_RESULT = "UPDATE_RENDER_RESULT" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I recommend to refactor those constants to an Enum class.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrapped actions into enums. |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,85 @@ | ||||||||
import atexit | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shall we rename it to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||
import os | ||||||||
import signal | ||||||||
import socket | ||||||||
import subprocess | ||||||||
import sys | ||||||||
import threading | ||||||||
import time | ||||||||
|
||||||||
import server | ||||||||
|
||||||||
from typing import Optional | ||||||||
|
||||||||
|
||||||||
def is_port_open(port: int): | ||||||||
# check whether the given port is open | ||||||||
try: | ||||||||
sock = socket.socket() | ||||||||
sock.bind(("", port)) | ||||||||
sock.close() | ||||||||
|
||||||||
return True | ||||||||
except OSError: | ||||||||
return False | ||||||||
|
||||||||
|
||||||||
def get_free_port(default_port: int = None): | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For users, there are two possible scenarios. In the first scenario, the user does not care which port is used as long as the service is started successfully and the port used is returned. The current function can meet this requirement. In the second scenario, the user may be required to use a specific port, such as port 80. If the target port is occupied, an exception should be thrown and there is no need to continue running. Considering these two scenarios, we can modify the parameter list of this function, for example:
In the first example, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated port binding logics: if the user specified a port, the server will check whether the port is already in use and then create connections using zmq, otherwise the server will automatically find an available port. |
||||||||
if default_port: | ||||||||
if is_port_open(default_port): | ||||||||
return default_port | ||||||||
sock = socket.socket() | ||||||||
sock.bind(("", 0)) | ||||||||
port = sock.getsockname()[1] | ||||||||
|
||||||||
return port | ||||||||
|
||||||||
|
||||||||
def run_bridge_server_as_subprocess( | ||||||||
websocket_port: int, | ||||||||
zmq_port: Optional[int] = None, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ChatGPT: Union[int, None] explicitly specifies that the variable can be either an integer or None. It does not allow any other type to be assigned to it. On the other hand, Optional[int] is actually an alias for Union[int, None], which means it represents the same set of possible types. However, Optional[int] does not explicitly indicate that the variable cannot be assigned any other type. Therefore, using Union[int, None] is more specific and explicit, and may help prevent type-related bugs in your code. However, both Optional[int] and Union[int, None] can be used interchangeably in most cases. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I doubt the response from ChatGPT. According to PEP484 which is the official definitions of python typing hints,
So do the documentation of typing.Optional:
It's also recommended to use There is a related disccusion at StackOverflow says why
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. from ChatGPT:
Yes, there is a difference, but it's mainly in how they are expressed. Both Optional[int] and Union[int, None] represent the same type, which is either an integer or None. The difference lies in how you express this type annotation. Optional[int] is a more concise and readable way to express a type that can either be an integer or None. It is equivalent to Union[int, None]. The Optional type is just a shorthand for a Union with None as one of the alternatives. In Python, you can use both Optional[int] and Union[int, None] to annotate a variable or a function argument that accepts either an integer or None. However, it's generally recommended to use the Optional type for readability and simplicity. from typing import Optional, Union
def example_optional(x: Optional[int]) -> None:
print(x)
def example_union(x: Union[int, None]) -> None:
print(x)
example_optional(42) # Prints 42
example_optional(None) # Prints None
example_union(42) # Prints 42
example_union(None) # Prints None In both cases, the functions example_optional and example_union accept either an integer or None as their argument, and both function calls produce the same output. And I asked ChatGPT about the practical difference in static type checkers
No, there is no difference between Optional[int] and Union[int, None] when using Python type checkers like mypy. Optional[int] is actually a shorthand for Union[int, None], and they are equivalent in terms of type checking. Both indicate that the annotated variable can either be an integer or None. Using Optional[int] is generally preferred as it is more concise and easier to read. However, if you are using an older version of Python or a type checker that does not support Optional, you can use Union[int, None] instead. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since Optional[int] is equivalent to Union[int, None] whereas more concise and readable, we're using Optional as the type annotation. |
||||||||
ip_address: str = "127.0.0.1", | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace the default ip address to 0.0.0.0 makes zmq fail to connect with client.
|
||||||||
): | ||||||||
# run bridge server as a sub-process | ||||||||
args = [sys.executable, "-u", "-m", server.__name__] | ||||||||
|
||||||||
# find an available port for zmq | ||||||||
if zmq_port is None: | ||||||||
zmq_port = get_free_port() | ||||||||
print(f"Using ZMQ port: {zmq_port}") | ||||||||
|
||||||||
args.append("--zmq-port") | ||||||||
args.append(str(zmq_port)) | ||||||||
args.append("--websocket-port") | ||||||||
args.append(str(websocket_port)) | ||||||||
args.append("--ip-address") | ||||||||
args.append(str(ip_address)) | ||||||||
|
||||||||
process = subprocess.Popen(args, start_new_session=True) | ||||||||
|
||||||||
def cleanup(process): | ||||||||
process.kill() | ||||||||
process.wait() | ||||||||
|
||||||||
def poll_process(): | ||||||||
""" | ||||||||
Continually check to see if the viewer bridge server process is still running and has not failed. | ||||||||
If it fails, alert the user and exit the entire program. | ||||||||
""" | ||||||||
while process.poll() is None: | ||||||||
time.sleep(0.5) | ||||||||
|
||||||||
message = "The bridge server subprocess failed." | ||||||||
cleanup(process) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||
|
||||||||
# windows system do not have signal.SIGKILL | ||||||||
# os.kill(os.getpid(), signal.SIGKILL) | ||||||||
os.kill(os.getpid(), signal.SIGINT) | ||||||||
|
||||||||
watcher_thread = threading.Thread(target=poll_process) | ||||||||
watcher_thread.daemon = True | ||||||||
watcher_thread.start() | ||||||||
# clean up process when it has shut down | ||||||||
atexit.register(cleanup, process) | ||||||||
|
||||||||
return zmq_port |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
pyzmq==25.0.2 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
pyngrok==5.2.1 | ||
tornado | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pin tornado's major version to prevent incompatibility (i.e. |
||
umsgpack==0.1.0 | ||
opencv-contrib-python==4.5.3.56 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from viewer_state import ViewerState | ||
|
||
|
||
def run_viewer(): | ||
""" | ||
start the viewer | ||
""" | ||
viewer_state = ViewerState() | ||
while True: | ||
viewer_state.update_scene() | ||
|
||
|
||
if __name__ == "__main__": | ||
run_viewer() |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,173 @@ | ||||||||||||||||||||||||||
import pickle | ||||||||||||||||||||||||||
import sys | ||||||||||||||||||||||||||
from typing import List, Optional, Tuple | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
# for web networking | ||||||||||||||||||||||||||
import tornado.gen | ||||||||||||||||||||||||||
import tornado.ioloop | ||||||||||||||||||||||||||
import tornado.web | ||||||||||||||||||||||||||
import tornado.websocket | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
# for data conversion | ||||||||||||||||||||||||||
import umsgpack | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
# ZeroMQ: for messaging | ||||||||||||||||||||||||||
import zmq | ||||||||||||||||||||||||||
import zmq.eventloop.ioloop | ||||||||||||||||||||||||||
from zmq.eventloop.zmqstream import ZMQStream | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
from pyngrok import ngrok | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
from actions import \ | ||||||||||||||||||||||||||
UPDATE_CAMERA_FOV, UPDATE_CAMERA_ROTATION, UPDATE_CAMERA_TRANSLATION, \ | ||||||||||||||||||||||||||
UPDATE_RENDER_TYPE, UPDATE_RESOLUTION, UPDATE_STATE, UPDATE_RENDER_RESULT | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
from state import State | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
class WebSocketHandler(tornado.websocket.WebSocketHandler): # pylint: disable=abstract-method | ||||||||||||||||||||||||||
"""for receiving and sending commands from/to the viewer""" | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def __init__(self, *args, **kwargs): | ||||||||||||||||||||||||||
self.bridge = kwargs.pop("bridge") | ||||||||||||||||||||||||||
super().__init__(*args, **kwargs) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def check_origin(self, origin: str) -> bool: | ||||||||||||||||||||||||||
return True | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def open(self, *args: str, **kwargs: str): | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
self.bridge.websocket_pool.add(self) | ||||||||||||||||||||||||||
print("opened:", self, file=sys.stderr) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
async def on_message(self, message: bytearray): # pylint: disable=invalid-overridden-method | ||||||||||||||||||||||||||
"""parses the message from viewer and calls the appropriate function""" | ||||||||||||||||||||||||||
data = message | ||||||||||||||||||||||||||
unpacked_message = umsgpack.unpackb(message) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
type = unpacked_message["type"] | ||||||||||||||||||||||||||
data = unpacked_message["data"] | ||||||||||||||||||||||||||
# type_ = m["type"] | ||||||||||||||||||||||||||
# path = list(filter(lambda x: len(x) > 0, m["path"].split("/"))) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
if type == UPDATE_CAMERA_TRANSLATION: | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use Enum class (in
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrapped actions into enums. |
||||||||||||||||||||||||||
self.bridge.state.camera_translation = data | ||||||||||||||||||||||||||
elif type == UPDATE_CAMERA_ROTATION: | ||||||||||||||||||||||||||
self.bridge.state.camera_rotation = data | ||||||||||||||||||||||||||
elif type == UPDATE_RENDER_TYPE: | ||||||||||||||||||||||||||
self.bridge.state.render_type = data | ||||||||||||||||||||||||||
elif type == UPDATE_CAMERA_FOV: | ||||||||||||||||||||||||||
self.bridge.state.camera_fov = data | ||||||||||||||||||||||||||
elif type == UPDATE_RESOLUTION: | ||||||||||||||||||||||||||
self.bridge.state.resolution = data | ||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||
# TODO: handle exception | ||||||||||||||||||||||||||
pass | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def on_close(self) -> None: | ||||||||||||||||||||||||||
self.bridge.websocket_pool.remove(self) | ||||||||||||||||||||||||||
print("closed: ", self, file=sys.stderr) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
class ZMQWebSocketBridge: | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
context = zmq.Context() # pylint: disable=abstract-class-instantiated | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def __init__(self, zmq_port: int, websocket_port: int, ip_address: str): | ||||||||||||||||||||||||||
self.zmq_port = zmq_port | ||||||||||||||||||||||||||
self.websocket_pool = set() | ||||||||||||||||||||||||||
self.app = self.make_app() | ||||||||||||||||||||||||||
self.ioloop = tornado.ioloop.IOLoop.current() | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
# zmq | ||||||||||||||||||||||||||
zmq_url = f"tcp://{ip_address}:{self.zmq_port:d}" | ||||||||||||||||||||||||||
self.zmq_socket, self.zmq_stream, self.zmq_url = self.setup_zmq(zmq_url) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
# websocket | ||||||||||||||||||||||||||
listen_kwargs = {"address" : "0.0.0.0"} | ||||||||||||||||||||||||||
self.app.listen(websocket_port, **listen_kwargs) | ||||||||||||||||||||||||||
self.websocket_port = websocket_port | ||||||||||||||||||||||||||
self.websocket_url = f"0.0.0.0:{self.websocket_port}" | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
# state | ||||||||||||||||||||||||||
self.state = State() | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def __str__(self) -> str: | ||||||||||||||||||||||||||
class_name = self.__class__.__name__ | ||||||||||||||||||||||||||
return f'{class_name} using zmq_port="{self.zmq_port}" and websocket_port="{self.websocket_port}"' | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def make_app(self): | ||||||||||||||||||||||||||
# create an application for the websocket server | ||||||||||||||||||||||||||
return tornado.web.Application([(r"/", WebSocketHandler, {"bridge": self})]) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def handle_zmq(self, frames: List[bytes]): | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
type_ = frames[0].decode("utf-8") | ||||||||||||||||||||||||||
data = frames[1] | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
if type_ == UPDATE_RENDER_RESULT: | ||||||||||||||||||||||||||
self.forward_to_websockets(frames) | ||||||||||||||||||||||||||
self.zmq_socket.send(umsgpack.packb(b"ok")) | ||||||||||||||||||||||||||
elif type_ == UPDATE_STATE: | ||||||||||||||||||||||||||
serialized = pickle.dumps(self.state) | ||||||||||||||||||||||||||
self.zmq_socket.send(serialized) | ||||||||||||||||||||||||||
elif type_ == "ping": | ||||||||||||||||||||||||||
self.zmq_socket.send(umsgpack.packb(b"ping received")) | ||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||
print("type: " + str(type_)) | ||||||||||||||||||||||||||
self.zmq_socket.send(umsgpack.packb(b"error: unknown command")) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def forward_to_websockets( | ||||||||||||||||||||||||||
self, | ||||||||||||||||||||||||||
frames: Tuple[str, str, bytes], | ||||||||||||||||||||||||||
websocket_to_skip: Optional[WebSocketHandler] = None | ||||||||||||||||||||||||||
): | ||||||||||||||||||||||||||
"""forward a zmq message to all websockets""" | ||||||||||||||||||||||||||
"""nerf backend -> viewer""" | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment |
||||||||||||||||||||||||||
_type, _data = frames # cmd, data | ||||||||||||||||||||||||||
for websocket in self.websocket_pool: | ||||||||||||||||||||||||||
if websocket_to_skip and websocket == websocket_to_skip: | ||||||||||||||||||||||||||
pass | ||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||
websocket.write_message(_data, binary=True) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def setup_zmq(self, url: str): | ||||||||||||||||||||||||||
"""setup a zmq socket and connect it to the given url""" | ||||||||||||||||||||||||||
zmq_socket = self.context.socket(zmq.REP) # pylint: disable=no-member | ||||||||||||||||||||||||||
zmq_socket.bind(url) | ||||||||||||||||||||||||||
zmq_stream = ZMQStream(zmq_socket) | ||||||||||||||||||||||||||
zmq_stream.on_recv(self.handle_zmq) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return zmq_socket, zmq_stream, url | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def run(self): | ||||||||||||||||||||||||||
"""starts and runs the websocet bridge""" | ||||||||||||||||||||||||||
self.ioloop.start() | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def run_bridge_server( | ||||||||||||||||||||||||||
zmq_port: int = 6000, | ||||||||||||||||||||||||||
websocket_port: int = 4567, | ||||||||||||||||||||||||||
ip_address: str = "127.0.0.1", | ||||||||||||||||||||||||||
use_ngrok: bool = False | ||||||||||||||||||||||||||
): | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extra indents
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extra indents have been fixed by pre-commit. |
||||||||||||||||||||||||||
# whether expose the zmq port | ||||||||||||||||||||||||||
if use_ngrok: | ||||||||||||||||||||||||||
http_tunnel = ngrok.connect(addr=str(zmq_port), proto="tcp") | ||||||||||||||||||||||||||
print(http_tunnel) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
bridge = ZMQWebSocketBridge( | ||||||||||||||||||||||||||
zmq_port=zmq_port, | ||||||||||||||||||||||||||
websocket_port=websocket_port, | ||||||||||||||||||||||||||
ip_address=ip_address | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
print(bridge) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||
bridge.run() | ||||||||||||||||||||||||||
except KeyboardInterrupt: | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why |
||||||||||||||||||||||||||
pass | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
if __name__ == "__main__": | ||||||||||||||||||||||||||
run_bridge_server() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nerf -> NeRF
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Glitch fixed: Nerf Viewer -> XRNeRF Viewer