obi-notebook 0.1.dev9__tar.gz → 0.2.dev0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/PKG-INFO +1 -1
  2. obi_notebook-0.2.dev0/example.ipynb +147 -0
  3. obi_notebook-0.2.dev0/src/obi_notebook/get_entities.py +274 -0
  4. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/src/obi_notebook.egg-info/PKG-INFO +1 -1
  5. obi_notebook-0.1.dev9/example.ipynb +0 -182
  6. obi_notebook-0.1.dev9/src/obi_notebook/get_entities.py +0 -142
  7. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/.github/workflows/sdist.yml +0 -0
  8. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/.github/workflows/tox.yml +0 -0
  9. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/.gitignore +0 -0
  10. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/CHANGELOG.rst +0 -0
  11. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/LICENSE.txt +0 -0
  12. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/README.md +0 -0
  13. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/demo.gif +0 -0
  14. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/pyproject.toml +0 -0
  15. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/setup.cfg +0 -0
  16. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/src/obi_notebook/__init__.py +0 -0
  17. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/src/obi_notebook/get_projects.py +0 -0
  18. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/src/obi_notebook.egg-info/SOURCES.txt +0 -0
  19. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/src/obi_notebook.egg-info/dependency_links.txt +0 -0
  20. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/src/obi_notebook.egg-info/requires.txt +0 -0
  21. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/src/obi_notebook.egg-info/top_level.txt +0 -0
  22. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/tox.ini +0 -0
  23. {obi_notebook-0.1.dev9 → obi_notebook-0.2.dev0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: obi_notebook
3
- Version: 0.1.dev9
3
+ Version: 0.2.dev0
4
4
  Summary: convenience functions for OBI notebooks
5
5
  Author-email: Open Brain Institute <jean-denis.courcol@epfl.ch>
6
6
  Maintainer-email: Open Brain Institute <jean-denis.courcol@epfl.ch>
@@ -0,0 +1,147 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "id": "7eb47475-67c6-499a-a2b4-19f8a64974d0",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "import obi_auth\n",
11
+ "\n",
12
+ "from obi_notebook import get_entities, get_projects"
13
+ ]
14
+ },
15
+ {
16
+ "cell_type": "markdown",
17
+ "id": "4d984b92-cadd-4b07-9813-b2333272610f",
18
+ "metadata": {},
19
+ "source": [
20
+ "get authentication token for production"
21
+ ]
22
+ },
23
+ {
24
+ "cell_type": "code",
25
+ "execution_count": null,
26
+ "id": "323cb45c-91e3-451f-8a80-374e61433d31",
27
+ "metadata": {},
28
+ "outputs": [],
29
+ "source": [
30
+ "token = obi_auth.get_token(environment=\"production\")"
31
+ ]
32
+ },
33
+ {
34
+ "cell_type": "markdown",
35
+ "id": "b60e8643-3ad5-4619-a052-5bd8b64713fd",
36
+ "metadata": {},
37
+ "source": [
38
+ "a widget to select project to use"
39
+ ]
40
+ },
41
+ {
42
+ "cell_type": "code",
43
+ "execution_count": null,
44
+ "id": "77c47560-1163-4466-83fc-43abc6e0801a",
45
+ "metadata": {},
46
+ "outputs": [],
47
+ "source": [
48
+ "project_context = get_projects.get_projects(token)"
49
+ ]
50
+ },
51
+ {
52
+ "cell_type": "code",
53
+ "execution_count": null,
54
+ "id": "d538dcac-1bdc-496b-8840-a7b32ea842ed",
55
+ "metadata": {},
56
+ "outputs": [],
57
+ "source": [
58
+ "project_context"
59
+ ]
60
+ },
61
+ {
62
+ "cell_type": "code",
63
+ "execution_count": null,
64
+ "id": "93029f77-d71f-40f4-8c4d-9f23d2b650bc",
65
+ "metadata": {},
66
+ "outputs": [],
67
+ "source": [
68
+ "circuit_ids = []\n",
69
+ "circuit_ids = get_entities.get_entities(\n",
70
+ " \"circuit\",\n",
71
+ " token,\n",
72
+ " circuit_ids,\n",
73
+ " return_entities=False,\n",
74
+ " multi_select=True,\n",
75
+ " show_pages=True,\n",
76
+ " project_context=project_context,\n",
77
+ " page_size=10,\n",
78
+ " default_scale=\"small\",\n",
79
+ " exclude_scales=[\"single\"],\n",
80
+ " add_columns=[\"subject.name\"],\n",
81
+ ")"
82
+ ]
83
+ },
84
+ {
85
+ "cell_type": "code",
86
+ "execution_count": null,
87
+ "id": "da699ed4-39fd-4602-b54d-58a0b58c2539",
88
+ "metadata": {},
89
+ "outputs": [],
90
+ "source": [
91
+ "circuit_ids"
92
+ ]
93
+ },
94
+ {
95
+ "cell_type": "code",
96
+ "execution_count": null,
97
+ "id": "a5f5445d-a61a-4fbe-962f-51e701434771",
98
+ "metadata": {},
99
+ "outputs": [],
100
+ "source": [
101
+ "morph_ids = []\n",
102
+ "morph_ids = get_entities.get_entities(\n",
103
+ " \"reconstruction-morphology\",\n",
104
+ " token,\n",
105
+ " morph_ids,\n",
106
+ " return_entities=False,\n",
107
+ " multi_select=True,\n",
108
+ " show_pages=True,\n",
109
+ " project_context=project_context,\n",
110
+ " page_size=100,\n",
111
+ " add_columns=[\"mtypes\"],\n",
112
+ ")"
113
+ ]
114
+ },
115
+ {
116
+ "cell_type": "code",
117
+ "execution_count": null,
118
+ "id": "ab24b139",
119
+ "metadata": {},
120
+ "outputs": [],
121
+ "source": [
122
+ "morph_ids"
123
+ ]
124
+ }
125
+ ],
126
+ "metadata": {
127
+ "kernelspec": {
128
+ "display_name": ".venv (3.13.1)",
129
+ "language": "python",
130
+ "name": "python3"
131
+ },
132
+ "language_info": {
133
+ "codemirror_mode": {
134
+ "name": "ipython",
135
+ "version": 3
136
+ },
137
+ "file_extension": ".py",
138
+ "mimetype": "text/x-python",
139
+ "name": "python",
140
+ "nbconvert_exporter": "python",
141
+ "pygments_lexer": "ipython3",
142
+ "version": "3.13.1"
143
+ }
144
+ },
145
+ "nbformat": 4,
146
+ "nbformat_minor": 5
147
+ }
@@ -0,0 +1,274 @@
1
+ """A table widget to select entities."""
2
+
3
+ import ipywidgets as widgets
4
+ import numpy as np
5
+ import pandas as pd
6
+ import requests
7
+ from entitysdk import Client, models, types
8
+ from ipydatagrid import DataGrid, TextRenderer
9
+ from IPython.display import clear_output, display
10
+
11
+ LST_MTYPES_ = None
12
+ LST_SPECIES_ = None
13
+ STR_NO_MTYPE = "NONE"
14
+
15
+
16
+ def _estimate_column_widths(df, char_width=8, padding=2, max_size=250):
17
+ widths = {}
18
+ for col in df.columns:
19
+ max_len = max(df[col].astype(str).map(len).max(), len(col))
20
+ widths[col] = min(max_size, max_len * char_width + padding)
21
+ return widths
22
+
23
+
24
+ def _resolve_list_to_first_element(obj):
25
+ if isinstance(obj, list):
26
+ if len(obj) > 0:
27
+ return obj[0]["pref_label"]
28
+ return STR_NO_MTYPE
29
+ return obj["pref_label"]
30
+
31
+
32
+ _df_postprocess_funs = {"reconstruction-morphology": {"mtypes": _resolve_list_to_first_element}}
33
+
34
+
35
+ def _list_of_existing_mtypes(entity_core_url, token):
36
+ global LST_MTYPES_
37
+ if LST_MTYPES_ is None:
38
+ response = requests.get(
39
+ f"{entity_core_url}/mtype",
40
+ headers={"authorization": f"Bearer {token}"},
41
+ params={"page_size": 1000},
42
+ timeout=30,
43
+ )
44
+ data = response.json()
45
+ df_mtype = pd.json_normalize(data["data"])
46
+ LST_MTYPES_ = list(df_mtype["pref_label"])
47
+ return LST_MTYPES_
48
+
49
+
50
+ def _list_of_existing_species(entity_core_url, token):
51
+ global LST_SPECIES_
52
+ if LST_SPECIES_ is None:
53
+ response = requests.get(
54
+ f"{entity_core_url}/species",
55
+ headers={"authorization": f"Bearer {token}"},
56
+ params={"page_size": 1000},
57
+ timeout=30,
58
+ )
59
+ data = response.json()
60
+ df_species = pd.json_normalize(data["data"])
61
+ LST_SPECIES_ = list(df_species["name"])
62
+ return LST_SPECIES_
63
+
64
+
65
+ def get_entities(
66
+ entity_type,
67
+ token,
68
+ result,
69
+ env="production",
70
+ project_context=None,
71
+ return_entities=False,
72
+ multi_select=True,
73
+ page_size=10,
74
+ show_pages=True,
75
+ add_columns=None,
76
+ default_scale=None,
77
+ exclude_scales=None,
78
+ ):
79
+ """Select entities of type entity_type and add them to result.
80
+
81
+ Note: The 'result' parameter is a mutable object (a list) that is modified in-place
82
+ and also returned.
83
+ """
84
+ if page_size is not None:
85
+ if page_size <= 0:
86
+ raise ValueError("ERROR: Page size must be larger than 0!")
87
+ # TODO: Could add an upper limit as well here
88
+
89
+ if add_columns is None:
90
+ add_columns = []
91
+ if exclude_scales is None:
92
+ exclude_scales = []
93
+
94
+ subdomain = "www" if env == "production" else "staging"
95
+ entity_core_url = f"https://{subdomain}.openbraininstitute.org/api/entitycore"
96
+
97
+ # Widgets
98
+ filters_dict = {}
99
+ if entity_type == "circuit":
100
+ scale_options = [
101
+ _scale.value for _scale in types.CircuitScale if _scale.value not in exclude_scales
102
+ ]
103
+ if default_scale is None or default_scale not in scale_options:
104
+ default_scale = scale_options[0]
105
+ scale_filter = widgets.Dropdown(
106
+ options=scale_options,
107
+ value=default_scale,
108
+ description="Scale:",
109
+ )
110
+ filters_dict["scale"] = scale_filter
111
+ elif entity_type == "reconstruction-morphology":
112
+ lst_mtypes = _list_of_existing_mtypes(entity_core_url, token) + [""]
113
+ mtype_filter = widgets.Combobox(
114
+ placeholder="Select M-Type",
115
+ description="M-Type:",
116
+ options=lst_mtypes,
117
+ ensure_option=True,
118
+ )
119
+ filters_dict["mtype__pref_label"] = mtype_filter
120
+ lst_species = _list_of_existing_species(entity_core_url, token) + [""]
121
+ species_filter = widgets.Dropdown(options=lst_species, value="", description="Species:")
122
+ filters_dict["species__name"] = species_filter
123
+
124
+ filters_dict["name__ilike"] = widgets.Text(description="Name:")
125
+
126
+ if show_pages:
127
+ filters_dict["page"] = widgets.Dropdown(
128
+ options=[1],
129
+ value=1,
130
+ description="Results page:",
131
+ disabled=False,
132
+ style={"description_width": "auto"},
133
+ layout=widgets.Layout(width="max-content"),
134
+ )
135
+
136
+ # Output area
137
+ output = widgets.Output()
138
+
139
+ # Fetch and display function
140
+ def fetch_data(filter_values):
141
+ if page_size is None:
142
+ params = {}
143
+ else:
144
+ params = {"page_size": page_size}
145
+ for k, v in filter_values.items():
146
+ if isinstance(v, str):
147
+ if len(v.strip()) == 0:
148
+ continue
149
+ params[k] = v
150
+
151
+ headers = {"authorization": f"Bearer {token}"}
152
+ if project_context:
153
+ headers["virtual-lab-id"] = str(project_context.virtual_lab_id)
154
+ headers["project-id"] = str(project_context.project_id)
155
+ response = requests.get(
156
+ f"{entity_core_url}/{entity_type}",
157
+ headers=headers,
158
+ params=params,
159
+ timeout=30,
160
+ )
161
+
162
+ try:
163
+ data = response.json()
164
+ df = pd.json_normalize(data["data"])
165
+ pagination = data["pagination"]
166
+ return df, pagination
167
+ except Exception as e:
168
+ print("Error fetching or parsing data:", e)
169
+ return pd.DataFrame(), None
170
+
171
+ grid = None
172
+
173
+ # On change callback
174
+ def on_change(change=None):
175
+ nonlocal result
176
+ nonlocal grid
177
+ with output:
178
+ clear_output()
179
+ filter_values = {k: v.value for k, v in filters_dict.items()}
180
+ df, pagination = fetch_data(filter_values)
181
+
182
+ proper_columns = [
183
+ "id",
184
+ "name",
185
+ "description",
186
+ "brain_region.name",
187
+ "subject.species.name",
188
+ "species.name", # For morphologies
189
+ ] + add_columns
190
+ if len(df) == 0:
191
+ if show_pages and filters_dict["page"].value != 1:
192
+ filters_dict["page"].options = [1] # Will update .value as well
193
+ else:
194
+ print("no results")
195
+ return
196
+
197
+ proper_columns = [_col for _col in proper_columns if _col in df.columns]
198
+ df = df[proper_columns].reset_index(drop=True)
199
+
200
+ for colname, fun in _df_postprocess_funs.get(entity_type, {}).items():
201
+ if colname in df.columns:
202
+ df[colname] = df[colname].apply(fun)
203
+
204
+ if show_pages:
205
+ num_pages = np.maximum(
206
+ 1, np.ceil(pagination["total_items"] / pagination["page_size"]).astype(int)
207
+ )
208
+ filters_dict["page"].options = range(1, num_pages + 1)
209
+ df.index = df.index + (pagination["page"] - 1) * pagination["page_size"]
210
+
211
+ column_widths = _estimate_column_widths(df)
212
+ grid = DataGrid(
213
+ df,
214
+ layout={"height": "300px"},
215
+ # auto_fit_columns=True,
216
+ auto_fit_params={"area": "all"},
217
+ selection_mode="row", # Enable row selection
218
+ column_widths=column_widths,
219
+ )
220
+ grid.default_renderer = TextRenderer()
221
+ display(grid)
222
+
223
+ def on_selection_change(event, grid=grid):
224
+ with output:
225
+ if not multi_select and len(grid.selections) > 0:
226
+ if (event["new"][-1]["r1"] != event["new"][-1]["r2"]) or len(
227
+ grid.selections
228
+ ) > 1: # Multiple rows selected
229
+ if event["new"][-1]["r1"] == event["old"][-1]["r1"]: # r1 unchanged
230
+ new_r = event["new"][-1]["r1"]
231
+ else: # r2 unchanged
232
+ new_r = event["new"][-1]["r2"]
233
+ # Enforce selection of a single row (last one that was selected)
234
+ grid.selections = [
235
+ {
236
+ "r1": new_r,
237
+ "r2": new_r,
238
+ "c1": grid.selections[-1]["c1"],
239
+ "c2": grid.selections[-1]["c2"],
240
+ }
241
+ ]
242
+ result.clear()
243
+ l_ids = set()
244
+ for selection in grid.selections:
245
+ for row in range(selection["r1"], selection["r2"] + 1):
246
+ l_ids.add(df.iloc[row]["id"])
247
+
248
+ if return_entities:
249
+ client = Client(
250
+ api_url=entity_core_url,
251
+ project_context=project_context,
252
+ token_manager=token,
253
+ )
254
+
255
+ model_class = getattr(models, entity_type.capitalize())
256
+ retrieved_entities = client.search_entity(
257
+ entity_type=model_class, query={"id__in": list(l_ids)}
258
+ )
259
+ result.extend(retrieved_entities)
260
+ else:
261
+ result.extend(l_ids)
262
+
263
+ grid.observe(on_selection_change, names="selections")
264
+
265
+ for filter_ in filters_dict.values():
266
+ filter_.observe(on_change, names="value")
267
+
268
+ # Display
269
+ display(widgets.HBox(list(filters_dict.values())), output)
270
+
271
+ # Initial load
272
+ on_change()
273
+
274
+ return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: obi_notebook
3
- Version: 0.1.dev9
3
+ Version: 0.2.dev0
4
4
  Summary: convenience functions for OBI notebooks
5
5
  Author-email: Open Brain Institute <jean-denis.courcol@epfl.ch>
6
6
  Maintainer-email: Open Brain Institute <jean-denis.courcol@epfl.ch>
@@ -1,182 +0,0 @@
1
- {
2
- "cells": [
3
- {
4
- "cell_type": "code",
5
- "execution_count": 1,
6
- "id": "7eb47475-67c6-499a-a2b4-19f8a64974d0",
7
- "metadata": {},
8
- "outputs": [],
9
- "source": [
10
- "import obi_auth\n",
11
- "from obi_notebook import get_projects, get_entities"
12
- ]
13
- },
14
- {
15
- "cell_type": "markdown",
16
- "id": "4d984b92-cadd-4b07-9813-b2333272610f",
17
- "metadata": {},
18
- "source": [
19
- "get authentication token for production"
20
- ]
21
- },
22
- {
23
- "cell_type": "code",
24
- "execution_count": 2,
25
- "id": "323cb45c-91e3-451f-8a80-374e61433d31",
26
- "metadata": {},
27
- "outputs": [],
28
- "source": [
29
- "token = obi_auth.get_token(environment=\"production\")"
30
- ]
31
- },
32
- {
33
- "cell_type": "markdown",
34
- "id": "b60e8643-3ad5-4619-a052-5bd8b64713fd",
35
- "metadata": {},
36
- "source": [
37
- "a widget to select project to use"
38
- ]
39
- },
40
- {
41
- "cell_type": "code",
42
- "execution_count": 3,
43
- "id": "77c47560-1163-4466-83fc-43abc6e0801a",
44
- "metadata": {},
45
- "outputs": [
46
- {
47
- "data": {
48
- "application/vnd.jupyter.widget-view+json": {
49
- "model_id": "cc2954a000034b5db323c3c5bb43a985",
50
- "version_major": 2,
51
- "version_minor": 0
52
- },
53
- "text/plain": [
54
- "Dropdown(description='Select:', options=(('Understanding connectivity', {'id': 'fe443f04-2d56-4c51-8347-8ca5f3…"
55
- ]
56
- },
57
- "metadata": {},
58
- "output_type": "display_data"
59
- }
60
- ],
61
- "source": [
62
- "project_context = get_projects.get_projects(token)"
63
- ]
64
- },
65
- {
66
- "cell_type": "code",
67
- "execution_count": 4,
68
- "id": "d538dcac-1bdc-496b-8840-a7b32ea842ed",
69
- "metadata": {},
70
- "outputs": [
71
- {
72
- "data": {
73
- "text/plain": [
74
- "ProjectContext(project_id=UUID('fe443f04-2d56-4c51-8347-8ca5f3a97ee6'), virtual_lab_id=UUID('cee1a37f-fe43-4589-9197-c46746aff976'), environment=<DeploymentEnvironment.production: 'production'>)"
75
- ]
76
- },
77
- "execution_count": 4,
78
- "metadata": {},
79
- "output_type": "execute_result"
80
- }
81
- ],
82
- "source": [
83
- "project_context"
84
- ]
85
- },
86
- {
87
- "cell_type": "code",
88
- "execution_count": 8,
89
- "id": "93029f77-d71f-40f4-8c4d-9f23d2b650bc",
90
- "metadata": {},
91
- "outputs": [
92
- {
93
- "data": {
94
- "application/vnd.jupyter.widget-view+json": {
95
- "model_id": "371c88af967a42a984fdf8fbf362660f",
96
- "version_major": 2,
97
- "version_minor": 0
98
- },
99
- "text/plain": [
100
- "HBox(children=(Dropdown(description='Scale:', options=('single', 'pair', 'small', 'microcircuit', 'region', 's…"
101
- ]
102
- },
103
- "metadata": {},
104
- "output_type": "display_data"
105
- },
106
- {
107
- "data": {
108
- "application/vnd.jupyter.widget-view+json": {
109
- "model_id": "3d13a11549b84a3193044113977ba95b",
110
- "version_major": 2,
111
- "version_minor": 0
112
- },
113
- "text/plain": [
114
- "Output()"
115
- ]
116
- },
117
- "metadata": {},
118
- "output_type": "display_data"
119
- }
120
- ],
121
- "source": [
122
- "circuit_ids = []\n",
123
- "circuit_ids = get_entities.get_entities(\"circuit\", token, circuit_ids,)"
124
- ]
125
- },
126
- {
127
- "cell_type": "code",
128
- "execution_count": 9,
129
- "id": "da699ed4-39fd-4602-b54d-58a0b58c2539",
130
- "metadata": {},
131
- "outputs": [
132
- {
133
- "data": {
134
- "text/plain": [
135
- "['1be432a7-c7fa-422b-bb47-8b189ede363e',\n",
136
- " 'bf3a620d-5a36-401b-97ac-d27cde014039',\n",
137
- " 'b907f9a6-04e3-4a51-85f6-25ace2c816dc',\n",
138
- " 'ba297b44-9340-4646-8684-01ce31ed33f5',\n",
139
- " '8176966b-cce1-45ef-9642-e87c4436ee7d',\n",
140
- " 'f5e228e7-1c2b-4fa9-89ae-97026c97f83e']"
141
- ]
142
- },
143
- "execution_count": 9,
144
- "metadata": {},
145
- "output_type": "execute_result"
146
- }
147
- ],
148
- "source": [
149
- "circuit_ids"
150
- ]
151
- },
152
- {
153
- "cell_type": "code",
154
- "execution_count": null,
155
- "id": "b6b9479f-60b8-4fa1-bd0d-8dbbda0909b8",
156
- "metadata": {},
157
- "outputs": [],
158
- "source": []
159
- }
160
- ],
161
- "metadata": {
162
- "kernelspec": {
163
- "display_name": "Python (myenv)",
164
- "language": "python",
165
- "name": "obi-notebook"
166
- },
167
- "language_info": {
168
- "codemirror_mode": {
169
- "name": "ipython",
170
- "version": 3
171
- },
172
- "file_extension": ".py",
173
- "mimetype": "text/x-python",
174
- "name": "python",
175
- "nbconvert_exporter": "python",
176
- "pygments_lexer": "ipython3",
177
- "version": "3.12.8"
178
- }
179
- },
180
- "nbformat": 4,
181
- "nbformat_minor": 5
182
- }
@@ -1,142 +0,0 @@
1
- """A table widget to select entities."""
2
-
3
- import ipywidgets as widgets
4
- import pandas as pd
5
- import requests
6
- from entitysdk import Client, models
7
- from ipydatagrid import DataGrid, TextRenderer
8
- from IPython.display import clear_output, display
9
-
10
-
11
- def _estimate_column_widths(df, char_width=8, padding=2, max_size=250):
12
- widths = {}
13
- for col in df.columns:
14
- max_len = max(df[col].astype(str).map(len).max(), len(col))
15
- widths[col] = min(max_size, max_len * char_width + padding)
16
- return widths
17
-
18
-
19
- def get_entities(
20
- entity_type, token, result, env="production", project_context=None, return_entities=False
21
- ):
22
- """Select entities of type entity_type and add them to result.
23
-
24
- Note: The 'result' parameter is a mutable object (a list) that is modified in-place
25
- and also returned.
26
- """
27
- # Widgets
28
- filters_dict = {}
29
- if entity_type == "circuit":
30
- scale_filter = widgets.Dropdown(
31
- options=["single", "pair", "small", "microcircuit", "region", "system", "whole"],
32
- description="Scale:",
33
- )
34
- filters_dict["scale"] = scale_filter
35
-
36
- filters_dict["name"] = widgets.Text(description="Name:")
37
-
38
- # Output area
39
- output = widgets.Output()
40
-
41
- subdomain = "www" if env == "production" else "staging"
42
- entity_core_url = f"https://{subdomain}.openbraininstitute.org/api/entitycore"
43
-
44
- # Fetch and display function
45
- def fetch_data(filter_values):
46
- params = {"page_size": 10}
47
- for k, v in filter_values.items():
48
- if k == "name":
49
- params["name__ilike"] = v
50
- else:
51
- params[k] = v
52
-
53
- headers = {"authorization": f"Bearer {token}"}
54
- if project_context:
55
- headers["virtual-lab-id"] = project_context.virtual_lab_id
56
- headers["project-id"] = project_context.project_id
57
- response = requests.get(
58
- f"{entity_core_url}/{entity_type}",
59
- headers=headers,
60
- params=params,
61
- timeout=30,
62
- )
63
-
64
- try:
65
- data = response.json()
66
- df = pd.json_normalize(data["data"])
67
- return df
68
- except Exception as e:
69
- print("Error fetching or parsing data:", e)
70
- return pd.DataFrame()
71
-
72
- grid = None
73
-
74
- # On change callback
75
- def on_change(change=None):
76
- nonlocal result
77
- nonlocal grid
78
- with output:
79
- clear_output()
80
- filter_values = {k: v.value for k, v in filters_dict.items()}
81
- df = fetch_data(filter_values)
82
-
83
- proper_columns = [
84
- "id",
85
- "name",
86
- "description",
87
- "brain_region.name",
88
- "subject.species.name",
89
- ]
90
- if len(df) == 0:
91
- print("no results")
92
- return
93
-
94
- df = df[proper_columns].reset_index(drop=True)
95
- column_widths = _estimate_column_widths(df)
96
- grid = DataGrid(
97
- df,
98
- layout={"height": "300px"},
99
- # auto_fit_columns=True,
100
- auto_fit_params={"area": "all"},
101
- selection_mode="row", # Enable row selection
102
- selection_behavior="multi",
103
- column_widths=column_widths,
104
- )
105
- grid.default_renderer = TextRenderer()
106
- display(grid)
107
-
108
- def on_selection_change(event, grid=grid):
109
- with output:
110
- result.clear()
111
- l_ids = set()
112
- for selection in grid.selections:
113
- for row in range(selection["r1"], selection["r2"] + 1):
114
- l_ids.add(df.iloc[row]["id"])
115
-
116
- if return_entities:
117
- client = Client(
118
- api_url=entity_core_url,
119
- project_context=project_context,
120
- token_manager=token,
121
- )
122
-
123
- model_class = getattr(models, entity_type.capitalize())
124
- retrieved_entities = client.search_entity(
125
- entity_type=model_class, query={"id__in": list(l_ids)}
126
- )
127
- result.extend(retrieved_entities)
128
- else:
129
- result.extend(l_ids)
130
-
131
- grid.observe(on_selection_change, names="selections")
132
-
133
- for filter_ in filters_dict.values():
134
- filter_.observe(on_change, names="value")
135
-
136
- # Display
137
- display(widgets.HBox(list(filters_dict.values())), output)
138
-
139
- # Initial load
140
- on_change()
141
-
142
- return result
File without changes
File without changes