google / neuroglancer

WebGL-based viewer for volumetric data
Apache License 2.0
1.02k stars 283 forks source link

2 questions / requests: controlling the key on startup and attaching to a running nglancer server #174

Open Wildcarde opened 4 years ago

Wildcarde commented 4 years ago

First question /request: In a docker framework I'm building out as a combination quick data review tool and development platofrm we are launching a neuroglancer object using python with the neuroglancer.Viewer() command and then spinning to keep the process alive so that we can do some initial setup. While I'm guessing this isn't the only way to do this, I couldn't find a way to spin up a free standing neuroglancer instance and pre-load the configuration settings we want. However I'm working to put this all behind an apache proxy to give the end user a simple plane to talk to and can't control the token used to secure neuroglancer on startup so I can't put a simple reverse proxy layer in front of it. This appears to be due to the fact that instantiating a viewer object immediately has side effects before you can tune what you want. My current plan is to put a configurable proxy (https://github.com/jupyterhub/configurable-http-proxy) in front of the neuroglancer instance and use the run time token generation to populate that but i'd like to avoid the hop if possible. I need this conf proxy for volumes anyway but it would still be helpful to control this for smaller setups. If you could just enable passing token= into the __init__ process that would fix this for our use case. I've included a copy of our neuroglancer launcher shim below.

Second question: Is it possible to attach to a running neuroglancer instance from a remote location, I've got the approach where you instance a neuroglancer viewer object and feed configurations into it working and that works fine if you are doing that from inside a notebook or just spin locking it like we do below. But I'd like to be able to spin up a free standing neuroglancer instance, and then attach to it form a jupyternotebook to use as a hackable development interface. I'm hoping to have a new more capable push up soon to https://github.com/Wildcarde/nglancer-frame that demonstrates at least some of what we are thinking about in this direction.

in dev version of the launcher:

#! /bin/env python

## basic shim to load up neuroglancer in a browser:
import neuroglancer
import logging
from time import sleep
import progproxy

logging.basicConfig(level=logging.DEBUG)
# we are currently using the seunglab hosted neuroglancer static resources
# ideally this would be self hosted for local development against nglancer
logging.info('configuring neuroglancer defaults')
neuroglancer.set_static_content_source(url='https://neuromancer-seung-import.appspot.com')

## set the tornado server that is launched to talk on all ips and at port 8080
neuroglancer.set_server_bind_address('0.0.0.0','8080')

logging.info('starting viewer subprocess')
#setup a viewer with pre-configured defaults and launch.
viewer = neuroglancer.Viewer()

logging.info('viewer token: {}'.format(viewer.token))

logging.info('setting viewers default volume')
#load data from cloudvolume container:
with viewer.txn() as s:
    s.layers['segmentation'] = neuroglancer.SegmentationLayer(
        source='precomputed://http://localhost/testcv/')

## need to retool this so it shows the correct link, the internal FQDN is not useful
logging.info("viewer at: {}".format(viewer))

logging.debug("neuroglancer viewer is now available")

while(1):
    sleep(0.1)

edit: sorry about the empty notification email, hit enter early.

jbms commented 4 years ago

Adding an option to specify the token to use would be a good idea and should be easy enough --- would you like to send a patch?

If you don't actually need any interaction from Python, nor do you need to serve volumes from Python, and merely want to put neuroglancer into a specific state, you can construct a neuroglancer.ViewerState object, then convert it to a URL via neuroglancer.to_url.

As far as "attaching to a running Neuroglancer instance", do you specifically want to control the single web browser from multiple Python (or other) processes simultaneously, and/or have the state shared between multiple Python processes, or is it merely about avoiding having to copy in the correct URL each time you restart the Python process? In general I agree this would be useful and there are various options for how it could be done, but it might take a bit of work depending on what is desired.

Wildcarde commented 4 years ago

Adding an option to specify the token to use would be a good idea and should be easy enough --- would you like to send a patch?

This I may be able to scratch together and send over I'll send over a patch if I get it working.

do you specifically want to control the single web browser from multiple Python (or other) processes simultaneously, and/or have the state shared between multiple Python processes, or is it merely about avoiding having to copy in the correct URL each time you restart the Python process?

Kinda both? But for different reasons.
I'd like to be able to put nglancer instances behind a reverse proxy (https://github.com/jupyterhub/configurable-http-proxy and traefik being the current candidates) so that we can have separate viewer instances for different stages of a pipeline with their own layers, sessions, and users.
The other side is, it would be great to be able to launch an nglancer instance in an initial configuration state (ie, something like this: https://github.com/Wildcarde/nglancer-frame/blob/master/neuroglancer/nglancer-launcher.py) and be able to attach to that nglancer instance to have a python process provide additional inputs, either because they are hacking on it with a jupyter notebook, or an automate process is launching a container for an additional layer and needs to programatically add mapping to that new layer into the viewer.

Wildcarde commented 4 years ago

A quick follow-up question: is there a way to instruct neuroglancer to stage the urls differently? Ie, move the token to the root of the tree and have the smaller url bits as sub branches past that instead of host/v/ and neighboring urls? This would allow me to point a reverse proxy at the site and rename it as needed by simply saying /viewer0/ -> host:9999/token/ and have it work without exotic mod rewrite tools or similar setups.

perlman commented 4 years ago

150 was a (very) small change to work with reverse proxies. It simply changes ^/v/<token> to (.*)/v/<token>. With this tiny change, I am able to use construct URLs for use with jupyter-server-proxy.

Wildcarde commented 4 years ago

This seems to slightly miss what I'm asking here. There is a set of urls supplied as part of the neuroglancer application:

INFO_PATH_REGEX = r'^/neuroglancer/info/(?P<token>[^/]+)$'

DATA_PATH_REGEX = r'^/neuroglancer/(?P<data_format>[^/]+)/(?P<token>[^/]+)/(?P<scale_key>[^/]+)/(?P<start_x>[0-9]+),(?P<end_x>[0-9]+)/(?P<start_y>[0-9]+),(?P<end_y>[0-9]+)/(?P<start_z>[0-9]+),(?P<end_z>[0-9]+)$'

SKELETON_PATH_REGEX = r'^/neuroglancer/skeleton/(?P<key>[^/]+)/(?P<object_id>[0-9]+)$'

MESH_PATH_REGEX = r'^/neuroglancer/mesh/(?P<key>[^/]+)/(?P<object_id>[0-9]+)$'

STATIC_PATH_REGEX = r'^/v/(?P<viewer_token>[^/]+)/(?P<path>(?:[a-zA-Z0-9_\-][a-zA-Z0-9_\-.]*)?)$'

All of these append the key at or near the end making it impossible to put an arbitrary reverse proxy in front of this tool without knowing the key value; either a button has to be presented that has the correct token already included in the url or the user needs to know the token and paste it into the url in order to use the tool. Both defeating the base of the question. I need to be able to spin up a neuroglancer that will self register behind a configurable proxy as something like <session hash>/viewerX and have the user need nothing to exist past that point in order to land on the neuroglancer interface. If I was just putting an apache proxy infront of a single viewer object, doing the work to make a fixed token entry and using rewrite engine while not particularly easy to get working would probably work. But as we are trying to integrate this into a pipeline where researchers will be using it to review data at different stages in the overall process that doesn't work here.

jbms commented 4 years ago

I can see that the current URL syntax makes the proxy configuration more complicated than necessary. I'm not opposed to changing it to something that is better. I imagine a change might break some existing users' reverse proxy configurations, but if there is a clear improvement I think that cost is reasonable.

If you want to implement something better, I don't think it would be much work.

Wildcarde commented 4 years ago

I thought I should update this, while I'm hoping to do the updates to make controlling the token possible from inside python we are currently examining working around the URL structuring in an alternate way by loading the relevant information into a key/value store and having the wrapper application reconstruct the urls directly.

austinhoag commented 4 years ago

@Wildcarde and I are working on the same project. We found a solution to the first question using a programmable proxy and a key/value store, i.e. we did not have to modify any neuroglancer code.

For the second question (creating a python viewer object from an already running neuroglancer instance) it is looking like we will need to modify the neuroglancer python code directly. The need has narrowed slightly so this might be easier to solve than the original request.

To give some context to the problem we are facing: we are running a flask (python) service where users can request a link to view their data in neuroglancer. When they make a request, we create a neuroglancer.viewer.Viewer() instance, load in the volumes the user requested, modify the viewer state to the specifications the user requested and then return the viewer link. This is all working great, but the issue is: what if the user wants to modify the existing viewer state, add new volumes, etc. in a jupyter notebook (common use case).

One way I can think of to approach this is to add optional parameters specifying the address and port of the tornado server on which the existing viewer is already registered, along with the viewer token when making the Viewer() object. Then some sort of API would need to be created for communicating with the tornado server to update the viewer state. I am interested to hear from those who have worked on the python interface to neuroglancer on whether this seems like a viable approach.