mesofield 0.3.2b0__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 (111) hide show
  1. docs/_static/custom.css +40 -0
  2. docs/_static/favicon.png +0 -0
  3. docs/_static/logo.png +0 -0
  4. docs/api/index.md +70 -0
  5. docs/conf.py +200 -0
  6. docs/developer_guide.md +303 -0
  7. docs/index.md +25 -0
  8. docs/tutorial.md +4 -0
  9. docs/user_guide.md +172 -0
  10. examples/teensy_pulse_generator.py +320 -0
  11. experiments/pipeline_demo/experiment.json +24 -0
  12. experiments/pipeline_demo/hardware.yaml +23 -0
  13. experiments/pipeline_demo/procedure.py +50 -0
  14. experiments/two_cam_demo/experiment.json +24 -0
  15. experiments/two_cam_demo/hardware.yaml +58 -0
  16. experiments/two_cam_demo/load_dataset.py +213 -0
  17. experiments/two_cam_demo/procedure.py +87 -0
  18. external/video-codecs/openh264-1.8.0-win64.dll +0 -0
  19. mesofield/__init__.py +45 -0
  20. mesofield/__main__.py +11 -0
  21. mesofield/_version.py +24 -0
  22. mesofield/base.py +750 -0
  23. mesofield/cli/__init__.py +57 -0
  24. mesofield/cli/_richhelp.py +100 -0
  25. mesofield/cli/acquire.py +254 -0
  26. mesofield/cli/datakit.py +165 -0
  27. mesofield/cli/process.py +376 -0
  28. mesofield/cli/rig.py +108 -0
  29. mesofield/cli/tools.py +347 -0
  30. mesofield/config.py +751 -0
  31. mesofield/data/__init__.py +23 -0
  32. mesofield/data/batch.py +633 -0
  33. mesofield/data/manager.py +388 -0
  34. mesofield/data/writer.py +289 -0
  35. mesofield/datakit/__init__.py +44 -0
  36. mesofield/datakit/__main__.py +35 -0
  37. mesofield/datakit/_utils/_logger.py +5 -0
  38. mesofield/datakit/_version.py +141 -0
  39. mesofield/datakit/config.py +50 -0
  40. mesofield/datakit/core.py +783 -0
  41. mesofield/datakit/datamodel.py +200 -0
  42. mesofield/datakit/discover.py +124 -0
  43. mesofield/datakit/explore.py +651 -0
  44. mesofield/datakit/notebooks/pupil_dlc.ipynb +2445 -0
  45. mesofield/datakit/profile.py +535 -0
  46. mesofield/datakit/shell.py +83 -0
  47. mesofield/datakit/sources/__init__.py +65 -0
  48. mesofield/datakit/sources/analysis/mesomap.py +194 -0
  49. mesofield/datakit/sources/analysis/mesoscope.py +77 -0
  50. mesofield/datakit/sources/analysis/pupil.py +246 -0
  51. mesofield/datakit/sources/behavior/__init__.py +0 -0
  52. mesofield/datakit/sources/behavior/dataqueue.py +281 -0
  53. mesofield/datakit/sources/behavior/psychopy.py +364 -0
  54. mesofield/datakit/sources/behavior/treadmill.py +323 -0
  55. mesofield/datakit/sources/behavior/wheel.py +277 -0
  56. mesofield/datakit/sources/camera/mesoscope.py +32 -0
  57. mesofield/datakit/sources/camera/metadata_json.py +130 -0
  58. mesofield/datakit/sources/camera/pupil.py +28 -0
  59. mesofield/datakit/sources/camera/suite2p.py +547 -0
  60. mesofield/datakit/sources/register.py +204 -0
  61. mesofield/datakit/sources/session/config.py +130 -0
  62. mesofield/datakit/sources/session/notes.py +63 -0
  63. mesofield/datakit/sources/session/timestamps.py +58 -0
  64. mesofield/datakit/timeline.py +306 -0
  65. mesofield/devices/__init__.py +42 -0
  66. mesofield/devices/base.py +498 -0
  67. mesofield/devices/base_camera.py +295 -0
  68. mesofield/devices/cameras.py +740 -0
  69. mesofield/devices/daq.py +151 -0
  70. mesofield/devices/encoder.py +384 -0
  71. mesofield/devices/mocks.py +275 -0
  72. mesofield/devices/psychopy_device.py +455 -0
  73. mesofield/devices/subprocesses/__init__.py +0 -0
  74. mesofield/devices/subprocesses/psychopy.py +133 -0
  75. mesofield/devices/treadmill.py +318 -0
  76. mesofield/engines.py +380 -0
  77. mesofield/gui/Mesofield_icon.png +0 -0
  78. mesofield/gui/__init__.py +76 -0
  79. mesofield/gui/config_wizard.py +724 -0
  80. mesofield/gui/controller.py +535 -0
  81. mesofield/gui/dynamic_controller.py +78 -0
  82. mesofield/gui/maingui.py +427 -0
  83. mesofield/gui/mdagui.py +285 -0
  84. mesofield/gui/qt_device_adapter.py +109 -0
  85. mesofield/gui/speedplotter.py +152 -0
  86. mesofield/gui/theme.py +445 -0
  87. mesofield/gui/tiff_viewer.py +1050 -0
  88. mesofield/gui/viewer.py +691 -0
  89. mesofield/hardware.py +549 -0
  90. mesofield/playback.py +1298 -0
  91. mesofield/processing/__init__.py +12 -0
  92. mesofield/processing/runner.py +237 -0
  93. mesofield/processors/__init__.py +13 -0
  94. mesofield/processors/base.py +287 -0
  95. mesofield/processors/frame_mean.py +19 -0
  96. mesofield/protocols.py +378 -0
  97. mesofield/scaffold/__init__.py +34 -0
  98. mesofield/scaffold/experiment.py +400 -0
  99. mesofield/scaffold/rigs.py +121 -0
  100. mesofield/signals.py +85 -0
  101. mesofield/utils/__init__.py +0 -0
  102. mesofield/utils/_logger.py +156 -0
  103. mesofield/utils/retrofit.py +309 -0
  104. mesofield/utils/utils.py +217 -0
  105. mesofield-0.3.2b0.dist-info/METADATA +178 -0
  106. mesofield-0.3.2b0.dist-info/RECORD +111 -0
  107. mesofield-0.3.2b0.dist-info/WHEEL +5 -0
  108. mesofield-0.3.2b0.dist-info/entry_points.txt +2 -0
  109. mesofield-0.3.2b0.dist-info/licenses/LICENSE +21 -0
  110. mesofield-0.3.2b0.dist-info/top_level.txt +6 -0
  111. scripts/bench_frame_processor.py +103 -0
@@ -0,0 +1,44 @@
1
+ """Datakit package entry point."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+ if os.getenv("DATAKIT_SAFE_MODE") == "1":
8
+ for var in ("OMP_NUM_THREADS", "MKL_NUM_THREADS", "OPENBLAS_NUM_THREADS",
9
+ "NUMEXPR_NUM_THREADS", "VECLIB_MAXIMUM_THREADS"):
10
+ os.environ.setdefault(var, "1")
11
+
12
+ from ._utils._logger import get_logger
13
+
14
+ logger = get_logger("datakit")
15
+
16
+ from ._version import __version__, build_meta, get_version # noqa: E402
17
+ from .config import settings # noqa: E402
18
+ from .core import Dataset, inspect_sources, load, load_dataset, load_path # noqa: E402
19
+ from .datamodel import LoadedStream # noqa: E402
20
+ from .explore import explore # noqa: E402
21
+ from .profile import MaterializedMemoryReport, profile_materialized # noqa: E402
22
+ from .shell import open_shell # noqa: E402
23
+ from .sources.register import DataSource, LoadContext # noqa: E402
24
+
25
+ __all__ = [
26
+ "Dataset",
27
+ "DataSource",
28
+ "LoadContext",
29
+ "LoadedStream",
30
+ "MaterializedMemoryReport",
31
+ "__version__",
32
+ "build_meta",
33
+ "explore",
34
+ "get_logger",
35
+ "get_version",
36
+ "inspect_sources",
37
+ "load",
38
+ "load_dataset",
39
+ "load_path",
40
+ "logger",
41
+ "open_shell",
42
+ "profile_materialized",
43
+ "settings",
44
+ ]
@@ -0,0 +1,35 @@
1
+ """``python -m mesofield.datakit`` — open an interactive datakit shell.
2
+
3
+ Thin wrapper around :func:`mesofield.datakit.shell.open_shell`; the same
4
+ behaviour is exposed as ``mesofield datakit shell``.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ from pathlib import Path
11
+
12
+ from .shell import open_shell
13
+
14
+
15
+ def main() -> int:
16
+ parser = argparse.ArgumentParser(prog="python -m mesofield.datakit")
17
+ parser.add_argument(
18
+ "target",
19
+ type=Path,
20
+ nargs="?",
21
+ default=None,
22
+ help="Experiment directory, or a materialized .pkl/.h5 dataset file. "
23
+ "Omit to open a bare datakit shell.",
24
+ )
25
+ parser.add_argument(
26
+ "--hdf-key",
27
+ default="dataset",
28
+ help="HDF5 key to read when target is an .h5/.hdf5 file (default: dataset).",
29
+ )
30
+ args = parser.parse_args()
31
+ return open_shell(args.target, hdf_key=args.hdf_key)
32
+
33
+
34
+ if __name__ == "__main__":
35
+ raise SystemExit(main())
@@ -0,0 +1,5 @@
1
+ """Datakit logging via the mesofield primary logger."""
2
+
3
+ from mesofield.utils._logger import get_logger, install_excepthook, log_this_fr, setup_logging
4
+
5
+ __all__ = ["get_logger", "install_excepthook", "log_this_fr", "setup_logging"]
@@ -0,0 +1,141 @@
1
+ """Version + provenance metadata for ``datakit``.
2
+
3
+ This module resolves the running package's version from git tags when the
4
+ package is being executed from a working tree, and falls back to installed
5
+ package metadata or the on-disk ``VERSION`` file otherwise. The
6
+ ``build_meta()`` helper produces a small dictionary that is embedded into
7
+ materialized datasets so that pickled artefacts can be traced back to the
8
+ exact source revision that produced them.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import datetime as _dt
14
+ import platform as _platform
15
+ import re
16
+ import subprocess
17
+ import sys
18
+ from functools import lru_cache
19
+ from pathlib import Path
20
+ from typing import Any, Dict, Optional
21
+
22
+ _PACKAGE_NAME = "datakit"
23
+ _REPO_ROOT = Path(__file__).resolve().parent.parent
24
+ _VERSION_FILE = _REPO_ROOT / "VERSION"
25
+
26
+
27
+ def _run_git(*args: str) -> Optional[str]:
28
+ try:
29
+ out = subprocess.run(
30
+ ["git", "-C", str(_REPO_ROOT), *args],
31
+ capture_output=True,
32
+ text=True,
33
+ timeout=2.0,
34
+ check=False,
35
+ )
36
+ except (FileNotFoundError, subprocess.SubprocessError, OSError):
37
+ return None
38
+ if out.returncode != 0:
39
+ return None
40
+ value = out.stdout.strip()
41
+ return value or None
42
+
43
+
44
+ def _installed_version() -> Optional[str]:
45
+ try:
46
+ from importlib.metadata import PackageNotFoundError, version as _v
47
+ except ImportError: # pragma: no cover - py<3.8
48
+ return None
49
+ try:
50
+ return _v(_PACKAGE_NAME)
51
+ except PackageNotFoundError:
52
+ return None
53
+
54
+
55
+ def _file_version() -> Optional[str]:
56
+ try:
57
+ return _VERSION_FILE.read_text(encoding="utf-8").strip() or None
58
+ except OSError:
59
+ return None
60
+
61
+
62
+ def _normalise_remote_url(url: str) -> str:
63
+ """Convert a git remote URL to an https:// browsable form when possible."""
64
+
65
+ if url.startswith(("http://", "https://")):
66
+ return url[:-4] if url.endswith(".git") else url
67
+ # git@github.com:owner/repo.git -> https://github.com/owner/repo
68
+ m = re.match(r"^[^@]+@([^:]+):(.+?)(\.git)?$", url)
69
+ if m:
70
+ host, path, _ = m.groups()
71
+ return f"https://{host}/{path}"
72
+ return url
73
+
74
+
75
+ @lru_cache(maxsize=1)
76
+ def _git_info() -> Dict[str, Any]:
77
+ if not (_REPO_ROOT / ".git").exists():
78
+ return {}
79
+ commit = _run_git("rev-parse", "HEAD")
80
+ if commit is None:
81
+ return {}
82
+ short = _run_git("rev-parse", "--short", "HEAD")
83
+ branch = _run_git("rev-parse", "--abbrev-ref", "HEAD")
84
+ describe = _run_git("describe", "--tags", "--always", "--dirty")
85
+ tag = _run_git("describe", "--tags", "--exact-match")
86
+ status = _run_git("status", "--porcelain")
87
+ remote = _run_git("config", "--get", "remote.origin.url")
88
+ dirty = bool(status)
89
+
90
+ info: Dict[str, Any] = {
91
+ "git_commit": commit,
92
+ "git_commit_short": short or (commit[:7] if commit else None),
93
+ "git_branch": None if branch == "HEAD" else branch,
94
+ "git_describe": describe,
95
+ "git_tag": tag,
96
+ "git_dirty": dirty,
97
+ }
98
+ if remote:
99
+ url = _normalise_remote_url(remote)
100
+ info["git_url"] = url
101
+ info["git_link"] = f"{url}/commit/{commit}" if commit else url
102
+ return info
103
+
104
+
105
+ @lru_cache(maxsize=1)
106
+ def get_version() -> str:
107
+ """Best-effort version string for the running ``datakit`` package."""
108
+
109
+ info = _git_info()
110
+ describe = info.get("git_describe")
111
+ if describe:
112
+ suffix = "+dirty" if info.get("git_dirty") and not describe.endswith("-dirty") else ""
113
+ return f"{describe}{suffix}"
114
+ return _installed_version() or _file_version() or "0.0.0+unknown"
115
+
116
+
117
+ def build_meta() -> Dict[str, Any]:
118
+ """Return a snapshot of provenance metadata for the running package.
119
+
120
+ ``built_at`` is regenerated on every call so embedded copies record the
121
+ moment a dataset was materialized rather than when the module was imported.
122
+ """
123
+
124
+ meta: Dict[str, Any] = {
125
+ "package": _PACKAGE_NAME,
126
+ "version": get_version(),
127
+ "version_file": _file_version(),
128
+ "version_installed": _installed_version(),
129
+ "python": sys.version.split()[0],
130
+ "python_implementation": _platform.python_implementation(),
131
+ "platform": _platform.platform(),
132
+ "built_at": _dt.datetime.now(_dt.timezone.utc).isoformat(timespec="seconds"),
133
+ }
134
+ meta.update(_git_info())
135
+ return meta
136
+
137
+
138
+ __version__ = get_version()
139
+
140
+
141
+ __all__ = ["__version__", "get_version", "build_meta"]
@@ -0,0 +1,50 @@
1
+ """Centralized configuration for ``datakit``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Tuple
7
+
8
+
9
+ @dataclass
10
+ class SourceMetaDefaults:
11
+ """Keys used to annotate stream metadata for all data sources."""
12
+
13
+ meta_camera_key: str = "camera_tag"
14
+ meta_timeseries_key: str = "is_timeseries"
15
+ meta_source_key: str = "source_tag"
16
+ meta_interval_key: str = "is_interval"
17
+
18
+
19
+ @dataclass
20
+ class DatasetLayout:
21
+ """Shape and naming expectations for materialized experiment datasets."""
22
+
23
+ index_names: Tuple[str, str, str] = ("Subject", "Session", "Task")
24
+ scope_key: str = "scope"
25
+ session_scope: str = "session"
26
+ experiment_scope: str = "experiment"
27
+
28
+
29
+ @dataclass
30
+ class TimelineDefaults:
31
+ """Options that describe how we discover and parse timeline CSV files."""
32
+
33
+ dataqueue_glob: str = "*_dataqueue.csv"
34
+ queue_column: str = "queue_elapsed"
35
+ window_device_patterns: Tuple[str, ...] = ("dhyana", "mesoscope")
36
+
37
+
38
+ @dataclass
39
+ class Settings:
40
+ """Container aggregating all configuration namespaces for callers."""
41
+
42
+ sources: SourceMetaDefaults = field(default_factory=SourceMetaDefaults)
43
+ dataset: DatasetLayout = field(default_factory=DatasetLayout)
44
+ timeline: TimelineDefaults = field(default_factory=TimelineDefaults)
45
+
46
+
47
+ settings = Settings()
48
+
49
+
50
+ __all__ = ["settings"]