phy 2.1.0rc1__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.
Files changed (189) hide show
  1. phy/__init__.py +53 -0
  2. phy/apps/__init__.py +260 -0
  3. phy/apps/base.py +1887 -0
  4. phy/apps/kwik/__init__.py +10 -0
  5. phy/apps/kwik/gui.py +276 -0
  6. phy/apps/kwik/static/state.json +90 -0
  7. phy/apps/kwik/tests/__init__.py +0 -0
  8. phy/apps/kwik/tests/test_gui.py +103 -0
  9. phy/apps/template/__init__.py +9 -0
  10. phy/apps/template/gui.py +226 -0
  11. phy/apps/template/static/state.json +95 -0
  12. phy/apps/template/tests/__init__.py +0 -0
  13. phy/apps/template/tests/test_gui.py +223 -0
  14. phy/apps/tests/__init__.py +0 -0
  15. phy/apps/tests/test_base.py +576 -0
  16. phy/apps/trace/__init__.py +8 -0
  17. phy/apps/trace/gui.py +101 -0
  18. phy/apps/trace/tests/__init__.py +0 -0
  19. phy/apps/trace/tests/test_gui.py +26 -0
  20. phy/cluster/__init__.py +8 -0
  21. phy/cluster/_history.py +174 -0
  22. phy/cluster/_utils.py +362 -0
  23. phy/cluster/clustering.py +517 -0
  24. phy/cluster/supervisor.py +1273 -0
  25. phy/cluster/tests/__init__.py +0 -0
  26. phy/cluster/tests/conftest.py +44 -0
  27. phy/cluster/tests/test_clustering.py +502 -0
  28. phy/cluster/tests/test_history.py +142 -0
  29. phy/cluster/tests/test_supervisor.py +772 -0
  30. phy/cluster/tests/test_utils.py +217 -0
  31. phy/cluster/views/__init__.py +19 -0
  32. phy/cluster/views/amplitude.py +311 -0
  33. phy/cluster/views/base.py +703 -0
  34. phy/cluster/views/cluscatter.py +392 -0
  35. phy/cluster/views/correlogram.py +315 -0
  36. phy/cluster/views/feature.py +526 -0
  37. phy/cluster/views/histogram.py +352 -0
  38. phy/cluster/views/probe.py +185 -0
  39. phy/cluster/views/raster.py +218 -0
  40. phy/cluster/views/scatter.py +123 -0
  41. phy/cluster/views/template.py +287 -0
  42. phy/cluster/views/tests/__init__.py +7 -0
  43. phy/cluster/views/tests/conftest.py +27 -0
  44. phy/cluster/views/tests/test_amplitude.py +127 -0
  45. phy/cluster/views/tests/test_base.py +74 -0
  46. phy/cluster/views/tests/test_cluscatter.py +108 -0
  47. phy/cluster/views/tests/test_correlogram.py +56 -0
  48. phy/cluster/views/tests/test_feature.py +107 -0
  49. phy/cluster/views/tests/test_histogram.py +70 -0
  50. phy/cluster/views/tests/test_probe.py +50 -0
  51. phy/cluster/views/tests/test_raster.py +119 -0
  52. phy/cluster/views/tests/test_scatter.py +119 -0
  53. phy/cluster/views/tests/test_template.py +118 -0
  54. phy/cluster/views/tests/test_trace.py +320 -0
  55. phy/cluster/views/tests/test_waveform.py +108 -0
  56. phy/cluster/views/trace.py +873 -0
  57. phy/cluster/views/waveform.py +544 -0
  58. phy/conftest.py +95 -0
  59. phy/gui/__init__.py +22 -0
  60. phy/gui/actions.py +722 -0
  61. phy/gui/gui.py +917 -0
  62. phy/gui/qt.py +700 -0
  63. phy/gui/state.py +248 -0
  64. phy/gui/static/fa-solid-900.ttf +0 -0
  65. phy/gui/static/icons/f015.png +0 -0
  66. phy/gui/static/icons/f01e.png +0 -0
  67. phy/gui/static/icons/f060.png +0 -0
  68. phy/gui/static/icons/f061.png +0 -0
  69. phy/gui/static/icons/f0a8.png +0 -0
  70. phy/gui/static/icons/f0a9.png +0 -0
  71. phy/gui/static/icons/f0c7.png +0 -0
  72. phy/gui/static/icons/f0e2.png +0 -0
  73. phy/gui/static/icons/f247.png +0 -0
  74. phy/gui/static/index.html +17 -0
  75. phy/gui/static/list.min.js +2 -0
  76. phy/gui/static/styles.css +134 -0
  77. phy/gui/static/table.min.js +50 -0
  78. phy/gui/tests/__init__.py +3 -0
  79. phy/gui/tests/conftest.py +36 -0
  80. phy/gui/tests/test_actions.py +409 -0
  81. phy/gui/tests/test_gui.py +315 -0
  82. phy/gui/tests/test_qt.py +201 -0
  83. phy/gui/tests/test_state.py +91 -0
  84. phy/gui/tests/test_widgets.py +411 -0
  85. phy/gui/widgets.py +1151 -0
  86. phy/plot/__init__.py +33 -0
  87. phy/plot/axes.py +258 -0
  88. phy/plot/base.py +939 -0
  89. phy/plot/gloo/__init__.py +27 -0
  90. phy/plot/gloo/array.py +84 -0
  91. phy/plot/gloo/atlas.py +171 -0
  92. phy/plot/gloo/buffer.py +105 -0
  93. phy/plot/gloo/framebuffer.py +404 -0
  94. phy/plot/gloo/gl.py +110 -0
  95. phy/plot/gloo/globject.py +141 -0
  96. phy/plot/gloo/gpudata.py +152 -0
  97. phy/plot/gloo/parser.py +238 -0
  98. phy/plot/gloo/program.py +632 -0
  99. phy/plot/gloo/shader.py +440 -0
  100. phy/plot/gloo/snippet.py +575 -0
  101. phy/plot/gloo/texture.py +489 -0
  102. phy/plot/gloo/uniforms.py +247 -0
  103. phy/plot/gloo/variable.py +453 -0
  104. phy/plot/glsl/constants.glsl +48 -0
  105. phy/plot/glsl/histogram.frag +5 -0
  106. phy/plot/glsl/histogram.vert +17 -0
  107. phy/plot/glsl/image.frag +6 -0
  108. phy/plot/glsl/image.vert +9 -0
  109. phy/plot/glsl/line.frag +5 -0
  110. phy/plot/glsl/line.vert +8 -0
  111. phy/plot/glsl/line_agg_geom.frag +88 -0
  112. phy/plot/glsl/line_agg_geom.vert +18 -0
  113. phy/plot/glsl/markers/arrow.glsl +12 -0
  114. phy/plot/glsl/markers/asterisk.glsl +16 -0
  115. phy/plot/glsl/markers/chevron.glsl +14 -0
  116. phy/plot/glsl/markers/clover.glsl +20 -0
  117. phy/plot/glsl/markers/club.glsl +31 -0
  118. phy/plot/glsl/markers/cross.glsl +17 -0
  119. phy/plot/glsl/markers/diamond.glsl +12 -0
  120. phy/plot/glsl/markers/disc.glsl +9 -0
  121. phy/plot/glsl/markers/ellipse.glsl +67 -0
  122. phy/plot/glsl/markers/hbar.glsl +9 -0
  123. phy/plot/glsl/markers/heart.glsl +15 -0
  124. phy/plot/glsl/markers/infinity.glsl +15 -0
  125. phy/plot/glsl/markers/markers.glsl +24 -0
  126. phy/plot/glsl/markers/pin.glsl +18 -0
  127. phy/plot/glsl/markers/ring.glsl +11 -0
  128. phy/plot/glsl/markers/spade.glsl +28 -0
  129. phy/plot/glsl/markers/square.glsl +10 -0
  130. phy/plot/glsl/markers/tag.glsl +11 -0
  131. phy/plot/glsl/markers/triangle.glsl +14 -0
  132. phy/plot/glsl/markers/vbar.glsl +15 -0
  133. phy/plot/glsl/msdf.frag +69 -0
  134. phy/plot/glsl/msdf.vert +60 -0
  135. phy/plot/glsl/patch.frag +6 -0
  136. phy/plot/glsl/patch.vert +13 -0
  137. phy/plot/glsl/plot.frag +14 -0
  138. phy/plot/glsl/plot.vert +22 -0
  139. phy/plot/glsl/plot_agg.frag +23 -0
  140. phy/plot/glsl/plot_agg.vert +109 -0
  141. phy/plot/glsl/polygon.frag +5 -0
  142. phy/plot/glsl/polygon.vert +7 -0
  143. phy/plot/glsl/scatter.frag +15 -0
  144. phy/plot/glsl/scatter.vert +19 -0
  145. phy/plot/glsl/simple.frag +5 -0
  146. phy/plot/glsl/simple.vert +6 -0
  147. phy/plot/glsl/text.frag +14 -0
  148. phy/plot/glsl/text.vert +58 -0
  149. phy/plot/glsl/uni_plot.frag +14 -0
  150. phy/plot/glsl/uni_plot.vert +19 -0
  151. phy/plot/glsl/uni_scatter.frag +40 -0
  152. phy/plot/glsl/uni_scatter.vert +20 -0
  153. phy/plot/glsl/utils.glsl +101 -0
  154. phy/plot/interact.py +549 -0
  155. phy/plot/panzoom.py +589 -0
  156. phy/plot/plot.py +447 -0
  157. phy/plot/static/SourceCodePro-Regular.npy.gz +0 -0
  158. phy/plot/tests/__init__.py +63 -0
  159. phy/plot/tests/conftest.py +30 -0
  160. phy/plot/tests/test_axes.py +37 -0
  161. phy/plot/tests/test_base.py +229 -0
  162. phy/plot/tests/test_interact.py +312 -0
  163. phy/plot/tests/test_panzoom.py +323 -0
  164. phy/plot/tests/test_plot.py +214 -0
  165. phy/plot/tests/test_transform.py +274 -0
  166. phy/plot/tests/test_utils.py +56 -0
  167. phy/plot/tests/test_visuals.py +474 -0
  168. phy/plot/transform.py +540 -0
  169. phy/plot/utils.py +243 -0
  170. phy/plot/visuals.py +1579 -0
  171. phy/utils/__init__.py +29 -0
  172. phy/utils/color.py +342 -0
  173. phy/utils/config.py +85 -0
  174. phy/utils/context.py +218 -0
  175. phy/utils/plugin.py +160 -0
  176. phy/utils/profiling.py +145 -0
  177. phy/utils/tests/__init__.py +0 -0
  178. phy/utils/tests/conftest.py +37 -0
  179. phy/utils/tests/test_color.py +159 -0
  180. phy/utils/tests/test_config.py +141 -0
  181. phy/utils/tests/test_context.py +153 -0
  182. phy/utils/tests/test_plugin.py +85 -0
  183. phy/utils/tests/test_profiling.py +28 -0
  184. phy-2.1.0rc1.dist-info/METADATA +217 -0
  185. phy-2.1.0rc1.dist-info/RECORD +189 -0
  186. phy-2.1.0rc1.dist-info/WHEEL +5 -0
  187. phy-2.1.0rc1.dist-info/entry_points.txt +2 -0
  188. phy-2.1.0rc1.dist-info/licenses/LICENSE.md +12 -0
  189. phy-2.1.0rc1.dist-info/top_level.txt +1 -0
phy/__init__.py ADDED
@@ -0,0 +1,53 @@
1
+ # flake8: noqa
2
+
3
+ """phy: interactive visualization and manual spike sorting of large-scale ephys data."""
4
+
5
+
6
+ # ------------------------------------------------------------------------------
7
+ # Imports
8
+ # ------------------------------------------------------------------------------
9
+
10
+ import atexit
11
+ import logging
12
+ import os.path as op
13
+ import sys
14
+
15
+ from io import StringIO
16
+
17
+ from phylib.utils import Bunch
18
+ from phylib.utils._misc import _git_version
19
+ from phylib.utils.event import connect, unconnect, emit
20
+ from .utils.config import load_master_config
21
+ from .utils.plugin import IPlugin, get_plugin, discover_plugins
22
+
23
+
24
+ # ------------------------------------------------------------------------------
25
+ # Global variables and functions
26
+ # ------------------------------------------------------------------------------
27
+
28
+ __author__ = 'Cyrille Rossant'
29
+ __email__ = 'cyrille.rossant at gmail.com'
30
+ __version__ = '2.1.0rc1'
31
+ __version_git__ = __version__ + _git_version()
32
+
33
+
34
+ # Set a null handler on the root logger
35
+ logger = logging.getLogger()
36
+ logger.setLevel(logging.DEBUG)
37
+ logger.addHandler(logging.NullHandler())
38
+ logger.propagate = False
39
+
40
+
41
+ @atexit.register
42
+ def on_exit(): # pragma: no cover
43
+ # Close the logging handlers.
44
+ for handler in logger.handlers:
45
+ handler.close()
46
+ logger.removeHandler(handler)
47
+
48
+
49
+ def test(): # pragma: no cover
50
+ """Run the full testing suite of phy."""
51
+ import pytest
52
+
53
+ pytest.main()
phy/apps/__init__.py ADDED
@@ -0,0 +1,260 @@
1
+ """CLI tool."""
2
+
3
+
4
+ # ------------------------------------------------------------------------------
5
+ # Imports
6
+ # ------------------------------------------------------------------------------
7
+
8
+ import logging
9
+ import sys
10
+ from contextlib import contextmanager
11
+ from pathlib import Path
12
+ from traceback import format_exception
13
+
14
+ import click
15
+ from phylib import _Formatter, _logger_date_fmt, _logger_fmt, add_default_handler # noqa # noqa
16
+
17
+ from phy import __version_git__
18
+ from phy.gui.qt import QtDialogLogger
19
+ from phy.utils.profiling import _enable_pdb, _enable_profiler
20
+
21
+ from .base import BaseController, FeatureMixin, TemplateMixin, TraceMixin, WaveformMixin # noqa
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ # ------------------------------------------------------------------------------
27
+ # CLI utils
28
+ # ------------------------------------------------------------------------------
29
+
30
+ DEBUG = False
31
+ if '--debug' in sys.argv: # pragma: no cover
32
+ DEBUG = True
33
+ sys.argv.remove('--debug')
34
+
35
+
36
+ if '--pdb' in sys.argv: # pragma: no cover
37
+ sys.argv.remove('--pdb')
38
+ _enable_pdb()
39
+
40
+
41
+ # Add `profile` in the builtins.
42
+ if '--lprof' in sys.argv or '--prof' in sys.argv: # pragma: no cover
43
+ _enable_profiler('--lprof' in sys.argv)
44
+ if '--prof' in sys.argv:
45
+ sys.argv.remove('--prof')
46
+ if '--lprof' in sys.argv:
47
+ sys.argv.remove('--lprof')
48
+
49
+
50
+ # ------------------------------------------------------------------------------
51
+ # Set up logging with the CLI tool
52
+ # ------------------------------------------------------------------------------
53
+
54
+
55
+ def exceptionHandler(exception_type, exception, traceback): # pragma: no cover
56
+ tb = ''.join(format_exception(exception_type, exception, traceback))
57
+ logger.error('An error has occurred (%s): %s\n%s', exception_type.__name__, exception, tb)
58
+
59
+
60
+ @contextmanager
61
+ def capture_exceptions(): # pragma: no cover
62
+ """Log exceptions instead of crashing the GUI, and display an error dialog on errors."""
63
+ logger.debug('Start capturing exceptions.')
64
+
65
+ # Add a custom exception hook.
66
+ excepthook = sys.excepthook
67
+ sys.excepthook = exceptionHandler
68
+
69
+ # Add a dialog exception handler.
70
+ handler = QtDialogLogger()
71
+ handler.setLevel(logging.ERROR)
72
+ logging.getLogger('phy').addHandler(handler)
73
+
74
+ yield
75
+
76
+ # Reset the original exception hook.
77
+ sys.excepthook = excepthook
78
+
79
+ # Remove the dialog exception handler.
80
+ logging.getLogger('phy').removeHandler(handler)
81
+
82
+ logger.debug('Stop capturing exceptions.')
83
+
84
+
85
+ # ------------------------------------------------------------------------------
86
+ # Root CLI tool
87
+ # ------------------------------------------------------------------------------
88
+
89
+
90
+ @click.group()
91
+ @click.version_option(version=__version_git__)
92
+ @click.help_option('-h', '--help')
93
+ @click.pass_context
94
+ def phycli(ctx):
95
+ """Interactive visualization and manual spike sorting of large-scale ephys data."""
96
+ add_default_handler(level='DEBUG' if DEBUG else 'INFO', logger=logging.getLogger('phy'))
97
+ add_default_handler(level='DEBUG' if DEBUG else 'INFO', logger=logging.getLogger('phylib'))
98
+ add_default_handler(level='DEBUG' if DEBUG else 'INFO', logger=logging.getLogger('mtscomp'))
99
+
100
+
101
+ # ------------------------------------------------------------------------------
102
+ # GUI command wrapper
103
+ # ------------------------------------------------------------------------------
104
+
105
+
106
+ def _gui_command(f):
107
+ """Command options for GUI commands."""
108
+ f = click.option(
109
+ '--clear-cache/--no-clear-cache',
110
+ default=False,
111
+ help='Clear the .phy cache in the data directory.',
112
+ )(f)
113
+ f = click.option(
114
+ '--clear-state/--no-clear-state',
115
+ default=False,
116
+ help='Clear the GUI state in `~/.phy/` and in `.phy`.',
117
+ )(f)
118
+ return f
119
+
120
+
121
+ # ------------------------------------------------------------------------------
122
+ # Raw data GUI
123
+ # ------------------------------------------------------------------------------
124
+
125
+
126
+ @phycli.command('trace-gui') # pragma: no cover
127
+ @click.argument('dat-path', type=click.Path(exists=True))
128
+ @click.option('-s', '--sample-rate', type=float)
129
+ @click.option('-d', '--dtype', type=str)
130
+ @click.option('-n', '--n-channels', type=int)
131
+ @click.option('-h', '--offset', type=int)
132
+ @click.option('-f', '--fortran', type=bool, is_flag=True)
133
+ @_gui_command
134
+ @click.pass_context
135
+ def cli_trace_gui(ctx, dat_path, **kwargs):
136
+ """Launch the trace GUI on a raw data file."""
137
+ from .trace.gui import trace_gui
138
+
139
+ with capture_exceptions():
140
+ kwargs['n_channels_dat'] = kwargs.pop('n_channels')
141
+ kwargs['order'] = 'F' if kwargs.pop('fortran', None) else None
142
+ trace_gui(dat_path, **kwargs)
143
+
144
+
145
+ # ------------------------------------------------------------------------------
146
+ # Template GUI
147
+ # ------------------------------------------------------------------------------
148
+
149
+
150
+ @phycli.command('template-gui') # pragma: no cover
151
+ @click.argument('params-path', type=click.Path(exists=True))
152
+ @_gui_command
153
+ @click.pass_context
154
+ def cli_template_gui(ctx, params_path, **kwargs):
155
+ """Launch the template GUI on a params.py file."""
156
+ from .template.gui import template_gui
157
+
158
+ prof = __builtins__.get('profile', None)
159
+ with capture_exceptions():
160
+ if prof:
161
+ from phy.utils.profiling import _profile
162
+
163
+ return _profile(prof, 'template_gui(params_path)', globals(), locals())
164
+ template_gui(params_path, **kwargs)
165
+
166
+
167
+ @phycli.command('template-describe')
168
+ @click.argument('params-path', type=click.Path(exists=True))
169
+ @click.pass_context
170
+ def cli_template_describe(ctx, params_path):
171
+ """Describe a template file."""
172
+ from .template.gui import template_describe
173
+
174
+ template_describe(params_path)
175
+
176
+
177
+ # ------------------------------------------------------------------------------
178
+ # Kwik GUI
179
+ # ------------------------------------------------------------------------------
180
+
181
+
182
+ # Create the `phy cluster-manual file.kwik` command.
183
+ @phycli.command('kwik-gui') # pragma: no cover
184
+ @click.argument('path', type=click.Path(exists=True))
185
+ @click.option('--channel-group', type=int)
186
+ @click.option('--clustering', type=str)
187
+ @_gui_command
188
+ @click.pass_context
189
+ def cli_kwik_gui(ctx, path, channel_group=None, clustering=None, **kwargs):
190
+ """Launch the Kwik GUI on a Kwik file."""
191
+ from .kwik.gui import kwik_gui
192
+
193
+ with capture_exceptions():
194
+ assert path
195
+ kwik_gui(path, channel_group=channel_group, clustering=clustering, **kwargs)
196
+
197
+
198
+ @phycli.command('kwik-describe')
199
+ @click.argument('path', type=click.Path(exists=True))
200
+ @click.option('--channel-group', type=int, help='channel group')
201
+ @click.option('--clustering', type=str, help='clustering')
202
+ @click.pass_context
203
+ def cli_kwik_describe(ctx, path, channel_group=0, clustering='main'):
204
+ """Describe a Kwik file."""
205
+ from .kwik.gui import kwik_describe
206
+
207
+ assert path
208
+ kwik_describe(path, channel_group=channel_group, clustering=clustering)
209
+
210
+
211
+ # ------------------------------------------------------------------------------
212
+ # Conversion
213
+ # ------------------------------------------------------------------------------
214
+
215
+
216
+ @phycli.command('alf-convert')
217
+ @click.argument('subdirs', nargs=-1, type=click.Path(exists=True, file_okay=False, dir_okay=True))
218
+ @click.argument('out_dir', type=click.Path())
219
+ @click.pass_context
220
+ def cli_alf_convert(ctx, subdirs, out_dir):
221
+ """Convert an ephys dataset into ALF. If several directories are specified, it is assumed
222
+ that each directory contains the data for one probe of the same recording."""
223
+ from phylib.io.alf import EphysAlfCreator
224
+ from phylib.io.merge import Merger
225
+ from phylib.io.model import load_model
226
+
227
+ out_dir = Path(out_dir)
228
+
229
+ if len(subdirs) >= 2:
230
+ # Merge in the `merged` subdirectory inside the output directory.
231
+ m = Merger(subdirs, out_dir / '_tmp_merged')
232
+ model = m.merge()
233
+ else:
234
+ model = load_model(Path(subdirs[0]) / 'params.py')
235
+
236
+ c = EphysAlfCreator(model)
237
+ c.convert(out_dir)
238
+
239
+
240
+ # ------------------------------------------------------------------------------
241
+ # Waveform extraction
242
+ # ------------------------------------------------------------------------------
243
+
244
+
245
+ @phycli.command('extract-waveforms')
246
+ @click.argument('params-path', type=click.Path(exists=True))
247
+ @click.argument('n_spikes_per_cluster', type=int, default=500)
248
+ @click.option('--nc', type=int, default=16)
249
+ @click.pass_context
250
+ def template_extract_waveforms(
251
+ ctx, params_path, n_spikes_per_cluster, nc=None
252
+ ): # pragma: no cover
253
+ """Extract spike waveforms."""
254
+ from phylib.io.model import load_model
255
+
256
+ model = load_model(params_path)
257
+ model.save_spikes_subset_waveforms(
258
+ max_n_spikes_per_template=n_spikes_per_cluster, max_n_channels=nc
259
+ )
260
+ model.close()