syd 0.2.0__py3-none-any.whl → 1.0.1__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 +1 -1
- syd/flask_deployment/__init__.py +0 -6
- syd/flask_deployment/deployer.py +446 -460
- syd/flask_deployment/static/css/styles.css +50 -16
- syd/flask_deployment/static/js/viewer.js +154 -67
- syd/flask_deployment/templates/index.html +1 -1
- syd/notebook_deployment/__init__.py +0 -1
- syd/notebook_deployment/deployer.py +212 -227
- syd/notebook_deployment/widgets.py +2 -2
- syd/parameters.py +69 -104
- syd/support.py +27 -0
- syd/viewer.py +10 -6
- syd-1.0.1.dist-info/METADATA +228 -0
- syd-1.0.1.dist-info/RECORD +19 -0
- syd-0.2.0.dist-info/METADATA +0 -126
- syd-0.2.0.dist-info/RECORD +0 -19
- {syd-0.2.0.dist-info → syd-1.0.1.dist-info}/WHEEL +0 -0
- {syd-0.2.0.dist-info → syd-1.0.1.dist-info}/licenses/LICENSE +0 -0
syd/parameters.py
CHANGED
|
@@ -3,14 +3,13 @@ from dataclasses import dataclass, field
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from copy import deepcopy
|
|
6
|
-
from warnings import warn
|
|
7
6
|
|
|
8
7
|
from .support import (
|
|
9
8
|
NoInitialValue,
|
|
10
9
|
ParameterMeta,
|
|
11
10
|
ParameterUpdateError,
|
|
12
|
-
ParameterUpdateWarning,
|
|
13
11
|
get_parameter_attributes,
|
|
12
|
+
warn_parameter_update,
|
|
14
13
|
)
|
|
15
14
|
|
|
16
15
|
T = TypeVar("T")
|
|
@@ -433,12 +432,10 @@ class SelectionParameter(Parameter[Any]):
|
|
|
433
432
|
|
|
434
433
|
# If value is not found after all checks, reset to first option
|
|
435
434
|
if not value_found:
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
f"Value {self.value} not in options, setting to first option ({self.options[0]})",
|
|
441
|
-
)
|
|
435
|
+
warn_parameter_update(
|
|
436
|
+
self.name,
|
|
437
|
+
type(self).__name__,
|
|
438
|
+
f"Value {self.value} not in options, setting to first option ({self.options[0]})",
|
|
442
439
|
)
|
|
443
440
|
self.value = self.options[0]
|
|
444
441
|
|
|
@@ -558,22 +555,18 @@ class MultipleSelectionParameter(Parameter[List[Any]]):
|
|
|
558
555
|
def _validate_update(self) -> None:
|
|
559
556
|
self.options = self._validate_options(self.options)
|
|
560
557
|
if not isinstance(self.value, (list, tuple)):
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
f"For parameter {self.name}, value {self.value} is not a list or tuple. Setting to empty list.",
|
|
566
|
-
)
|
|
558
|
+
warn_parameter_update(
|
|
559
|
+
self.name,
|
|
560
|
+
type(self).__name__,
|
|
561
|
+
f"For parameter {self.name}, value {self.value} is not a list or tuple. Setting to empty list.",
|
|
567
562
|
)
|
|
568
563
|
self.value = []
|
|
569
564
|
if not all(val in self.options for val in self.value):
|
|
570
565
|
invalid = [val for val in self.value if val not in self.options]
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
f"For parameter {self.name}, value {self.value} contains invalid selections: {invalid}. Setting to empty list.",
|
|
576
|
-
)
|
|
566
|
+
warn_parameter_update(
|
|
567
|
+
self.name,
|
|
568
|
+
type(self).__name__,
|
|
569
|
+
f"For parameter {self.name}, value {self.value} contains invalid selections: {invalid}. Setting to empty list.",
|
|
577
570
|
)
|
|
578
571
|
self.value = []
|
|
579
572
|
# Keep only unique values while preserving order based on self.options
|
|
@@ -652,21 +645,17 @@ class IntegerParameter(Parameter[int]):
|
|
|
652
645
|
|
|
653
646
|
if compare_to_range:
|
|
654
647
|
if new_value < self.min:
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
f"Value {new_value} below minimum {self.min}, clamping",
|
|
660
|
-
)
|
|
648
|
+
warn_parameter_update(
|
|
649
|
+
self.name,
|
|
650
|
+
type(self).__name__,
|
|
651
|
+
f"Value {new_value} below minimum {self.min}, clamping",
|
|
661
652
|
)
|
|
662
653
|
new_value = self.min
|
|
663
654
|
if new_value > self.max:
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
f"Value {new_value} above maximum {self.max}, clamping",
|
|
669
|
-
)
|
|
655
|
+
warn_parameter_update(
|
|
656
|
+
self.name,
|
|
657
|
+
type(self).__name__,
|
|
658
|
+
f"Value {new_value} above maximum {self.max}, clamping",
|
|
670
659
|
)
|
|
671
660
|
new_value = self.max
|
|
672
661
|
return int(new_value)
|
|
@@ -688,12 +677,10 @@ class IntegerParameter(Parameter[int]):
|
|
|
688
677
|
"IntegerParameter must have both min and max bounds",
|
|
689
678
|
)
|
|
690
679
|
if self.min > self.max:
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
f"Min value greater than max value, swapping",
|
|
696
|
-
)
|
|
680
|
+
warn_parameter_update(
|
|
681
|
+
self.name,
|
|
682
|
+
type(self).__name__,
|
|
683
|
+
f"Min value greater than max value, swapping",
|
|
697
684
|
)
|
|
698
685
|
self.min, self.max = self.max, self.min
|
|
699
686
|
self.value = self._validate(self.value)
|
|
@@ -788,21 +775,17 @@ class FloatParameter(Parameter[float]):
|
|
|
788
775
|
|
|
789
776
|
if compare_to_range:
|
|
790
777
|
if new_value < self.min:
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
f"Value {new_value} below minimum {self.min}, clamping",
|
|
796
|
-
)
|
|
778
|
+
warn_parameter_update(
|
|
779
|
+
self.name,
|
|
780
|
+
type(self).__name__,
|
|
781
|
+
f"Value {new_value} below minimum {self.min}, clamping",
|
|
797
782
|
)
|
|
798
783
|
new_value = self.min
|
|
799
784
|
if new_value > self.max:
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
f"Value {new_value} above maximum {self.max}, clamping",
|
|
805
|
-
)
|
|
785
|
+
warn_parameter_update(
|
|
786
|
+
self.name,
|
|
787
|
+
type(self).__name__,
|
|
788
|
+
f"Value {new_value} above maximum {self.max}, clamping",
|
|
806
789
|
)
|
|
807
790
|
new_value = self.max
|
|
808
791
|
|
|
@@ -825,12 +808,10 @@ class FloatParameter(Parameter[float]):
|
|
|
825
808
|
"FloatParameter must have both min and max bounds",
|
|
826
809
|
)
|
|
827
810
|
if self.min > self.max:
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
f"Min value greater than max value, swapping",
|
|
833
|
-
)
|
|
811
|
+
warn_parameter_update(
|
|
812
|
+
self.name,
|
|
813
|
+
type(self).__name__,
|
|
814
|
+
f"Min value greater than max value, swapping",
|
|
834
815
|
)
|
|
835
816
|
self.min, self.max = self.max, self.min
|
|
836
817
|
self.value = self._validate(self.value)
|
|
@@ -932,31 +913,25 @@ class IntegerRangeParameter(Parameter[Tuple[int, int]]):
|
|
|
932
913
|
high = self._validate_single(new_value[1])
|
|
933
914
|
|
|
934
915
|
if low > high:
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
f"Low value {low} greater than high value {high}, swapping",
|
|
940
|
-
)
|
|
916
|
+
warn_parameter_update(
|
|
917
|
+
self.name,
|
|
918
|
+
type(self).__name__,
|
|
919
|
+
f"Low value {low} greater than high value {high}, swapping",
|
|
941
920
|
)
|
|
942
921
|
low, high = high, low
|
|
943
922
|
|
|
944
923
|
if low < self.min:
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
f"Low value {low} below minimum {self.min}, clamping",
|
|
950
|
-
)
|
|
924
|
+
warn_parameter_update(
|
|
925
|
+
self.name,
|
|
926
|
+
type(self).__name__,
|
|
927
|
+
f"Low value {low} below minimum {self.min}, clamping",
|
|
951
928
|
)
|
|
952
929
|
low = self.min
|
|
953
930
|
if high > self.max:
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
f"High value {high} above maximum {self.max}, clamping",
|
|
959
|
-
)
|
|
931
|
+
warn_parameter_update(
|
|
932
|
+
self.name,
|
|
933
|
+
type(self).__name__,
|
|
934
|
+
f"High value {high} above maximum {self.max}, clamping",
|
|
960
935
|
)
|
|
961
936
|
high = self.max
|
|
962
937
|
|
|
@@ -979,12 +954,10 @@ class IntegerRangeParameter(Parameter[Tuple[int, int]]):
|
|
|
979
954
|
"IntegerRangeParameter must have both min and max bounds",
|
|
980
955
|
)
|
|
981
956
|
if self.min > self.max:
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
f"Min value greater than max value, swapping",
|
|
987
|
-
)
|
|
957
|
+
warn_parameter_update(
|
|
958
|
+
self.name,
|
|
959
|
+
type(self).__name__,
|
|
960
|
+
f"Min value greater than max value, swapping",
|
|
988
961
|
)
|
|
989
962
|
self.min, self.max = self.max, self.min
|
|
990
963
|
self.value = self._validate(self.value)
|
|
@@ -1102,31 +1075,25 @@ class FloatRangeParameter(Parameter[Tuple[float, float]]):
|
|
|
1102
1075
|
high = self._validate_single(new_value[1])
|
|
1103
1076
|
|
|
1104
1077
|
if low > high:
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
f"Low value {low} greater than high value {high}, swapping",
|
|
1110
|
-
)
|
|
1078
|
+
warn_parameter_update(
|
|
1079
|
+
self.name,
|
|
1080
|
+
type(self).__name__,
|
|
1081
|
+
f"Low value {low} greater than high value {high}, swapping",
|
|
1111
1082
|
)
|
|
1112
1083
|
low, high = high, low
|
|
1113
1084
|
|
|
1114
1085
|
if low < self.min:
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
f"Low value {low} below minimum {self.min}, clamping",
|
|
1120
|
-
)
|
|
1086
|
+
warn_parameter_update(
|
|
1087
|
+
self.name,
|
|
1088
|
+
type(self).__name__,
|
|
1089
|
+
f"Low value {low} below minimum {self.min}, clamping",
|
|
1121
1090
|
)
|
|
1122
1091
|
low = self.min
|
|
1123
1092
|
if high > self.max:
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
f"High value {high} above maximum {self.max}, clamping",
|
|
1129
|
-
)
|
|
1093
|
+
warn_parameter_update(
|
|
1094
|
+
self.name,
|
|
1095
|
+
type(self).__name__,
|
|
1096
|
+
f"High value {high} above maximum {self.max}, clamping",
|
|
1130
1097
|
)
|
|
1131
1098
|
high = self.max
|
|
1132
1099
|
|
|
@@ -1149,12 +1116,10 @@ class FloatRangeParameter(Parameter[Tuple[float, float]]):
|
|
|
1149
1116
|
"FloatRangeParameter must have both min and max bounds",
|
|
1150
1117
|
)
|
|
1151
1118
|
if self.min > self.max:
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
f"Min value greater than max value, swapping",
|
|
1157
|
-
)
|
|
1119
|
+
warn_parameter_update(
|
|
1120
|
+
self.name,
|
|
1121
|
+
type(self).__name__,
|
|
1122
|
+
f"Min value greater than max value, swapping",
|
|
1158
1123
|
)
|
|
1159
1124
|
self.min, self.max = self.max, self.min
|
|
1160
1125
|
self.value = self._validate(self.value)
|
syd/support.py
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
from abc import ABCMeta
|
|
2
2
|
from typing import Any, List
|
|
3
|
+
from warnings import warn
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@contextmanager
|
|
9
|
+
def plot_context():
|
|
10
|
+
plt.ioff()
|
|
11
|
+
try:
|
|
12
|
+
yield
|
|
13
|
+
finally:
|
|
14
|
+
plt.ion()
|
|
3
15
|
|
|
4
16
|
|
|
5
17
|
class NoUpdate:
|
|
@@ -20,6 +32,9 @@ class NoUpdate:
|
|
|
20
32
|
and other._noupdate_identifier == self._noupdate_identifier
|
|
21
33
|
)
|
|
22
34
|
|
|
35
|
+
def __repr__(self):
|
|
36
|
+
return "NotUpdated"
|
|
37
|
+
|
|
23
38
|
|
|
24
39
|
class NoInitialValue:
|
|
25
40
|
"""Singleton class to represent a non-initial value in parameter operations."""
|
|
@@ -39,6 +54,9 @@ class NoInitialValue:
|
|
|
39
54
|
and other._noinitialvalue_identifier == self._noinitialvalue_identifier
|
|
40
55
|
)
|
|
41
56
|
|
|
57
|
+
def __repr__(self):
|
|
58
|
+
return "NotInitialized"
|
|
59
|
+
|
|
42
60
|
|
|
43
61
|
# Keep original Parameter class and exceptions unchanged
|
|
44
62
|
class ParameterAddError(Exception):
|
|
@@ -110,6 +128,15 @@ class ParameterUpdateWarning(Warning):
|
|
|
110
128
|
)
|
|
111
129
|
|
|
112
130
|
|
|
131
|
+
def warn_parameter_update(
|
|
132
|
+
parameter_name: str, parameter_type: str, message: str = None
|
|
133
|
+
):
|
|
134
|
+
"""
|
|
135
|
+
Warn the user that a parameter has been updated to a value behind the scenes.
|
|
136
|
+
"""
|
|
137
|
+
warn(ParameterUpdateWarning(parameter_name, parameter_type, message))
|
|
138
|
+
|
|
139
|
+
|
|
113
140
|
def get_parameter_attributes(param_class) -> List[str]:
|
|
114
141
|
"""
|
|
115
142
|
Get all valid attributes for a parameter class.
|
syd/viewer.py
CHANGED
|
@@ -7,7 +7,6 @@ from matplotlib.figure import Figure
|
|
|
7
7
|
from .parameters import ParameterType, ActionType, Parameter
|
|
8
8
|
from .support import NoUpdate, NoInitialValue, ParameterAddError, ParameterUpdateError
|
|
9
9
|
|
|
10
|
-
|
|
11
10
|
# Create the singleton instances
|
|
12
11
|
NO_UPDATE = NoUpdate()
|
|
13
12
|
NO_INITIAL_VALUE = NoInitialValue()
|
|
@@ -217,24 +216,29 @@ class Viewer:
|
|
|
217
216
|
"""Deploy the app in a notebook or standalone environment"""
|
|
218
217
|
env = env.lower()
|
|
219
218
|
if env == "notebook":
|
|
220
|
-
|
|
219
|
+
# On demand import because the deployers need to import the viewer
|
|
220
|
+
from .notebook_deployment.deployer import NotebookDeployer
|
|
221
221
|
|
|
222
222
|
deployer = NotebookDeployer(self, **kwargs)
|
|
223
223
|
deployer.deploy()
|
|
224
224
|
return self
|
|
225
225
|
|
|
226
226
|
elif env == "browser" or env == "flask":
|
|
227
|
-
|
|
227
|
+
# On demand import because the deployers need to import the viewer
|
|
228
|
+
from .flask_deployment.deployer import FlaskDeployer
|
|
228
229
|
|
|
229
|
-
# Ensure port is None by default if not specified
|
|
230
230
|
if "port" not in kwargs:
|
|
231
231
|
kwargs["port"] = None
|
|
232
|
+
if "continuous" in kwargs:
|
|
233
|
+
kwargs.pop("continuous")
|
|
232
234
|
|
|
233
|
-
deployer =
|
|
235
|
+
deployer = FlaskDeployer(self, **kwargs)
|
|
236
|
+
deployer.deploy()
|
|
234
237
|
return self
|
|
238
|
+
|
|
235
239
|
else:
|
|
236
240
|
raise ValueError(
|
|
237
|
-
f"Unsupported environment: {env}, only 'notebook', '
|
|
241
|
+
f"Unsupported environment: {env}, only 'notebook', 'flask'/'browser' are supported right now."
|
|
238
242
|
)
|
|
239
243
|
|
|
240
244
|
@contextmanager
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: syd
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: A Python package for making GUIs for data science easy.
|
|
5
|
+
Project-URL: Homepage, https://github.com/landoskape/syd
|
|
6
|
+
Author-email: Andrew Landau <andrew+tyler+landau+getridofthisanddtheplusses@gmail.com>
|
|
7
|
+
License-Expression: GPL-3.0-or-later
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: data-science,gui,interactive,jupyter,machine-learning,notebook,python
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Requires-Dist: flask
|
|
22
|
+
Requires-Dist: ipykernel
|
|
23
|
+
Requires-Dist: ipympl
|
|
24
|
+
Requires-Dist: ipywidgets
|
|
25
|
+
Requires-Dist: matplotlib
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'test'
|
|
28
|
+
Requires-Dist: pytest>=7.0.0; extra == 'test'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# Syd
|
|
32
|
+
|
|
33
|
+
[](https://badge.fury.io/py/syd)
|
|
34
|
+
[](https://github.com/landoskape/syd/actions/workflows/tests.yml)
|
|
35
|
+
[](https://shareyourdata.readthedocs.io/en/stable/?badge=stable)
|
|
36
|
+
[](https://codecov.io/gh/landoskape/syd)
|
|
37
|
+
[](https://github.com/psf/black)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
<div>
|
|
41
|
+
<img src="./docs/assets/syd-logo-white.png" alt="Syd" width="350" align="right"/>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
A package to help you share your data!
|
|
45
|
+
|
|
46
|
+
Have you ever wanted to look through all your data really quickly interactively? Of course you have. Mo data mo problems, but only if you don't know what to do with it. And that's why Syd stands for show your data!
|
|
47
|
+
|
|
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
|
+
|
|
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
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
It's easy, just use pip install. The dependencies are light so it should work in most environments.
|
|
54
|
+
```bash
|
|
55
|
+
pip install syd
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Documentation
|
|
59
|
+
The full documentation is available at [shareyourdata.readthedocs.io](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
|
+
|
|
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
|
+
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
|
+
|
|
68
|
+
```python
|
|
69
|
+
import numpy as np
|
|
70
|
+
import matplotlib.pyplot as plt
|
|
71
|
+
from syd import make_viewer
|
|
72
|
+
def plot(state):
|
|
73
|
+
# Here's a simple plot function that plots a sine wave
|
|
74
|
+
fig = plt.figure()
|
|
75
|
+
t = np.linspace(0, 2 * np.pi, 1000)
|
|
76
|
+
ax = plt.gca()
|
|
77
|
+
ax.plot(t, state["amplitude"] * np.sin(state["frequency"] * t), color=state["color"])
|
|
78
|
+
return fig
|
|
79
|
+
|
|
80
|
+
viewer = make_viewer(plot)
|
|
81
|
+
viewer.add_float("amplitude", value=1.0, min=0.1, max=2.0)
|
|
82
|
+
viewer.add_float("frequency", value=1.0, min=0.1, max=5.0)
|
|
83
|
+
viewer.add_selection("color", value="red", options=["red", "blue", "green", "black"])
|
|
84
|
+
|
|
85
|
+
# env = "browser" # for viewing in a web browser (accessible via an IP address)
|
|
86
|
+
env = "notebook" # for viewing within a jupyter notebook
|
|
87
|
+
viewer = viewer.deploy(env=env)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+

|
|
91
|
+
### More Examples
|
|
92
|
+
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
|
+
|
|
94
|
+
| Example | Description | Try It! |
|
|
95
|
+
|---------|-------------|---------------|
|
|
96
|
+
| [Basic Tutorial](examples/1-simple_example.ipynb) | A good starting point with detailed explanations of how to use the core elements of Syd. | [](https://colab.research.google.com/github/landoskape/syd/blob/main/examples/1-simple_example.ipynb) |
|
|
97
|
+
| [Comprehensive](examples/2a-complex_example.ipynb) | Showcases just about everything you can do with Syd. | [](https://colab.research.google.com/github/landoskape/syd/blob/main/examples/2a-complex_example.ipynb) |
|
|
98
|
+
| [Making a Viewer Class](examples/2b-subclass_example.ipynb) | Rewrites the comprehensive example as a class, which is useful when you have complex data processing or callbacks. | [](https://colab.research.google.com/github/landoskape/syd/blob/main/examples/2b-subclass_example.ipynb) |
|
|
99
|
+
| [Data Loading](examples/3-data_loading.ipynb) | Showcases different ways to get your data into a Syd viewer. | [](https://colab.research.google.com/github/landoskape/syd/blob/main/examples/3-data_loading.ipynb) |
|
|
100
|
+
| [Hierarchical Callbacks](examples/4-hierarchical_callbacks.ipynb) | Demonstrates how to handle complicated callback situations. | [](https://colab.research.google.com/github/landoskape/syd/blob/main/examples/4-hierarchical_callbacks.ipynb) |
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
### 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:
|
|
105
|
+
```python
|
|
106
|
+
import numpy as np
|
|
107
|
+
from matplotlib import pyplot as plt
|
|
108
|
+
from syd import make_viewer
|
|
109
|
+
|
|
110
|
+
# Suppose you computed some data somewhere (in a script or in a jupyter notebook)
|
|
111
|
+
data = np.random.randn(100, 1000)
|
|
112
|
+
|
|
113
|
+
# When you write a plot function like this, it'll be able to access the data variable
|
|
114
|
+
def plot(state):
|
|
115
|
+
fig = plt.figure()
|
|
116
|
+
ax = fig.add_subplot(111)
|
|
117
|
+
|
|
118
|
+
# plot indexes to the data that you created outside the plot function
|
|
119
|
+
ax.imshow(data[state["index"]])
|
|
120
|
+
return fig
|
|
121
|
+
|
|
122
|
+
# Since plot "knows" about the data variable, all you need to do is pass the plot
|
|
123
|
+
# function to the syd viewer and it'll be able to access the data once deployed!
|
|
124
|
+
viewer = make_viewer(plot)
|
|
125
|
+
viewer.deploy(env="browser")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 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, to try it yourself, check out the full example [here](examples/4-hierarchical_callbacks.ipynb).
|
|
130
|
+
|
|
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 choose the mouse, then choose the session, and then view a particular neuron from within that session. But the viewer will break if you try to index to session 5 for mouse 2 but mouse 2 only has 4 sessions!
|
|
132
|
+
|
|
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:
|
|
134
|
+
```python
|
|
135
|
+
import numpy as np
|
|
136
|
+
from syd import Viewer # Much easier to build a Viewer class for hierarchical callbacks
|
|
137
|
+
|
|
138
|
+
class MouseViewer(Viewer):
|
|
139
|
+
def __init__(self, mice_names):
|
|
140
|
+
self.mice_names = mice_names
|
|
141
|
+
|
|
142
|
+
self.add_selection("mouse", options=list(mice_names))
|
|
143
|
+
|
|
144
|
+
# We don't know how many sessions or neurons to pick from yet!
|
|
145
|
+
self.add_integer("session", min=0, max=1)
|
|
146
|
+
self.add_integer("neuron", min=0, max=1)
|
|
147
|
+
|
|
148
|
+
# Any time the mouse changes, update the sessions to pick from
|
|
149
|
+
self.on_change("mouse", self.update_mouse)
|
|
150
|
+
|
|
151
|
+
# Any time the session changes, update the neurons to pick from
|
|
152
|
+
self.on_change("session", self.update_session)
|
|
153
|
+
|
|
154
|
+
# Since we built callbacks for setting the range of the session
|
|
155
|
+
# and neuron parameters, we can use them here!
|
|
156
|
+
# To get the state, we can use self.state, which is the current
|
|
157
|
+
# state of the viewer (in the init function, it'll just be the
|
|
158
|
+
# default value for each parameter you've added already).
|
|
159
|
+
self.update_mouse(self.state)
|
|
160
|
+
|
|
161
|
+
def update_mouse(self, state):
|
|
162
|
+
# Pseudo code for getting the number of sessions for a given mouse
|
|
163
|
+
num_sessions = get_num_sessions(state["mouse"])
|
|
164
|
+
|
|
165
|
+
# Now we update the number of sessions to pick from
|
|
166
|
+
self.update_integer("session", max=num_sessions - 1)
|
|
167
|
+
|
|
168
|
+
# Now we need to update the neurons to choose from ....
|
|
169
|
+
# But! Updating the session parameter might trigger a change to the
|
|
170
|
+
# session value. So, instead of using the state dictionary that was
|
|
171
|
+
# passed into the function, we can get the ~NEW~ state dictionary like this:
|
|
172
|
+
new_state = self.state
|
|
173
|
+
|
|
174
|
+
# Then perform the session update callback!
|
|
175
|
+
self.update_session(new_state)
|
|
176
|
+
|
|
177
|
+
def update_session(self, state):
|
|
178
|
+
# Pseudo code for getting the number of neurons for a given mouse and session
|
|
179
|
+
num_neurons = get_num_neurons(state["mouse"], state["session"])
|
|
180
|
+
|
|
181
|
+
# Now we update the number of neurons to pick from
|
|
182
|
+
self.update_integer("neuron", max=num_neurons - 1)
|
|
183
|
+
|
|
184
|
+
def plot(self, state):
|
|
185
|
+
# Pseudo code for plotting the data
|
|
186
|
+
data = get_data(state["mouse"], state["session"], state["neuron"])
|
|
187
|
+
fig = plot_the_data(data)
|
|
188
|
+
return fig
|
|
189
|
+
|
|
190
|
+
# Now we can create a viewer and deploy it
|
|
191
|
+
viewer = MouseViewer(["Mouse 1", "Mouse 2", "Mouse 3"])
|
|
192
|
+
viewer.deploy(env="browser")
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
|
|
198
|
+
|
|
199
|
+
## Contributing
|
|
200
|
+
|
|
201
|
+
Contributions are welcome! Here's how you can help:
|
|
202
|
+
|
|
203
|
+
1. Fork the repository
|
|
204
|
+
2. Create a new branch (`git checkout -b feature/amazing-feature`)
|
|
205
|
+
3. Make your changes
|
|
206
|
+
4. Run the tests (`pytest`)
|
|
207
|
+
5. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
208
|
+
6. Push to the branch (`git push origin feature/amazing-feature`)
|
|
209
|
+
7. Open a Pull Request online
|
|
210
|
+
|
|
211
|
+
Please make sure to update tests as appropriate and adhere to the existing coding style (black, line-length=88, other style guidelines not capture by black, generally following pep8 guidelines). Try to make the code coverage report improve or stay the same rather than decrease (right now the deployment system isn't covered by tests). I don't have any precommit hooks or anything so you're responsible for checking this yourself. You can process the code with black as follows:
|
|
212
|
+
```bash
|
|
213
|
+
pip install black
|
|
214
|
+
black . # from the root directory of the repo
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## To-Do List
|
|
218
|
+
- Layout controls
|
|
219
|
+
- [ ] Improve the display and make it look better
|
|
220
|
+
- [ ] Add a "save" button that saves the current state of the viewer to a json file
|
|
221
|
+
- [ ] Add a "load" button that loads the viewer state from a json file
|
|
222
|
+
- [ ] Add a "freeze" button that allows the user to update state variables without updating the plot until unfreezing
|
|
223
|
+
- [ ] Add a window for capturing any error messages that might be thrown by the plot function. Maybe we could have a little interface for looking at each one (up to a point) and the user could press a button to throw an error for the traceback.
|
|
224
|
+
- [ ] Consider "app_deployed" context for each deployer...
|
|
225
|
+
- Export options:
|
|
226
|
+
- [ ] 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
|
+
- [ ] Export full: export the viewer in a way that contains the data to give full functionality.
|
|
228
|
+
- [ ] Keep deploy() for backwards compatibility, but deprecate it in favor of show() and share() (for notebook and browser, respectively)
|
|
@@ -0,0 +1,19 @@
|
|
|
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,,
|