From 4e20767778b7f0562a4f771ff84208928f7fcfd3 Mon Sep 17 00:00:00 2001 From: Casey Schneider-Mizell Date: Mon, 20 Nov 2023 13:51:56 -0800 Subject: [PATCH 1/4] state upload and id fixes --- caveclient/endpoints.py | 8 +++++ caveclient/jsonservice.py | 70 +++++++++++++++++++++++++++++++++++---- 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/caveclient/endpoints.py b/caveclient/endpoints.py index e3e6bd0f..ef424b2b 100644 --- a/caveclient/endpoints.py +++ b/caveclient/endpoints.py @@ -263,3 +263,11 @@ } l2cache_api_versions = {1: l2cache_endpoints_v1} + +# ------------------------------- +# ------ Neuroglancer endpoints +# ------------------------------- + +ngl_endpoints_common = { + 'get_info': "{ngl_url}/version.json" +} \ No newline at end of file diff --git a/caveclient/jsonservice.py b/caveclient/jsonservice.py index 94c8e451..0c1bac4e 100644 --- a/caveclient/jsonservice.py +++ b/caveclient/jsonservice.py @@ -10,14 +10,32 @@ jsonservice_common, jsonservice_api_versions, default_global_server_address, + ngl_endpoints_common, ) import requests +import numpy as np +import numbers import json import re server_key = "json_server_address" +def neuroglancer_json_encoder(obj): + """JSON encoder for neuroglancer states. + Differs from normal in that it expresses ints as strings""" + if isinstance(obj, numbers.Integral): + return str(obj) + if isinstance(obj, np.integer): + return str(obj) + elif isinstance(obj, np.floating): + return float(obj) + elif isinstance(obj, np.ndarray): + return list(obj) + elif isinstance(obj, (set, frozenset)): + return list(obj) + raise TypeError + def JSONService( server_address=None, auth_client=None, @@ -118,6 +136,27 @@ def ngl_url(self): def ngl_url(self, new_ngl_url): self._ngl_url = new_ngl_url + def get_neuroglancer_info(self, ngl_url): + """Get the info field from a Neuroglancer deployment + + Parameters + ---------- + ngl_url : str + URL to a Neuroglancer deployment + + Returns + ------- + dict + JSON-formatted info field from the Neuroglancer deployment + """ + url_mapping"ngl_url"] = ngl_url + url = ngl_endpoints_common.get('get_info').format_map(url_mapping) + response = self.session.get(url) + handle_response(response, as_json=False) + return json.loads(response.content) + + + def get_state_json(self, state_id): """Download a Neuroglancer JSON state @@ -165,12 +204,18 @@ def upload_state_json(self, json_state, state_id=None, timestamp=None): url_mapping["state_id"] = state_id url = self._endpoints["upload_state_w_id"].format_map(url_mapping) - response = self.session.post(url, data=json.dumps(json_state, cls=BaseEncoder)) + response = self.session.post( + url, + data=json.dumps( + json_state, + default=neuroglancer_json_encoder, + ) + ) handle_response(response, as_json=False) response_re = re.search(".*\/(\d+)", str(response.content)) return int(response_re.groups()[0]) - def build_neuroglancer_url(self, state_id, ngl_url=None): + def build_neuroglancer_url(self, state_id, ngl_url=None, target_site=None): """Build a URL for a Neuroglancer deployment that will automatically retrieve specified state. If the datastack is specified, this is prepopulated from the info file field "viewer_site". If no ngl_url is specified in either the function or the client, only the JSON state url is returned. @@ -182,6 +227,10 @@ def build_neuroglancer_url(self, state_id, ngl_url=None): ngl_url : str Base url of a neuroglancer deployment. If None, defaults to the value for the datastack or the client. If no value is found, only the URL to the JSON state is returned. + target_site : str (optional) + Set this to 'seunglab' for a seunglab deployment, or 'cave-explorer'/'mainline' for a google main branch deployment. + If none, defaults to seunglab. + Returns ------- @@ -190,13 +239,22 @@ def build_neuroglancer_url(self, state_id, ngl_url=None): """ if ngl_url is None: ngl_url = self.ngl_url + if target_site is None: + target_site = "seunglab" + if ngl_url is None: ngl_url = "" parameter_text = "" - elif ngl_url[-1] == "/": - parameter_text = "?json_url=" - else: - parameter_text = "/?json_url=" + elif target_site == "seunglab": + if ngl_url[-1] == "/": + parameter_text = "?json_url=" + else: + parameter_text = "/?json_url=" + elif target_site == "cave-explorer" or target_site == "mainline": + if ngl_url[-1] == "/": + parameter_text = "#!middleauth+" + else: + parameter_text = "/#!middleauth+" url_mapping = self.default_url_mapping url_mapping["state_id"] = state_id From 415786c64e0be5b418fd1e36aeb21cf3db1446a4 Mon Sep 17 00:00:00 2001 From: Casey Schneider-Mizell Date: Mon, 20 Nov 2023 14:14:32 -0800 Subject: [PATCH 2/4] Better default handling + auto lookup auth format --- caveclient/jsonservice.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/caveclient/jsonservice.py b/caveclient/jsonservice.py index 0c1bac4e..28ae0105 100644 --- a/caveclient/jsonservice.py +++ b/caveclient/jsonservice.py @@ -136,27 +136,33 @@ def ngl_url(self): def ngl_url(self, new_ngl_url): self._ngl_url = new_ngl_url - def get_neuroglancer_info(self, ngl_url): + def get_neuroglancer_info(self, ngl_url=None): """Get the info field from a Neuroglancer deployment Parameters ---------- - ngl_url : str - URL to a Neuroglancer deployment + ngl_url : str (optional) + URL to a Neuroglancer deployment. + If None, defaults to the value for the datastack or the client. Returns ------- dict JSON-formatted info field from the Neuroglancer deployment """ - url_mapping"ngl_url"] = ngl_url + if ngl_url is None: + ngl_url = self.ngl_url + + url_mapping = self.default_url_mapping + url_mapping["ngl_url"] = ngl_url url = ngl_endpoints_common.get('get_info').format_map(url_mapping) response = self.session.get(url) + if response.status_code == 404: + return {} handle_response(response, as_json=False) return json.loads(response.content) - def get_state_json(self, state_id): """Download a Neuroglancer JSON state @@ -215,6 +221,7 @@ def upload_state_json(self, json_state, state_id=None, timestamp=None): response_re = re.search(".*\/(\d+)", str(response.content)) return int(response_re.groups()[0]) + def build_neuroglancer_url(self, state_id, ngl_url=None, target_site=None): """Build a URL for a Neuroglancer deployment that will automatically retrieve specified state. If the datastack is specified, this is prepopulated from the info file field "viewer_site". @@ -227,10 +234,9 @@ def build_neuroglancer_url(self, state_id, ngl_url=None, target_site=None): ngl_url : str Base url of a neuroglancer deployment. If None, defaults to the value for the datastack or the client. If no value is found, only the URL to the JSON state is returned. - target_site : str (optional) + target_site : 'seunglab' or 'cave-explorer' or 'mainline' or None Set this to 'seunglab' for a seunglab deployment, or 'cave-explorer'/'mainline' for a google main branch deployment. - If none, defaults to seunglab. - + If None, checks the info field of the neuroglancer endpoint to determine which to use. Returns ------- @@ -239,8 +245,12 @@ def build_neuroglancer_url(self, state_id, ngl_url=None, target_site=None): """ if ngl_url is None: ngl_url = self.ngl_url - if target_site is None: - target_site = "seunglab" + if target_site is None and ngl_url is not None: + ngl_info = self.get_neuroglancer_info(ngl_url) + if len(ngl_info) > 0: + target_site = 'cave-explorer' + else: + target_site = "seunglab" if ngl_url is None: ngl_url = "" From 8e40319e0a663b1e4ed796fbd863eaa96b963f50 Mon Sep 17 00:00:00 2001 From: Casey Schneider-Mizell Date: Mon, 20 Nov 2023 14:43:10 -0800 Subject: [PATCH 3/4] More accurate docstring --- caveclient/jsonservice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/caveclient/jsonservice.py b/caveclient/jsonservice.py index 28ae0105..69fcaf28 100644 --- a/caveclient/jsonservice.py +++ b/caveclient/jsonservice.py @@ -189,7 +189,7 @@ def upload_state_json(self, json_state, state_id=None, timestamp=None): Parameters ---------- json_state : dict - JSON-formatted Neuroglancer state + Dict representation of a neuroglancer state state_id : int ID of a JSON state uploaded to the state service. Using a state_id is an admin feature. From 827c0813be23bbc15f6280623716f910f53ac056 Mon Sep 17 00:00:00 2001 From: Casey Schneider-Mizell Date: Sat, 2 Dec 2023 15:00:46 -1000 Subject: [PATCH 4/4] add default fallback ngl endpoint --- caveclient/endpoints.py | 4 +++- caveclient/jsonservice.py | 16 ++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/caveclient/endpoints.py b/caveclient/endpoints.py index ef424b2b..72aa8ae4 100644 --- a/caveclient/endpoints.py +++ b/caveclient/endpoints.py @@ -268,6 +268,8 @@ # ------ Neuroglancer endpoints # ------------------------------- +fallback_ngl_endpoint = "https://neuroglancer.neuvue.io/" ngl_endpoints_common = { - 'get_info': "{ngl_url}/version.json" + 'get_info': "{ngl_url}/version.json", + 'fallback_ngl_url': fallback_ngl_endpoint, } \ No newline at end of file diff --git a/caveclient/jsonservice.py b/caveclient/jsonservice.py index 69fcaf28..7d9260a5 100644 --- a/caveclient/jsonservice.py +++ b/caveclient/jsonservice.py @@ -157,8 +157,11 @@ def get_neuroglancer_info(self, ngl_url=None): url_mapping["ngl_url"] = ngl_url url = ngl_endpoints_common.get('get_info').format_map(url_mapping) response = self.session.get(url) + # Not all neuroglancer deployments have a version.json, + # so return empty if not found rather than throw error. if response.status_code == 404: return {} + handle_response(response, as_json=False) return json.loads(response.content) @@ -233,10 +236,11 @@ def build_neuroglancer_url(self, state_id, ngl_url=None, target_site=None): State id to retrieve ngl_url : str Base url of a neuroglancer deployment. If None, defaults to the value for the datastack or the client. - If no value is found, only the URL to the JSON state is returned. + As a fallback, a default deployment is used. target_site : 'seunglab' or 'cave-explorer' or 'mainline' or None Set this to 'seunglab' for a seunglab deployment, or 'cave-explorer'/'mainline' for a google main branch deployment. If None, checks the info field of the neuroglancer endpoint to determine which to use. + Default is None. Returns ------- @@ -244,7 +248,10 @@ def build_neuroglancer_url(self, state_id, ngl_url=None, target_site=None): The full URL requested """ if ngl_url is None: - ngl_url = self.ngl_url + if self.ngl_url is not None: + ngl_url = self.ngl_url + else: + ngl_url = ngl_endpoints_common['fallback_ngl_url'] if target_site is None and ngl_url is not None: ngl_info = self.get_neuroglancer_info(ngl_url) if len(ngl_info) > 0: @@ -252,10 +259,7 @@ def build_neuroglancer_url(self, state_id, ngl_url=None, target_site=None): else: target_site = "seunglab" - if ngl_url is None: - ngl_url = "" - parameter_text = "" - elif target_site == "seunglab": + if target_site == "seunglab": if ngl_url[-1] == "/": parameter_text = "?json_url=" else: