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.
- docs/_static/custom.css +40 -0
- docs/_static/favicon.png +0 -0
- docs/_static/logo.png +0 -0
- docs/api/index.md +70 -0
- docs/conf.py +200 -0
- docs/developer_guide.md +303 -0
- docs/index.md +25 -0
- docs/tutorial.md +4 -0
- docs/user_guide.md +172 -0
- examples/teensy_pulse_generator.py +320 -0
- experiments/pipeline_demo/experiment.json +24 -0
- experiments/pipeline_demo/hardware.yaml +23 -0
- experiments/pipeline_demo/procedure.py +50 -0
- experiments/two_cam_demo/experiment.json +24 -0
- experiments/two_cam_demo/hardware.yaml +58 -0
- experiments/two_cam_demo/load_dataset.py +213 -0
- experiments/two_cam_demo/procedure.py +87 -0
- external/video-codecs/openh264-1.8.0-win64.dll +0 -0
- mesofield/__init__.py +45 -0
- mesofield/__main__.py +11 -0
- mesofield/_version.py +24 -0
- mesofield/base.py +750 -0
- mesofield/cli/__init__.py +57 -0
- mesofield/cli/_richhelp.py +100 -0
- mesofield/cli/acquire.py +254 -0
- mesofield/cli/datakit.py +165 -0
- mesofield/cli/process.py +376 -0
- mesofield/cli/rig.py +108 -0
- mesofield/cli/tools.py +347 -0
- mesofield/config.py +751 -0
- mesofield/data/__init__.py +23 -0
- mesofield/data/batch.py +633 -0
- mesofield/data/manager.py +388 -0
- mesofield/data/writer.py +289 -0
- mesofield/datakit/__init__.py +44 -0
- mesofield/datakit/__main__.py +35 -0
- mesofield/datakit/_utils/_logger.py +5 -0
- mesofield/datakit/_version.py +141 -0
- mesofield/datakit/config.py +50 -0
- mesofield/datakit/core.py +783 -0
- mesofield/datakit/datamodel.py +200 -0
- mesofield/datakit/discover.py +124 -0
- mesofield/datakit/explore.py +651 -0
- mesofield/datakit/notebooks/pupil_dlc.ipynb +2445 -0
- mesofield/datakit/profile.py +535 -0
- mesofield/datakit/shell.py +83 -0
- mesofield/datakit/sources/__init__.py +65 -0
- mesofield/datakit/sources/analysis/mesomap.py +194 -0
- mesofield/datakit/sources/analysis/mesoscope.py +77 -0
- mesofield/datakit/sources/analysis/pupil.py +246 -0
- mesofield/datakit/sources/behavior/__init__.py +0 -0
- mesofield/datakit/sources/behavior/dataqueue.py +281 -0
- mesofield/datakit/sources/behavior/psychopy.py +364 -0
- mesofield/datakit/sources/behavior/treadmill.py +323 -0
- mesofield/datakit/sources/behavior/wheel.py +277 -0
- mesofield/datakit/sources/camera/mesoscope.py +32 -0
- mesofield/datakit/sources/camera/metadata_json.py +130 -0
- mesofield/datakit/sources/camera/pupil.py +28 -0
- mesofield/datakit/sources/camera/suite2p.py +547 -0
- mesofield/datakit/sources/register.py +204 -0
- mesofield/datakit/sources/session/config.py +130 -0
- mesofield/datakit/sources/session/notes.py +63 -0
- mesofield/datakit/sources/session/timestamps.py +58 -0
- mesofield/datakit/timeline.py +306 -0
- mesofield/devices/__init__.py +42 -0
- mesofield/devices/base.py +498 -0
- mesofield/devices/base_camera.py +295 -0
- mesofield/devices/cameras.py +740 -0
- mesofield/devices/daq.py +151 -0
- mesofield/devices/encoder.py +384 -0
- mesofield/devices/mocks.py +275 -0
- mesofield/devices/psychopy_device.py +455 -0
- mesofield/devices/subprocesses/__init__.py +0 -0
- mesofield/devices/subprocesses/psychopy.py +133 -0
- mesofield/devices/treadmill.py +318 -0
- mesofield/engines.py +380 -0
- mesofield/gui/Mesofield_icon.png +0 -0
- mesofield/gui/__init__.py +76 -0
- mesofield/gui/config_wizard.py +724 -0
- mesofield/gui/controller.py +535 -0
- mesofield/gui/dynamic_controller.py +78 -0
- mesofield/gui/maingui.py +427 -0
- mesofield/gui/mdagui.py +285 -0
- mesofield/gui/qt_device_adapter.py +109 -0
- mesofield/gui/speedplotter.py +152 -0
- mesofield/gui/theme.py +445 -0
- mesofield/gui/tiff_viewer.py +1050 -0
- mesofield/gui/viewer.py +691 -0
- mesofield/hardware.py +549 -0
- mesofield/playback.py +1298 -0
- mesofield/processing/__init__.py +12 -0
- mesofield/processing/runner.py +237 -0
- mesofield/processors/__init__.py +13 -0
- mesofield/processors/base.py +287 -0
- mesofield/processors/frame_mean.py +19 -0
- mesofield/protocols.py +378 -0
- mesofield/scaffold/__init__.py +34 -0
- mesofield/scaffold/experiment.py +400 -0
- mesofield/scaffold/rigs.py +121 -0
- mesofield/signals.py +85 -0
- mesofield/utils/__init__.py +0 -0
- mesofield/utils/_logger.py +156 -0
- mesofield/utils/retrofit.py +309 -0
- mesofield/utils/utils.py +217 -0
- mesofield-0.3.2b0.dist-info/METADATA +178 -0
- mesofield-0.3.2b0.dist-info/RECORD +111 -0
- mesofield-0.3.2b0.dist-info/WHEEL +5 -0
- mesofield-0.3.2b0.dist-info/entry_points.txt +2 -0
- mesofield-0.3.2b0.dist-info/licenses/LICENSE +21 -0
- mesofield-0.3.2b0.dist-info/top_level.txt +6 -0
- scripts/bench_frame_processor.py +103 -0
mesofield/cli/tools.py
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""``mesofield tools`` — setup, export, and diagnostic utilities.
|
|
2
|
+
|
|
3
|
+
Lower-frequency helpers: native-driver installation, hardware export,
|
|
4
|
+
framerate benchmarking, the PsychoPy test harness, and a dev config shell.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
|
|
14
|
+
from ._richhelp import RichGroup
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.group('tools', cls=RichGroup)
|
|
18
|
+
def tools():
|
|
19
|
+
"""Setup, export, and diagnostic utilities."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# install-drivers
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@tools.command('install-drivers')
|
|
28
|
+
@click.option('--mm-dir', 'mm_dir', default=None,
|
|
29
|
+
help='Explicit path to the Micro-Manager root directory. '
|
|
30
|
+
'Auto-detected from pymmcore-plus when omitted.')
|
|
31
|
+
@click.option('--keep-zip/--no-keep-zip', default=False, show_default=True,
|
|
32
|
+
help='Keep the downloaded zip file after extraction.')
|
|
33
|
+
def install_drivers(mm_dir, keep_zip):
|
|
34
|
+
"""Download Thorlabs Scientific Camera SDK and install native DLLs into Micro-Manager.
|
|
35
|
+
|
|
36
|
+
This command performs the following steps:
|
|
37
|
+
|
|
38
|
+
\b
|
|
39
|
+
1. Locate (or install) the Micro-Manager device adapters via pymmcore-plus.
|
|
40
|
+
2. Download the Thorlabs Scientific Camera Interfaces SDK.
|
|
41
|
+
3. Extract the SDK into mesofield/external/drivers/.
|
|
42
|
+
4. Copy the 64-bit native DLLs into the Micro-Manager root directory.
|
|
43
|
+
"""
|
|
44
|
+
import shutil
|
|
45
|
+
import zipfile
|
|
46
|
+
|
|
47
|
+
THORLABS_SDK_URL = (
|
|
48
|
+
"https://media.thorlabs.com/contentassets/"
|
|
49
|
+
"039fcbaaafa0457eb2901466cf0b9489/"
|
|
50
|
+
"scientific_camera_interfaces_windows-2.1.zip"
|
|
51
|
+
"?v=1116040458"
|
|
52
|
+
)
|
|
53
|
+
# Relative path inside the extracted zip that contains the 64-bit DLLs
|
|
54
|
+
DLL_SUBPATH = Path(
|
|
55
|
+
"Scientific Camera Interfaces",
|
|
56
|
+
"SDK",
|
|
57
|
+
"Native Toolkit",
|
|
58
|
+
"dlls",
|
|
59
|
+
"Native_64_lib",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
EXTERNAL_DRIVERS_DIR = (
|
|
63
|
+
Path(__file__).resolve().parent.parent / "external" / "drivers"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# ---- Step 1: Resolve the Micro-Manager root directory ----
|
|
67
|
+
if mm_dir is not None:
|
|
68
|
+
mm_root = Path(mm_dir)
|
|
69
|
+
else:
|
|
70
|
+
mm_root = _resolve_micromanager_root()
|
|
71
|
+
|
|
72
|
+
if not mm_root.is_dir():
|
|
73
|
+
click.secho(f"ERROR: Micro-Manager directory does not exist: {mm_root}", fg="red")
|
|
74
|
+
raise SystemExit(1)
|
|
75
|
+
|
|
76
|
+
click.echo(f"Micro-Manager root: {mm_root}")
|
|
77
|
+
|
|
78
|
+
# ---- Step 2: Download the Thorlabs SDK zip ----
|
|
79
|
+
EXTERNAL_DRIVERS_DIR.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
zip_dest = EXTERNAL_DRIVERS_DIR / "scientific_camera_interfaces_windows-2.1.zip"
|
|
81
|
+
|
|
82
|
+
if zip_dest.exists():
|
|
83
|
+
click.echo(f"Zip already present at {zip_dest}, skipping download.")
|
|
84
|
+
else:
|
|
85
|
+
click.echo("Downloading Thorlabs Scientific Camera Interfaces SDK …")
|
|
86
|
+
try:
|
|
87
|
+
_download_with_progress(THORLABS_SDK_URL, zip_dest)
|
|
88
|
+
except Exception as exc:
|
|
89
|
+
click.secho(f"Download failed: {exc}", fg="red")
|
|
90
|
+
raise SystemExit(1)
|
|
91
|
+
|
|
92
|
+
# ---- Step 3: Extract into external/drivers/ ----
|
|
93
|
+
extract_dir = EXTERNAL_DRIVERS_DIR / "scientific_camera_interfaces"
|
|
94
|
+
if extract_dir.exists():
|
|
95
|
+
click.echo(f"Extraction folder already exists at {extract_dir}, skipping extraction.")
|
|
96
|
+
else:
|
|
97
|
+
click.echo(f"Extracting SDK to {extract_dir} …")
|
|
98
|
+
try:
|
|
99
|
+
with zipfile.ZipFile(zip_dest, "r") as zf:
|
|
100
|
+
zf.extractall(extract_dir)
|
|
101
|
+
except zipfile.BadZipFile as exc:
|
|
102
|
+
click.secho(f"Bad zip file: {exc}", fg="red")
|
|
103
|
+
raise SystemExit(1)
|
|
104
|
+
|
|
105
|
+
# ---- Step 4: Copy 64-bit native DLLs into Micro-Manager root ----
|
|
106
|
+
dll_source = extract_dir / DLL_SUBPATH
|
|
107
|
+
if not dll_source.is_dir():
|
|
108
|
+
# The zip might have a single top-level folder; search for a match
|
|
109
|
+
candidates = list(extract_dir.rglob("Native_64_lib"))
|
|
110
|
+
if candidates:
|
|
111
|
+
dll_source = candidates[0]
|
|
112
|
+
else:
|
|
113
|
+
click.secho(
|
|
114
|
+
f"ERROR: Could not locate Native_64_lib inside the extracted archive.\n"
|
|
115
|
+
f"Expected at: {dll_source}",
|
|
116
|
+
fg="red",
|
|
117
|
+
)
|
|
118
|
+
raise SystemExit(1)
|
|
119
|
+
|
|
120
|
+
dll_files = list(dll_source.glob("*.dll"))
|
|
121
|
+
if not dll_files:
|
|
122
|
+
click.secho(f"WARNING: No .dll files found in {dll_source}", fg="yellow")
|
|
123
|
+
raise SystemExit(1)
|
|
124
|
+
|
|
125
|
+
click.echo(f"Copying {len(dll_files)} DLL(s) from {dll_source} → {mm_root}")
|
|
126
|
+
for dll in dll_files:
|
|
127
|
+
dest = mm_root / dll.name
|
|
128
|
+
shutil.copy2(dll, dest)
|
|
129
|
+
click.echo(f" ✓ {dll.name}")
|
|
130
|
+
|
|
131
|
+
# ---- Cleanup ----
|
|
132
|
+
if not keep_zip and zip_dest.exists():
|
|
133
|
+
zip_dest.unlink()
|
|
134
|
+
click.echo("Removed downloaded zip file.")
|
|
135
|
+
|
|
136
|
+
click.secho("\nThorlabs Scientific Camera DLLs installed successfully.", fg="green")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _resolve_micromanager_root() -> Path:
|
|
140
|
+
"""Locate the Micro-Manager installation via pymmcore-plus.
|
|
141
|
+
|
|
142
|
+
Falls back to running ``mmcore install`` when no installation is found.
|
|
143
|
+
"""
|
|
144
|
+
import subprocess
|
|
145
|
+
import sys
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
from pymmcore_plus import find_micromanager
|
|
149
|
+
mm_path = find_micromanager()
|
|
150
|
+
if mm_path:
|
|
151
|
+
return Path(mm_path)
|
|
152
|
+
except ImportError:
|
|
153
|
+
click.secho(
|
|
154
|
+
"pymmcore-plus is not installed. Install it first:\n"
|
|
155
|
+
" pip install pymmcore-plus",
|
|
156
|
+
fg="red",
|
|
157
|
+
)
|
|
158
|
+
raise SystemExit(1)
|
|
159
|
+
except Exception:
|
|
160
|
+
pass # fall through to mmcore install
|
|
161
|
+
|
|
162
|
+
# No existing installation – offer to install device adapters
|
|
163
|
+
click.echo("No Micro-Manager installation detected.")
|
|
164
|
+
if click.confirm("Run 'mmcore install' to install Micro-Manager device adapters?", default=True):
|
|
165
|
+
subprocess.check_call([sys.executable, "-m", "pymmcore_plus", "install"])
|
|
166
|
+
# Re-resolve after install
|
|
167
|
+
try:
|
|
168
|
+
from pymmcore_plus import find_micromanager
|
|
169
|
+
mm_path = find_micromanager()
|
|
170
|
+
if mm_path:
|
|
171
|
+
return Path(mm_path)
|
|
172
|
+
except Exception:
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
click.secho("ERROR: Could not locate a Micro-Manager installation.", fg="red")
|
|
176
|
+
raise SystemExit(1)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _download_with_progress(url: str, dest: Path, chunk_size: int = 1024 * 64):
|
|
180
|
+
"""Download *url* to *dest* with a simple progress indicator."""
|
|
181
|
+
import urllib.request
|
|
182
|
+
|
|
183
|
+
req = urllib.request.Request(url, headers={"User-Agent": "mesofield-installer/1.0"})
|
|
184
|
+
with urllib.request.urlopen(req) as resp:
|
|
185
|
+
total = int(resp.headers.get("Content-Length", 0))
|
|
186
|
+
downloaded = 0
|
|
187
|
+
with open(dest, "wb") as fh:
|
|
188
|
+
while True:
|
|
189
|
+
chunk = resp.read(chunk_size)
|
|
190
|
+
if not chunk:
|
|
191
|
+
break
|
|
192
|
+
fh.write(chunk)
|
|
193
|
+
downloaded += len(chunk)
|
|
194
|
+
if total:
|
|
195
|
+
pct = downloaded * 100 // total
|
|
196
|
+
click.echo(f"\r {pct:3d}% ({downloaded // 1024:,} KB)", nl=False)
|
|
197
|
+
if total:
|
|
198
|
+
click.echo() # newline after progress
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# ---------------------------------------------------------------------------
|
|
202
|
+
# export-hardware
|
|
203
|
+
# ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@tools.command('export-hardware')
|
|
207
|
+
@click.argument('procedure_path', type=click.Path(exists=True, dir_okay=False))
|
|
208
|
+
@click.option('--output', '-o', default=None, type=click.Path(),
|
|
209
|
+
help='Output path for the hardware.yaml '
|
|
210
|
+
'(default: hardware.yaml beside the procedure file).')
|
|
211
|
+
def export_hardware(procedure_path, output):
|
|
212
|
+
"""Export a scripted procedure's hardware to a reusable hardware.yaml rig file.
|
|
213
|
+
|
|
214
|
+
PROCEDURE_PATH is a procedure.py whose `define_hardware` builds devices in
|
|
215
|
+
Python. The devices are instantiated and serialized into a `type:`-tagged
|
|
216
|
+
hardware.yaml that can later be loaded the normal file-based way.
|
|
217
|
+
"""
|
|
218
|
+
from mesofield.base import load_procedure_from_config
|
|
219
|
+
|
|
220
|
+
out = output or os.path.join(
|
|
221
|
+
os.path.dirname(os.path.abspath(procedure_path)), "hardware.yaml"
|
|
222
|
+
)
|
|
223
|
+
procedure = load_procedure_from_config(procedure_path)
|
|
224
|
+
procedure.hardware.to_yaml(out)
|
|
225
|
+
click.secho(f"Exported hardware configuration to {out}", fg="green")
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
# fps
|
|
230
|
+
# ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@tools.command('fps')
|
|
234
|
+
@click.option('--params', default='hardware.yaml', help='Path to the config file')
|
|
235
|
+
def fps(params):
|
|
236
|
+
"""Measure camera framerate and estimate dataset file sizes."""
|
|
237
|
+
import json
|
|
238
|
+
from tqdm import tqdm
|
|
239
|
+
import numpy as np
|
|
240
|
+
import datetime
|
|
241
|
+
from useq import MDAEvent, MDASequence
|
|
242
|
+
from pymmcore_plus import CMMCorePlus
|
|
243
|
+
from pymmcore_plus.metadata import FrameMetaV1
|
|
244
|
+
from mesofield.config import ExperimentConfig
|
|
245
|
+
|
|
246
|
+
frame_metadata: FrameMetaV1 = None
|
|
247
|
+
|
|
248
|
+
config = ExperimentConfig(params)
|
|
249
|
+
config.hardware.initialize(config)
|
|
250
|
+
|
|
251
|
+
# measure over a fixed number of frames to get fps
|
|
252
|
+
num_frames = 300
|
|
253
|
+
mmc: CMMCorePlus = config.hardware.ThorCam.core
|
|
254
|
+
sequence = MDASequence(time_plan={"interval": 0, "loops": num_frames})
|
|
255
|
+
|
|
256
|
+
# ask user for desired duration (in seconds)
|
|
257
|
+
duration = float(input("Enter duration in seconds for file‐size estimate: "))
|
|
258
|
+
num_animals = int(input("Enter number of animals: "))
|
|
259
|
+
num_sessions = int(input("Enter number of sessions: "))
|
|
260
|
+
|
|
261
|
+
times = []
|
|
262
|
+
pbar = tqdm(total=num_frames, desc="Acquiring frames")
|
|
263
|
+
img_size = 0
|
|
264
|
+
|
|
265
|
+
@mmc.mda.events.frameReady.connect
|
|
266
|
+
def new_frame(img: np.ndarray, event: MDAEvent, metadata: dict):
|
|
267
|
+
|
|
268
|
+
nonlocal img_size
|
|
269
|
+
nonlocal frame_metadata
|
|
270
|
+
# frame timestamps
|
|
271
|
+
frame_time_str = metadata['camera_metadata']['TimeReceivedByCore']
|
|
272
|
+
times.append(datetime.datetime.fromisoformat(frame_time_str))
|
|
273
|
+
|
|
274
|
+
# single instance of frame metadata for printing:
|
|
275
|
+
if frame_metadata is None:
|
|
276
|
+
frame_metadata = metadata
|
|
277
|
+
|
|
278
|
+
# record single image size once
|
|
279
|
+
if img_size == 0:
|
|
280
|
+
img_size = img.nbytes
|
|
281
|
+
pbar.update(1)
|
|
282
|
+
|
|
283
|
+
# run acquisition
|
|
284
|
+
mmc.run_mda(sequence, block=True)
|
|
285
|
+
pbar.close()
|
|
286
|
+
|
|
287
|
+
# compute fps
|
|
288
|
+
deltas = [(t2 - t1).total_seconds() for t1, t2 in zip(times[:-1], times[1:])]
|
|
289
|
+
fps_value = 1 / np.mean(deltas)
|
|
290
|
+
|
|
291
|
+
# estimate file size for the user‐specified duration
|
|
292
|
+
estimated_frames = int(fps_value * duration)
|
|
293
|
+
estimated_bytes = img_size * estimated_frames
|
|
294
|
+
estimated_gb = estimated_bytes / (1024**3)
|
|
295
|
+
total_gbs = estimated_gb * num_animals * num_sessions
|
|
296
|
+
summary = {
|
|
297
|
+
"Camera Device": mmc.getCameraDevice(),
|
|
298
|
+
"Exposure (ms)": mmc.getExposure(),
|
|
299
|
+
"Camera Metadata": frame_metadata["camera_metadata"],
|
|
300
|
+
"Measured FPS": round(fps_value, 2),
|
|
301
|
+
"Duration (s)": duration,
|
|
302
|
+
"Frames": estimated_frames,
|
|
303
|
+
"Individual TIFF Stack Size (MB)": round(estimated_gb * 1024, 2),
|
|
304
|
+
"Animals": num_animals,
|
|
305
|
+
"Sessions": num_sessions,
|
|
306
|
+
"Total Estimated Size (GB)": round(total_gbs, 2)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
print(json.dumps(summary, indent=4))
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
# ---------------------------------------------------------------------------
|
|
313
|
+
# psychopy
|
|
314
|
+
# ---------------------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@tools.command('psychopy')
|
|
318
|
+
def psychopy():
|
|
319
|
+
"""Launch the PsychoPy test harness GUI (development tool)."""
|
|
320
|
+
import sys
|
|
321
|
+
from PyQt6.QtWidgets import QApplication
|
|
322
|
+
import tests.test_psychopy as test_psychopy
|
|
323
|
+
from mesofield.gui import theme
|
|
324
|
+
|
|
325
|
+
app = QApplication(sys.argv)
|
|
326
|
+
theme.apply_theme(app)
|
|
327
|
+
gui = test_psychopy.DillPsychopyGui()
|
|
328
|
+
gui.show()
|
|
329
|
+
sys.exit(app.exec())
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
# ---------------------------------------------------------------------------
|
|
333
|
+
# config-shell
|
|
334
|
+
# ---------------------------------------------------------------------------
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
@tools.command('config-shell')
|
|
338
|
+
@click.option('--yaml_path', default='tests/dev.yaml', help='Path to the YAML config file')
|
|
339
|
+
@click.option('--json_path', default='tests/devsub.json', help='Path to the JSON config file')
|
|
340
|
+
def config_shell(yaml_path, json_path):
|
|
341
|
+
"""Load an IPython terminal with an ExperimentConfig in a dev configuration."""
|
|
342
|
+
from mesofield.config import ExperimentConfig
|
|
343
|
+
from IPython import embed
|
|
344
|
+
|
|
345
|
+
config = ExperimentConfig(yaml_path)
|
|
346
|
+
config.load_json(json_path)
|
|
347
|
+
embed(header='Mesofield ExperimentConfig Terminal. Type `config.` + TAB ', local={'config': config})
|