syd 1.0.1__py3-none-any.whl → 1.1.0__py3-none-any.whl
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.
- syd/__init__.py +2 -10
- syd/flask_deployment/deployer.py +12 -3
- syd/flask_deployment/static/css/styles.css +26 -8
- syd/flask_deployment/static/css/viewer.css +90 -0
- syd/flask_deployment/static/js/viewer.js +394 -30
- syd/notebook_deployment/deployer.py +1 -3
- syd/notebook_deployment/widgets.py +45 -27
- syd/viewer.py +80 -5
- {syd-1.0.1.dist-info → syd-1.1.0.dist-info}/METADATA +30 -26
- syd-1.1.0.dist-info/RECORD +20 -0
- syd-1.0.1.dist-info/RECORD +0 -19
- {syd-1.0.1.dist-info → syd-1.1.0.dist-info}/WHEEL +0 -0
- {syd-1.0.1.dist-info → syd-1.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -36,13 +36,15 @@ class BaseWidget(Generic[T, W], ABC):
|
|
|
36
36
|
def __init__(
|
|
37
37
|
self,
|
|
38
38
|
parameter: T,
|
|
39
|
-
continuous: bool = False,
|
|
40
39
|
width: str = "auto",
|
|
41
40
|
margin: str = "3px 0px",
|
|
42
41
|
description_width: str = "initial",
|
|
43
42
|
):
|
|
44
43
|
self._widget = self._create_widget(
|
|
45
|
-
parameter,
|
|
44
|
+
parameter,
|
|
45
|
+
width,
|
|
46
|
+
margin,
|
|
47
|
+
description_width,
|
|
46
48
|
)
|
|
47
49
|
self._updating = False # Flag to prevent circular updates
|
|
48
50
|
# List of callbacks to remember for quick disabling/enabling
|
|
@@ -52,7 +54,6 @@ class BaseWidget(Generic[T, W], ABC):
|
|
|
52
54
|
def _create_widget(
|
|
53
55
|
self,
|
|
54
56
|
parameter: T,
|
|
55
|
-
continuous: bool,
|
|
56
57
|
width: str = "auto",
|
|
57
58
|
margin: str = "3px 0px",
|
|
58
59
|
description_width: str = "initial",
|
|
@@ -123,7 +124,6 @@ class TextWidget(BaseWidget[TextParameter, widgets.Text]):
|
|
|
123
124
|
def _create_widget(
|
|
124
125
|
self,
|
|
125
126
|
parameter: TextParameter,
|
|
126
|
-
continuous: bool,
|
|
127
127
|
width: str = "auto",
|
|
128
128
|
margin: str = "3px 0px",
|
|
129
129
|
description_width: str = "initial",
|
|
@@ -131,7 +131,7 @@ class TextWidget(BaseWidget[TextParameter, widgets.Text]):
|
|
|
131
131
|
return widgets.Text(
|
|
132
132
|
value=parameter.value,
|
|
133
133
|
description=parameter.name,
|
|
134
|
-
continuous=
|
|
134
|
+
continuous=False,
|
|
135
135
|
layout=widgets.Layout(width=width, margin=margin),
|
|
136
136
|
style={"description_width": description_width},
|
|
137
137
|
)
|
|
@@ -143,7 +143,6 @@ class BooleanWidget(BaseWidget[BooleanParameter, widgets.ToggleButton]):
|
|
|
143
143
|
def _create_widget(
|
|
144
144
|
self,
|
|
145
145
|
parameter: BooleanParameter,
|
|
146
|
-
continuous: bool,
|
|
147
146
|
width: str = "auto",
|
|
148
147
|
margin: str = "3px 0px",
|
|
149
148
|
description_width: str = "initial",
|
|
@@ -162,7 +161,6 @@ class SelectionWidget(BaseWidget[SelectionParameter, widgets.Dropdown]):
|
|
|
162
161
|
def _create_widget(
|
|
163
162
|
self,
|
|
164
163
|
parameter: SelectionParameter,
|
|
165
|
-
continuous: bool,
|
|
166
164
|
width: str = "auto",
|
|
167
165
|
margin: str = "3px 0px",
|
|
168
166
|
description_width: str = "initial",
|
|
@@ -198,7 +196,6 @@ class MultipleSelectionWidget(
|
|
|
198
196
|
def _create_widget(
|
|
199
197
|
self,
|
|
200
198
|
parameter: MultipleSelectionParameter,
|
|
201
|
-
continuous: bool,
|
|
202
199
|
width: str = "auto",
|
|
203
200
|
margin: str = "3px 0px",
|
|
204
201
|
description_width: str = "initial",
|
|
@@ -235,7 +232,6 @@ class IntegerWidget(BaseWidget[IntegerParameter, widgets.IntSlider]):
|
|
|
235
232
|
def _create_widget(
|
|
236
233
|
self,
|
|
237
234
|
parameter: IntegerParameter,
|
|
238
|
-
continuous: bool,
|
|
239
235
|
width: str = "auto",
|
|
240
236
|
margin: str = "3px 0px",
|
|
241
237
|
description_width: str = "initial",
|
|
@@ -247,7 +243,7 @@ class IntegerWidget(BaseWidget[IntegerParameter, widgets.IntSlider]):
|
|
|
247
243
|
max=parameter.max,
|
|
248
244
|
step=1,
|
|
249
245
|
description=parameter.name,
|
|
250
|
-
continuous_update=
|
|
246
|
+
continuous_update=False,
|
|
251
247
|
style={"description_width": description_width},
|
|
252
248
|
layout=widgets.Layout(width=width, margin=margin),
|
|
253
249
|
)
|
|
@@ -264,6 +260,10 @@ class IntegerWidget(BaseWidget[IntegerParameter, widgets.IntSlider]):
|
|
|
264
260
|
def extra_updates_from_parameter(self, parameter: IntegerParameter) -> None:
|
|
265
261
|
"""Update the widget attributes from the parameter."""
|
|
266
262
|
current_value = self._widget.value
|
|
263
|
+
if parameter.min > self._widget.max:
|
|
264
|
+
self._widget.max = parameter.min + 1
|
|
265
|
+
if parameter.max < self._widget.min:
|
|
266
|
+
self._widget.min = parameter.max - 1
|
|
267
267
|
self._widget.min = parameter.min
|
|
268
268
|
self._widget.max = parameter.max
|
|
269
269
|
self.value = max(parameter.min, min(parameter.max, current_value))
|
|
@@ -275,7 +275,6 @@ class FloatWidget(BaseWidget[FloatParameter, widgets.FloatSlider]):
|
|
|
275
275
|
def _create_widget(
|
|
276
276
|
self,
|
|
277
277
|
parameter: FloatParameter,
|
|
278
|
-
continuous: bool,
|
|
279
278
|
width: str = "auto",
|
|
280
279
|
margin: str = "3px 0px",
|
|
281
280
|
description_width: str = "initial",
|
|
@@ -287,7 +286,7 @@ class FloatWidget(BaseWidget[FloatParameter, widgets.FloatSlider]):
|
|
|
287
286
|
max=parameter.max,
|
|
288
287
|
step=parameter.step,
|
|
289
288
|
description=parameter.name,
|
|
290
|
-
continuous_update=
|
|
289
|
+
continuous_update=False,
|
|
291
290
|
style={"description_width": description_width},
|
|
292
291
|
layout=widgets.Layout(width=width, margin=margin),
|
|
293
292
|
)
|
|
@@ -305,6 +304,10 @@ class FloatWidget(BaseWidget[FloatParameter, widgets.FloatSlider]):
|
|
|
305
304
|
def extra_updates_from_parameter(self, parameter: FloatParameter) -> None:
|
|
306
305
|
"""Update the widget attributes from the parameter."""
|
|
307
306
|
current_value = self._widget.value
|
|
307
|
+
if parameter.min > self._widget.max:
|
|
308
|
+
self._widget.max = parameter.min + 1
|
|
309
|
+
if parameter.max < self._widget.min:
|
|
310
|
+
self._widget.min = parameter.max - 1
|
|
308
311
|
self._widget.min = parameter.min
|
|
309
312
|
self._widget.max = parameter.max
|
|
310
313
|
self._widget.step = parameter.step
|
|
@@ -317,7 +320,6 @@ class IntegerRangeWidget(BaseWidget[IntegerRangeParameter, widgets.IntRangeSlide
|
|
|
317
320
|
def _create_widget(
|
|
318
321
|
self,
|
|
319
322
|
parameter: IntegerRangeParameter,
|
|
320
|
-
continuous: bool,
|
|
321
323
|
width: str = "auto",
|
|
322
324
|
margin: str = "3px 0px",
|
|
323
325
|
description_width: str = "initial",
|
|
@@ -330,7 +332,7 @@ class IntegerRangeWidget(BaseWidget[IntegerRangeParameter, widgets.IntRangeSlide
|
|
|
330
332
|
max=parameter.max,
|
|
331
333
|
step=1,
|
|
332
334
|
description=parameter.name,
|
|
333
|
-
continuous_update=
|
|
335
|
+
continuous_update=False,
|
|
334
336
|
style={"description_width": description_width},
|
|
335
337
|
layout=widgets.Layout(width=width, margin=margin),
|
|
336
338
|
)
|
|
@@ -349,6 +351,10 @@ class IntegerRangeWidget(BaseWidget[IntegerRangeParameter, widgets.IntRangeSlide
|
|
|
349
351
|
def extra_updates_from_parameter(self, parameter: IntegerRangeParameter) -> None:
|
|
350
352
|
"""Update the widget attributes from the parameter."""
|
|
351
353
|
low, high = self._widget.value
|
|
354
|
+
if parameter.min > self._widget.max:
|
|
355
|
+
self._widget.max = parameter.min + 1
|
|
356
|
+
if parameter.max < self._widget.min:
|
|
357
|
+
self._widget.min = parameter.max - 1
|
|
352
358
|
self._widget.min = parameter.min
|
|
353
359
|
self._widget.max = parameter.max
|
|
354
360
|
# Ensure values stay within bounds
|
|
@@ -363,7 +369,6 @@ class FloatRangeWidget(BaseWidget[FloatRangeParameter, widgets.FloatRangeSlider]
|
|
|
363
369
|
def _create_widget(
|
|
364
370
|
self,
|
|
365
371
|
parameter: FloatRangeParameter,
|
|
366
|
-
continuous: bool,
|
|
367
372
|
width: str = "auto",
|
|
368
373
|
margin: str = "3px 0px",
|
|
369
374
|
description_width: str = "initial",
|
|
@@ -376,7 +381,7 @@ class FloatRangeWidget(BaseWidget[FloatRangeParameter, widgets.FloatRangeSlider]
|
|
|
376
381
|
max=parameter.max,
|
|
377
382
|
step=parameter.step,
|
|
378
383
|
description=parameter.name,
|
|
379
|
-
continuous_update=
|
|
384
|
+
continuous_update=False,
|
|
380
385
|
style={"description_width": description_width},
|
|
381
386
|
layout=widgets.Layout(width=width, margin=margin),
|
|
382
387
|
)
|
|
@@ -396,6 +401,10 @@ class FloatRangeWidget(BaseWidget[FloatRangeParameter, widgets.FloatRangeSlider]
|
|
|
396
401
|
def extra_updates_from_parameter(self, parameter: FloatRangeParameter) -> None:
|
|
397
402
|
"""Update the widget attributes from the parameter."""
|
|
398
403
|
low, high = self._widget.value
|
|
404
|
+
if parameter.min > self._widget.max:
|
|
405
|
+
self._widget.max = parameter.min + 1
|
|
406
|
+
if parameter.max < self._widget.min:
|
|
407
|
+
self._widget.min = parameter.max - 1
|
|
399
408
|
self._widget.min = parameter.min
|
|
400
409
|
self._widget.max = parameter.max
|
|
401
410
|
self._widget.step = parameter.step
|
|
@@ -411,7 +420,6 @@ class UnboundedIntegerWidget(BaseWidget[UnboundedIntegerParameter, widgets.IntTe
|
|
|
411
420
|
def _create_widget(
|
|
412
421
|
self,
|
|
413
422
|
parameter: UnboundedIntegerParameter,
|
|
414
|
-
continuous: bool,
|
|
415
423
|
width: str = "auto",
|
|
416
424
|
margin: str = "3px 0px",
|
|
417
425
|
description_width: str = "initial",
|
|
@@ -440,7 +448,6 @@ class UnboundedFloatWidget(BaseWidget[UnboundedFloatParameter, widgets.FloatText
|
|
|
440
448
|
def _create_widget(
|
|
441
449
|
self,
|
|
442
450
|
parameter: UnboundedFloatParameter,
|
|
443
|
-
continuous: bool,
|
|
444
451
|
width: str = "auto",
|
|
445
452
|
margin: str = "3px 0px",
|
|
446
453
|
description_width: str = "initial",
|
|
@@ -470,7 +477,6 @@ class ButtonWidget(BaseWidget[ButtonAction, widgets.Button]):
|
|
|
470
477
|
def _create_widget(
|
|
471
478
|
self,
|
|
472
479
|
parameter: ButtonAction,
|
|
473
|
-
continuous: bool,
|
|
474
480
|
width: str = "auto",
|
|
475
481
|
margin: str = "3px 0px",
|
|
476
482
|
description_width: str = "initial",
|
|
@@ -514,19 +520,32 @@ class ButtonWidget(BaseWidget[ButtonAction, widgets.Button]):
|
|
|
514
520
|
|
|
515
521
|
def create_widget(
|
|
516
522
|
parameter: Union[Parameter[Any], ButtonAction],
|
|
517
|
-
continuous: bool = False,
|
|
518
523
|
width: str = "auto",
|
|
519
524
|
margin: str = "3px 0px",
|
|
520
525
|
description_width: str = "initial",
|
|
521
526
|
) -> BaseWidget[Union[Parameter[Any], ButtonAction], widgets.Widget]:
|
|
522
527
|
"""Create and return the appropriate widget for the given parameter.
|
|
523
528
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
529
|
+
Parameters
|
|
530
|
+
----------
|
|
531
|
+
parameter : Union[Parameter[Any], ButtonAction]
|
|
532
|
+
The parameter to create a widget for.
|
|
533
|
+
width : str, optional
|
|
534
|
+
Width of the widget. Default is 'auto'.
|
|
535
|
+
margin : str, optional
|
|
536
|
+
Margin of the widget. Default is '3px 0px'.
|
|
537
|
+
description_width : str, optional
|
|
538
|
+
Width of the description label. Default is 'initial'.
|
|
539
|
+
|
|
540
|
+
Returns
|
|
541
|
+
-------
|
|
542
|
+
BaseWidget[Union[Parameter[Any], ButtonAction], widgets.Widget]
|
|
543
|
+
The appropriate widget instance for the given parameter type.
|
|
544
|
+
|
|
545
|
+
Raises
|
|
546
|
+
------
|
|
547
|
+
ValueError
|
|
548
|
+
If no widget implementation exists for the given parameter type.
|
|
530
549
|
"""
|
|
531
550
|
widget_map = {
|
|
532
551
|
TextParameter: TextWidget,
|
|
@@ -562,7 +581,6 @@ def create_widget(
|
|
|
562
581
|
|
|
563
582
|
return widget_class(
|
|
564
583
|
parameter,
|
|
565
|
-
continuous,
|
|
566
584
|
width=width,
|
|
567
585
|
margin=margin,
|
|
568
586
|
description_width=description_width,
|
syd/viewer.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import List, Any, Callable, Dict, Tuple, Union, Optional
|
|
1
|
+
from typing import List, Any, Callable, Dict, Tuple, Union, Optional, Literal
|
|
2
2
|
from functools import wraps, partial
|
|
3
3
|
import inspect
|
|
4
4
|
from contextlib import contextmanager
|
|
@@ -12,6 +12,36 @@ NO_UPDATE = NoUpdate()
|
|
|
12
12
|
NO_INITIAL_VALUE = NoInitialValue()
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
def make_viewer(plot_func: Optional[Callable] = None) -> "Viewer":
|
|
16
|
+
"""Create an empty viewer object.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
plot_func : Callable, optional
|
|
21
|
+
A function that takes a state dictionary and returns a matplotlib figure.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
viewer : Viewer
|
|
26
|
+
A new viewer object
|
|
27
|
+
|
|
28
|
+
Examples
|
|
29
|
+
--------
|
|
30
|
+
>>> from syd import make_viewer
|
|
31
|
+
>>> def plot(state):
|
|
32
|
+
>>> ... generate figure, plot stuff ...
|
|
33
|
+
>>> return fig
|
|
34
|
+
>>> viewer = make_viewer(plot)
|
|
35
|
+
>>> viewer.add_float('x', value=1.0, min=0, max=10)
|
|
36
|
+
>>> viewer.on_change('x', viewer.update_based_on_x)
|
|
37
|
+
>>> viewer.show()
|
|
38
|
+
"""
|
|
39
|
+
viewer = Viewer()
|
|
40
|
+
if plot_func is not None:
|
|
41
|
+
viewer.set_plot(plot_func)
|
|
42
|
+
return viewer
|
|
43
|
+
|
|
44
|
+
|
|
15
45
|
def validate_parameter_operation(
|
|
16
46
|
operation: str,
|
|
17
47
|
parameter_type: Union[ParameterType, ActionType],
|
|
@@ -212,6 +242,52 @@ class Viewer:
|
|
|
212
242
|
"""Set the plot method for the viewer"""
|
|
213
243
|
self.plot = self._prepare_function(func, context="Setting plot:")
|
|
214
244
|
|
|
245
|
+
def show(
|
|
246
|
+
self,
|
|
247
|
+
controls_position: Literal["left", "top", "right", "bottom"] = "left",
|
|
248
|
+
controls_width_percent: int = 20,
|
|
249
|
+
suppress_warnings: bool = True,
|
|
250
|
+
update_threshold: float = 1.0,
|
|
251
|
+
):
|
|
252
|
+
"""Show the viewer in a notebook
|
|
253
|
+
|
|
254
|
+
Same as deploy(env="notebook") except it doesn't return the viewer object.
|
|
255
|
+
"""
|
|
256
|
+
_ = self.deploy(
|
|
257
|
+
env="notebook",
|
|
258
|
+
controls_position=controls_position,
|
|
259
|
+
controls_width_percent=controls_width_percent,
|
|
260
|
+
suppress_warnings=suppress_warnings,
|
|
261
|
+
update_threshold=update_threshold,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
def share(
|
|
265
|
+
self,
|
|
266
|
+
controls_position: str = "left",
|
|
267
|
+
fig_dpi: int = 300,
|
|
268
|
+
controls_width_percent: int = 20,
|
|
269
|
+
suppress_warnings: bool = True,
|
|
270
|
+
debug: bool = False,
|
|
271
|
+
host: str = "127.0.0.1",
|
|
272
|
+
port: Optional[int] = None,
|
|
273
|
+
open_browser: bool = True,
|
|
274
|
+
):
|
|
275
|
+
"""Share the viewer on a web browser using Flask
|
|
276
|
+
|
|
277
|
+
Same as deploy(env="browser") except it doesn't return the viewer object.
|
|
278
|
+
"""
|
|
279
|
+
_ = self.deploy(
|
|
280
|
+
env="browser",
|
|
281
|
+
controls_position=controls_position,
|
|
282
|
+
fig_dpi=fig_dpi,
|
|
283
|
+
controls_width_percent=controls_width_percent,
|
|
284
|
+
suppress_warnings=suppress_warnings,
|
|
285
|
+
debug=debug,
|
|
286
|
+
host=host,
|
|
287
|
+
port=port,
|
|
288
|
+
open_browser=open_browser,
|
|
289
|
+
)
|
|
290
|
+
|
|
215
291
|
def deploy(self, env: str = "notebook", **kwargs):
|
|
216
292
|
"""Deploy the app in a notebook or standalone environment"""
|
|
217
293
|
env = env.lower()
|
|
@@ -223,7 +299,7 @@ class Viewer:
|
|
|
223
299
|
deployer.deploy()
|
|
224
300
|
return self
|
|
225
301
|
|
|
226
|
-
elif env == "browser"
|
|
302
|
+
elif env == "browser":
|
|
227
303
|
# On demand import because the deployers need to import the viewer
|
|
228
304
|
from .flask_deployment.deployer import FlaskDeployer
|
|
229
305
|
|
|
@@ -238,7 +314,7 @@ class Viewer:
|
|
|
238
314
|
|
|
239
315
|
else:
|
|
240
316
|
raise ValueError(
|
|
241
|
-
f"Unsupported environment: {env}, only 'notebook', '
|
|
317
|
+
f"Unsupported environment: {env}, only 'notebook', 'browser' are supported right now."
|
|
242
318
|
)
|
|
243
319
|
|
|
244
320
|
@contextmanager
|
|
@@ -379,9 +455,8 @@ class Viewer:
|
|
|
379
455
|
try:
|
|
380
456
|
self._in_callbacks = True
|
|
381
457
|
if name in self.callbacks:
|
|
382
|
-
state = self.state
|
|
383
458
|
for callback in self.callbacks[name]:
|
|
384
|
-
callback(state)
|
|
459
|
+
callback(self.state)
|
|
385
460
|
finally:
|
|
386
461
|
self._in_callbacks = False
|
|
387
462
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: syd
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: A Python package for making GUIs for data science easy.
|
|
5
5
|
Project-URL: Homepage, https://github.com/landoskape/syd
|
|
6
6
|
Author-email: Andrew Landau <andrew+tyler+landau+getridofthisanddtheplusses@gmail.com>
|
|
@@ -47,7 +47,7 @@ Have you ever wanted to look through all your data really quickly interactively?
|
|
|
47
47
|
|
|
48
48
|
Syd is a system for creating a data viewing GUI that you can view in a jupyter notebook or in a web browser. And guess what? Since it can open in a web browser, you can even open it on any other computer on your local network! For example, your PI's computer. Gone are the days of single random examples that they make infinitely stubborn conclusions about. Now, you can look at all the examples, quickly and easily, on their computer. And that's why Syd stands for share your data!
|
|
49
49
|
|
|
50
|
-
Okay, so what is it? Syd is an automated system to convert some basic python plotting code into an interactive GUI. This means you only have to think about _**what
|
|
50
|
+
Okay, so what is it? Syd is an automated system to convert some basic python plotting code into an interactive GUI. This means you only have to think about _**what you want to plot**_ and _**which parameters**_ you want to be interactive. Syd handles all the behind-the-scenes action required to make an interface. And guess what? That means you get to _**spend your time thinking**_ about your data, rather than writing code to look at it. And that's why Syd stands for Science, Yes! Dayummmm!
|
|
51
51
|
|
|
52
52
|
## Installation
|
|
53
53
|
It's easy, just use pip install. The dependencies are light so it should work in most environments.
|
|
@@ -56,13 +56,9 @@ pip install syd
|
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
## Documentation
|
|
59
|
-
The full documentation is available
|
|
59
|
+
The full documentation is available [here](https://shareyourdata.readthedocs.io/). It includes a quick start guide, a comprehensive tutorial, and an API reference for the different elements of Syd. If you have any questions or want to suggest improvements to the docs, please let us know on the [github issues page](https://github.com/landoskape/syd/issues)!
|
|
60
60
|
|
|
61
61
|
## Quick Start
|
|
62
|
-
<!-- <div style="float: right; margin-left: 100px; margin-bottom: 10px;">
|
|
63
|
-
<img src="./docs/assets/viewer_screenshots/readme_example_gif.gif" alt="Syd" width="300" align="right"/>
|
|
64
|
-
</div> -->
|
|
65
|
-
|
|
66
62
|
This is an example of a sine wave viewer which is about as simple as it gets. You can choose which env to use - if you use ``env="notebook"`` then the GUI will deploy as the output of a jupyter cell (this only works in jupyter!). If you use ``env="browser"`` then the GUI will open a page in your default web browser and you can interact with the data there (works in jupyter notebooks and also from python scripts!).
|
|
67
63
|
|
|
68
64
|
```python
|
|
@@ -82,12 +78,12 @@ viewer.add_float("amplitude", value=1.0, min=0.1, max=2.0)
|
|
|
82
78
|
viewer.add_float("frequency", value=1.0, min=0.1, max=5.0)
|
|
83
79
|
viewer.add_selection("color", value="red", options=["red", "blue", "green", "black"])
|
|
84
80
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
viewer = viewer.deploy(env=env)
|
|
81
|
+
viewer.show() # for viewing in a jupyter notebook
|
|
82
|
+
# viewer.share() # for viewing in a web browser
|
|
88
83
|
```
|
|
89
84
|
|
|
90
85
|

|
|
86
|
+
|
|
91
87
|
### More Examples
|
|
92
88
|
We have several examples of more complex viewers with detailed explanations in the comments. Here are the links and descriptions to each of them:
|
|
93
89
|
|
|
@@ -101,7 +97,7 @@ We have several examples of more complex viewers with detailed explanations in t
|
|
|
101
97
|
|
|
102
98
|
|
|
103
99
|
### Data loading
|
|
104
|
-
Thinking about how to get data into a Syd viewer can be non-intuitive. For some examples that showcase different ways to get your data into a Syd viewer, check out the [data loading example](examples/3-data_loading.ipynb). Or, if you just want a quick example, check this out:
|
|
100
|
+
Thinking about how to get data into a Syd viewer can be non-intuitive. For some examples that showcase different ways to get your data into a Syd viewer, check out the [data loading example](examples/3-data_loading.ipynb). Or, if you just want a quick and fast example, check this one out:
|
|
105
101
|
```python
|
|
106
102
|
import numpy as np
|
|
107
103
|
from matplotlib import pyplot as plt
|
|
@@ -122,15 +118,15 @@ def plot(state):
|
|
|
122
118
|
# Since plot "knows" about the data variable, all you need to do is pass the plot
|
|
123
119
|
# function to the syd viewer and it'll be able to access the data once deployed!
|
|
124
120
|
viewer = make_viewer(plot)
|
|
125
|
-
viewer.
|
|
121
|
+
viewer.show()
|
|
126
122
|
```
|
|
127
123
|
|
|
128
124
|
### Handling Hierarchical Callbacks
|
|
129
|
-
Syd dramatically reduces the amount of work you need to do to build a GUI for viewing your data. However, it can still be a bit complicated to think about callbacks. Below is a quick demonstration
|
|
125
|
+
Syd dramatically reduces the amount of work you need to do to build a GUI for viewing your data. However, it can still be a bit complicated to think about callbacks. Below is a quick demonstration. To try it yourself, check out the full example [here](examples/4-hierarchical_callbacks.ipynb) or open it in colab [](https://colab.research.google.com/github/landoskape/syd/blob/main/examples/4-hierarchical_callbacks.ipynb).
|
|
130
126
|
|
|
131
|
-
For example, suppose your dataset is composed of electrophysiology recordings from 3 mice, where each mouse has a different number of sesssions, and each session has a different number of neurons. You want to build a viewer to
|
|
127
|
+
For example, suppose your dataset is composed of electrophysiology recordings from 3 mice, where each mouse has a different number of sesssions, and each session has a different number of neurons. You want to build a viewer to view a particular neuron from a particular session from a particular mouse. But the viewer will break if you try to index to session 5 for mouse 2 when mouse 2 has less than 5 sessions!
|
|
132
128
|
|
|
133
|
-
This is where hierarchical callbacks come in. There's a straightforward pattern to handling this kind of situation that you can follow. You can write a callback for each **level** of the hierarchy. Then, each callback can call the next callback in the hierarchy. It looks like this:
|
|
129
|
+
This is where hierarchical callbacks come in. There's a straightforward pattern to handling this kind of situation that you can follow. You can write a callback for each **level** of the hierarchy. Then, each callback can **update** the state and call the next callback in the hierarchy once it's finished. It looks like this:
|
|
134
130
|
```python
|
|
135
131
|
import numpy as np
|
|
136
132
|
from syd import Viewer # Much easier to build a Viewer class for hierarchical callbacks
|
|
@@ -141,19 +137,22 @@ class MouseViewer(Viewer):
|
|
|
141
137
|
|
|
142
138
|
self.add_selection("mouse", options=list(mice_names))
|
|
143
139
|
|
|
144
|
-
# We don't know how many sessions or neurons to pick from yet
|
|
140
|
+
# We don't know how many sessions or neurons to pick from yet,
|
|
141
|
+
# so just set the max to 1 for now.
|
|
145
142
|
self.add_integer("session", min=0, max=1)
|
|
146
143
|
self.add_integer("neuron", min=0, max=1)
|
|
147
144
|
|
|
148
|
-
# Any time the mouse changes, update the sessions to pick from
|
|
145
|
+
# Any time the mouse changes, update the sessions to pick from!
|
|
149
146
|
self.on_change("mouse", self.update_mouse)
|
|
150
147
|
|
|
151
|
-
# Any time the session changes, update the neurons to pick from
|
|
148
|
+
# Any time the session changes, update the neurons to pick from!
|
|
152
149
|
self.on_change("session", self.update_session)
|
|
153
150
|
|
|
154
151
|
# Since we built callbacks for setting the range of the session
|
|
155
|
-
# and neuron parameters, we can use them here
|
|
156
|
-
#
|
|
152
|
+
# and neuron parameters, we can use them here so the viewer is
|
|
153
|
+
# fully ready and up to date.
|
|
154
|
+
|
|
155
|
+
# To get the state, use self.state, which is the current
|
|
157
156
|
# state of the viewer (in the init function, it'll just be the
|
|
158
157
|
# default value for each parameter you've added already).
|
|
159
158
|
self.update_mouse(self.state)
|
|
@@ -166,9 +165,13 @@ class MouseViewer(Viewer):
|
|
|
166
165
|
self.update_integer("session", max=num_sessions - 1)
|
|
167
166
|
|
|
168
167
|
# Now we need to update the neurons to choose from ....
|
|
169
|
-
|
|
170
|
-
#
|
|
171
|
-
#
|
|
168
|
+
|
|
169
|
+
# But! Updating the session parameter's max value might trigger a change
|
|
170
|
+
# to the current session value. This ~won't be reflected~ in the state
|
|
171
|
+
# dictionary that was passed to this function.
|
|
172
|
+
|
|
173
|
+
# So, we need to load the ~NEW~ state dictionary, which is always
|
|
174
|
+
# accessible as self.state (or viewer.state if you're not using a class).
|
|
172
175
|
new_state = self.state
|
|
173
176
|
|
|
174
177
|
# Then perform the session update callback!
|
|
@@ -189,7 +192,7 @@ class MouseViewer(Viewer):
|
|
|
189
192
|
|
|
190
193
|
# Now we can create a viewer and deploy it
|
|
191
194
|
viewer = MouseViewer(["Mouse 1", "Mouse 2", "Mouse 3"])
|
|
192
|
-
viewer.
|
|
195
|
+
viewer.show()
|
|
193
196
|
```
|
|
194
197
|
|
|
195
198
|
## License
|
|
@@ -216,7 +219,6 @@ black . # from the root directory of the repo
|
|
|
216
219
|
|
|
217
220
|
## To-Do List
|
|
218
221
|
- Layout controls
|
|
219
|
-
- [ ] Improve the display and make it look better
|
|
220
222
|
- [ ] Add a "save" button that saves the current state of the viewer to a json file
|
|
221
223
|
- [ ] Add a "load" button that loads the viewer state from a json file
|
|
222
224
|
- [ ] Add a "freeze" button that allows the user to update state variables without updating the plot until unfreezing
|
|
@@ -225,4 +227,6 @@ black . # from the root directory of the repo
|
|
|
225
227
|
- Export options:
|
|
226
228
|
- [ ] Export lite: export the viewer as a HTML/Java package that contains an incomplete set of renderings of figures -- using a certain set of parameters.
|
|
227
229
|
- [ ] Export full: export the viewer in a way that contains the data to give full functionality.
|
|
228
|
-
- [ ]
|
|
230
|
+
- [ ] Idea for sharing: https://github.com/analyticalmonk/awesome-neuroscience, https://github.com/fasouto/awesome-dataviz
|
|
231
|
+
- [ ] The handling of value in Selection parameters is kind of weird.... I think we need to think more about what to do for fails!!!!
|
|
232
|
+
- [ ] Range parameters render poorly in browser mode.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
syd/__init__.py,sha256=scju2nVDJzGslTNmtc0Mp_ftux6TzB4BAOCrgU9R19A,63
|
|
2
|
+
syd/parameters.py,sha256=dlnYOVsi1CDtC2toVECf0kNBRipVrtUjr6XVX86b5MA,42886
|
|
3
|
+
syd/support.py,sha256=7wztPMaL750opyBDnaYYRVyBR5QUJtVspDzQWpu50rk,6106
|
|
4
|
+
syd/viewer.py,sha256=t4NWOMrzsVHwKsnEJ8Um_wDGSEE9FcyZyb_fp_pVpko,51516
|
|
5
|
+
syd/flask_deployment/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
6
|
+
syd/flask_deployment/deployer.py,sha256=LnIWvFBxphFONABJCD6WoMu-a89YWIV0ZCuN_sgvB1A,25657
|
|
7
|
+
syd/flask_deployment/testing_principles.md,sha256=GyULM97sDeie8h3tSPoduOckdMNGyWuwm1RdHo5jzK0,10130
|
|
8
|
+
syd/flask_deployment/static/__init__.py,sha256=ieWE8NKR-APw7h4Ge0ooZGk6wZrneSSs_1cMyTPbQSA,65
|
|
9
|
+
syd/flask_deployment/static/css/styles.css,sha256=Gec78sj73IJDHz5OzE68e6LqyQf4E_JX0tc5zmavT4A,5533
|
|
10
|
+
syd/flask_deployment/static/css/viewer.css,sha256=9HToyeDhTA1Vhawmvev7ceyxKAVZASSX_azj8Hh-OVI,1644
|
|
11
|
+
syd/flask_deployment/static/js/viewer.js,sha256=aIh0gxrwMWUYVge4taj0pucg2RiCcWVuV9ekBvUVsPU,40594
|
|
12
|
+
syd/flask_deployment/templates/__init__.py,sha256=ieWE8NKR-APw7h4Ge0ooZGk6wZrneSSs_1cMyTPbQSA,65
|
|
13
|
+
syd/flask_deployment/templates/index.html,sha256=fr1g9IOwNttULhQCIcw_fo0sNpmdgznSGfPStQllR_E,1594
|
|
14
|
+
syd/notebook_deployment/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
syd/notebook_deployment/deployer.py,sha256=ELtYiR6ac8Rg0I1Hh8_K-w5j_oZIAXf-nDazWvbD3Uc,12225
|
|
16
|
+
syd/notebook_deployment/widgets.py,sha256=ptys7exVA6NCF4eCRZMTPJblT0ZbtPdN4o2A0Yh5Cfc,20781
|
|
17
|
+
syd-1.1.0.dist-info/METADATA,sha256=tSSGUSgt_nNjzDYUGouMjas2UKFpZZa9oOfwgvMsHL0,13952
|
|
18
|
+
syd-1.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
19
|
+
syd-1.1.0.dist-info/licenses/LICENSE,sha256=YF6QR6Vjxcg5b_sYIyqkME7FZYau5TfEUGTG-0JeRK0,35129
|
|
20
|
+
syd-1.1.0.dist-info/RECORD,,
|
syd-1.0.1.dist-info/RECORD
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
syd/__init__.py,sha256=Lx1We_TNKsV2Co9v5RB05WgqXpFqieSBh-y3zF0q1LE,250
|
|
2
|
-
syd/parameters.py,sha256=dlnYOVsi1CDtC2toVECf0kNBRipVrtUjr6XVX86b5MA,42886
|
|
3
|
-
syd/support.py,sha256=7wztPMaL750opyBDnaYYRVyBR5QUJtVspDzQWpu50rk,6106
|
|
4
|
-
syd/viewer.py,sha256=UYDfH9TNds0CiC3vThINLrMNZE8ikSFqJHgjM8yMD94,49323
|
|
5
|
-
syd/flask_deployment/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
6
|
-
syd/flask_deployment/deployer.py,sha256=w-zuXX1PCnWRa_VCevbQ7yqMTNH5x3Dri-sQQXOF1sM,25110
|
|
7
|
-
syd/flask_deployment/testing_principles.md,sha256=GyULM97sDeie8h3tSPoduOckdMNGyWuwm1RdHo5jzK0,10130
|
|
8
|
-
syd/flask_deployment/static/__init__.py,sha256=ieWE8NKR-APw7h4Ge0ooZGk6wZrneSSs_1cMyTPbQSA,65
|
|
9
|
-
syd/flask_deployment/static/css/styles.css,sha256=gA-Urdq9wmd-XpVcxILxPGlkiOtKtujG7Mw7dWftIVM,5210
|
|
10
|
-
syd/flask_deployment/static/js/viewer.js,sha256=kSY24VGjlQLe-jtPOEU1nE7U0ALC_ZaVzGIBuEXZsTs,27441
|
|
11
|
-
syd/flask_deployment/templates/__init__.py,sha256=ieWE8NKR-APw7h4Ge0ooZGk6wZrneSSs_1cMyTPbQSA,65
|
|
12
|
-
syd/flask_deployment/templates/index.html,sha256=fr1g9IOwNttULhQCIcw_fo0sNpmdgznSGfPStQllR_E,1594
|
|
13
|
-
syd/notebook_deployment/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
syd/notebook_deployment/deployer.py,sha256=sf3f9STRXpU1ac-OGENYv1g5yBpUaiIcREBKtKWvIlA,12324
|
|
15
|
-
syd/notebook_deployment/widgets.py,sha256=UbkasRf8wY9beUwpwJYjv9X0Lus3DvgAEIORHwaC-zA,20058
|
|
16
|
-
syd-1.0.1.dist-info/METADATA,sha256=pHDBBxQoLRmHRBsWPtF5VlYI0hpd_tX6zcmYBNhdIJQ,13668
|
|
17
|
-
syd-1.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
18
|
-
syd-1.0.1.dist-info/licenses/LICENSE,sha256=YF6QR6Vjxcg5b_sYIyqkME7FZYau5TfEUGTG-0JeRK0,35129
|
|
19
|
-
syd-1.0.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|