plasma-app 2.2.2__tar.gz → 2.2.4__tar.gz

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 (76) hide show
  1. {plasma_app-2.2.2 → plasma_app-2.2.4}/PKG-INFO +28 -3
  2. {plasma_app-2.2.2 → plasma_app-2.2.4}/README.md +22 -2
  3. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/__init__.py +1 -1
  4. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/config.py +28 -6
  5. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/device.py +32 -1
  6. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/extract/pipeline.py +5 -5
  7. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/panels/extractor.py +26 -2
  8. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma_app.egg-info/PKG-INFO +28 -3
  9. {plasma_app-2.2.2 → plasma_app-2.2.4}/pyproject.toml +7 -0
  10. {plasma_app-2.2.2 → plasma_app-2.2.4}/LICENSE +0 -0
  11. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/__main__.py +0 -0
  12. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/__pyinstaller/__init__.py +0 -0
  13. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/__pyinstaller/hook-plasma.py +0 -0
  14. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/api.py +0 -0
  15. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/app_context.py +0 -0
  16. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/build_info.py +0 -0
  17. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/config.py +0 -0
  18. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/ctl.py +0 -0
  19. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/desktop_shortcut.py +0 -0
  20. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/__init__.py +0 -0
  21. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/bitalino.py +0 -0
  22. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/__init__.py +0 -0
  23. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/ble_scan.py +0 -0
  24. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/bleak_probe.py +0 -0
  25. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/bleak_reconnect_test.py +0 -0
  26. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/detect.py +0 -0
  27. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/extract/__init__.py +0 -0
  28. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/extract/__main__.py +0 -0
  29. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/extract/clocksync.py +0 -0
  30. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/extract/options.py +0 -0
  31. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/formats/__init__.py +0 -0
  32. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/gyro_bias.py +0 -0
  33. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/nus_sim.py +0 -0
  34. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/nus_stream.py +0 -0
  35. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/panels/__init__.py +0 -0
  36. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/panels/_common.py +0 -0
  37. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/panels/clocksync.py +0 -0
  38. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/panels/control.py +0 -0
  39. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/panels/downloader.py +0 -0
  40. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/panels/imu.py +0 -0
  41. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/panels/sqc.py +0 -0
  42. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/panels/tools.py +0 -0
  43. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/panels/uuid_tools.py +0 -0
  44. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/panels/viewer.py +0 -0
  45. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/quaternion.py +0 -0
  46. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/records.py +0 -0
  47. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense/signal_quality.py +0 -0
  48. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense_demo/__init__.py +0 -0
  49. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense_demo/config.py +0 -0
  50. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense_demo/device.py +0 -0
  51. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense_demo/fake_stream.py +0 -0
  52. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/msense_demo/faults.py +0 -0
  53. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/obs.py +0 -0
  54. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/pupil_labs.py +0 -0
  55. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/qb2.py +0 -0
  56. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/shimmer.py +0 -0
  57. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/devices/template.py +0 -0
  58. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/integrated_panel.py +0 -0
  59. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/journal.py +0 -0
  60. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/lsl_recorder.py +0 -0
  61. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/lsl_session.py +0 -0
  62. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/lsl_util.py +0 -0
  63. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/plot_util.py +0 -0
  64. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/plugins.py +0 -0
  65. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/resources/icons/plasma.icns +0 -0
  66. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/resources/icons/plasma.ico +0 -0
  67. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/resources/icons/plasma.png +0 -0
  68. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/resources/icons/plasma.svg +0 -0
  69. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/status.py +0 -0
  70. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma/xdf_writer.py +0 -0
  71. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma_app.egg-info/SOURCES.txt +0 -0
  72. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma_app.egg-info/dependency_links.txt +0 -0
  73. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma_app.egg-info/entry_points.txt +0 -0
  74. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma_app.egg-info/requires.txt +0 -0
  75. {plasma_app-2.2.2 → plasma_app-2.2.4}/plasma_app.egg-info/top_level.txt +0 -0
  76. {plasma_app-2.2.2 → plasma_app-2.2.4}/setup.cfg +0 -0
@@ -1,10 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plasma-app
3
- Version: 2.2.2
3
+ Version: 2.2.4
4
4
  Summary: PLASMA — Platform for LSL-based Acquisition of Sensor Metrics and Analytics
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/YuyiChang/PLASMA
7
7
  Project-URL: Repository, https://github.com/YuyiChang/PLASMA
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
8
13
  Requires-Python: >=3.10
9
14
  Description-Content-Type: text/markdown
10
15
  License-File: LICENSE
@@ -54,10 +59,30 @@ PLASMA: Platform for LSL-based Acquisition of Sensor Metrics and Analytics
54
59
  configure, pair MSense wristbands, run a session, read the memo panel, recover
55
60
  from faults.
56
61
 
62
+ [![PyPI version](https://img.shields.io/pypi/v/plasma-app.svg)](https://pypi.org/project/plasma-app/)
63
+ [![Python](https://img.shields.io/pypi/pyversions/plasma-app.svg)](https://pypi.org/project/plasma-app/)
64
+ [![License](https://img.shields.io/github/license/yuyichang/PLASMA.svg)](LICENSE)
65
+ [![GitHub Release](https://img.shields.io/github/v/release/yuyichang/PLASMA)](https://github.com/yuyichang/PLASMA/releases/latest)
66
+
67
+
68
+ ## Download
69
+
70
+ | Platform | Architecture | Download |
71
+ |---|---|---|
72
+ | macOS | Apple Silicon (arm64) | [Download `.dmg`](https://github.com/YuyiChang/PLASMA/releases/latest/download/PLASMA_MacOS_arm64.dmg) |
73
+ | Windows | x64 | [Download Installer `.exe`](https://github.com/YuyiChang/PLASMA/releases/latest/download/PLASMA_Windows_x64_Setup.exe) |
74
+ | Linux | x64 | [Download `.tar.gz`](https://github.com/YuyiChang/PLASMA/releases/latest/download/PLASMA_Linux_x64.tar.gz) |
75
+ | Linux | ARM64 | [Download `.tar.gz`](https://github.com/YuyiChang/PLASMA/releases/latest/download/PLASMA_Linux_arm64.tar.gz) |
76
+
77
+ > For other packages and previous versions, see [GitHub Releases](https://github.com/YuyiChang/PLASMA/releases).
78
+
79
+
57
80
  ## quickstart
58
81
 
59
82
  **macOS: `brew install --cask yuyichang/plasma/plasma`** is the recommended
60
- way to get PLASMA — no Python, no `liblsl`, no Gatekeeper dance. See
83
+ way to get PLASMA — no Python, no `liblsl`, no Gatekeeper dance. **Windows:
84
+ `scoop bucket add plasma https://github.com/YuyiChang/scoop-plasma` then
85
+ `scoop install plasma`** does the same, without the SmartScreen warning. See
61
86
  [Install & first launch](https://yuyichang.github.io/PLASMA/playbook/install/)
62
87
  for that and every other install path (prebuilt binaries, PyPI, from source).
63
88
 
@@ -101,7 +126,7 @@ plugins: `msense`, `qb2`, `pupil`, `shimmer`, `obs` (and `all`).
101
126
  - [x] linux aarch64 build — `PLASMA_Linux_arm64` for Jetson Orin (JetPack 6); CI job `build-linux-arm64`
102
127
  - [ ] infrequent stream (e.g., PLASMA journaler) shows green after recording stop
103
128
  - [ ] doc for msense workflow with better screenshots
104
- - [ ] memo `stage` transition logic optimization (e.g., timing of "ready to start" msg wrt sensor init status)
129
+ - [x] memo `stage` transition logic optimization (e.g., timing of "ready to start" msg wrt sensor init status)
105
130
 
106
131
  ## developing a sensor plugin
107
132
 
@@ -6,10 +6,30 @@ PLASMA: Platform for LSL-based Acquisition of Sensor Metrics and Analytics
6
6
  configure, pair MSense wristbands, run a session, read the memo panel, recover
7
7
  from faults.
8
8
 
9
+ [![PyPI version](https://img.shields.io/pypi/v/plasma-app.svg)](https://pypi.org/project/plasma-app/)
10
+ [![Python](https://img.shields.io/pypi/pyversions/plasma-app.svg)](https://pypi.org/project/plasma-app/)
11
+ [![License](https://img.shields.io/github/license/yuyichang/PLASMA.svg)](LICENSE)
12
+ [![GitHub Release](https://img.shields.io/github/v/release/yuyichang/PLASMA)](https://github.com/yuyichang/PLASMA/releases/latest)
13
+
14
+
15
+ ## Download
16
+
17
+ | Platform | Architecture | Download |
18
+ |---|---|---|
19
+ | macOS | Apple Silicon (arm64) | [Download `.dmg`](https://github.com/YuyiChang/PLASMA/releases/latest/download/PLASMA_MacOS_arm64.dmg) |
20
+ | Windows | x64 | [Download Installer `.exe`](https://github.com/YuyiChang/PLASMA/releases/latest/download/PLASMA_Windows_x64_Setup.exe) |
21
+ | Linux | x64 | [Download `.tar.gz`](https://github.com/YuyiChang/PLASMA/releases/latest/download/PLASMA_Linux_x64.tar.gz) |
22
+ | Linux | ARM64 | [Download `.tar.gz`](https://github.com/YuyiChang/PLASMA/releases/latest/download/PLASMA_Linux_arm64.tar.gz) |
23
+
24
+ > For other packages and previous versions, see [GitHub Releases](https://github.com/YuyiChang/PLASMA/releases).
25
+
26
+
9
27
  ## quickstart
10
28
 
11
29
  **macOS: `brew install --cask yuyichang/plasma/plasma`** is the recommended
12
- way to get PLASMA — no Python, no `liblsl`, no Gatekeeper dance. See
30
+ way to get PLASMA — no Python, no `liblsl`, no Gatekeeper dance. **Windows:
31
+ `scoop bucket add plasma https://github.com/YuyiChang/scoop-plasma` then
32
+ `scoop install plasma`** does the same, without the SmartScreen warning. See
13
33
  [Install & first launch](https://yuyichang.github.io/PLASMA/playbook/install/)
14
34
  for that and every other install path (prebuilt binaries, PyPI, from source).
15
35
 
@@ -53,7 +73,7 @@ plugins: `msense`, `qb2`, `pupil`, `shimmer`, `obs` (and `all`).
53
73
  - [x] linux aarch64 build — `PLASMA_Linux_arm64` for Jetson Orin (JetPack 6); CI job `build-linux-arm64`
54
74
  - [ ] infrequent stream (e.g., PLASMA journaler) shows green after recording stop
55
75
  - [ ] doc for msense workflow with better screenshots
56
- - [ ] memo `stage` transition logic optimization (e.g., timing of "ready to start" msg wrt sensor init status)
76
+ - [x] memo `stage` transition logic optimization (e.g., timing of "ready to start" msg wrt sensor init status)
57
77
 
58
78
  ## developing a sensor plugin
59
79
 
@@ -1,4 +1,4 @@
1
- __version__ = "2.2.2"
1
+ __version__ = "2.2.4"
2
2
 
3
3
  # Short git commit hash baked in by CI (.github/workflows/build.yml) just
4
4
  # before a release build is frozen with PyInstaller — a frozen bundle ships no
@@ -46,7 +46,12 @@ def merge_msense_records(existing, scanned, overwrite):
46
46
 
47
47
  ``overwrite=True`` → one fresh record per scanned device, nothing kept.
48
48
  ``overwrite=False`` → ``existing`` plus a record for every scanned address
49
- not already listed (case-insensitive on "UUID / MAC Address").
49
+ not already listed (case-insensitive on "UUID / MAC Address"). A scanned
50
+ address that IS already listed has that record's Name refreshed to the
51
+ freshly advertised name instead — Nickname/Enabled/IMU Stream are left
52
+ alone — so a firmware update that changes the advertised name reaches the
53
+ table via Append without needing Overwrite (which would also reset those
54
+ per-device toggles for every other configured wristband).
50
55
 
51
56
  Fresh records use the advertised name as Name, blank Nickname, Enabled on,
52
57
  IMU Stream off. Returns ``(records, summary_str)``.
@@ -64,13 +69,30 @@ def merge_msense_records(existing, scanned, overwrite):
64
69
  records = [_fresh(dev) for dev in scanned]
65
70
  return records, f"Overwrote table with {len(records)} scanned wristband(s)"
66
71
 
72
+ scanned_by_addr = {dev["address"].strip().upper(): dev for dev in scanned}
73
+ records = []
74
+ renamed = 0
75
+ for rec in existing:
76
+ dev = scanned_by_addr.get(str(rec.get("UUID / MAC Address", "")).strip().upper())
77
+ if dev and dev["name"] != rec.get("Name", ""):
78
+ rec = {**rec, "Name": dev["name"]}
79
+ renamed += 1
80
+ records.append(rec)
81
+
67
82
  have = {str(rec.get("UUID / MAC Address", "")).strip().upper() for rec in existing}
68
83
  added = [_fresh(dev) for dev in scanned if dev["address"].strip().upper() not in have]
69
- skipped = len(scanned) - len(added)
70
- msg = f"Appended {len(added)} new wristband(s)"
71
- if skipped:
72
- msg += f" ({skipped} already listed)"
73
- return list(existing) + added, msg
84
+ records += added
85
+
86
+ unchanged = len(scanned) - len(added) - renamed
87
+ parts = []
88
+ if added:
89
+ parts.append(f"{len(added)} new")
90
+ if renamed:
91
+ parts.append(f"{renamed} renamed")
92
+ if unchanged:
93
+ parts.append(f"{unchanged} unchanged")
94
+ msg = "Appended " + ", ".join(parts) if parts else "No changes"
95
+ return records, msg
74
96
 
75
97
 
76
98
  # ── device_info.json import (YAMS provisioning format) ───────────────────────
@@ -74,6 +74,10 @@ BATTERY_CHAR_UUID = "00002a19-0000-1000-8000-00805f9b34fb"
74
74
 
75
75
  SQC_MIN_MTU = 128
76
76
  SQC_DEBUG = True # emit per-notification telemetry (printed off the BLE thread)
77
+ # devices with no active SQC/live session still get unsolicited NUS traffic
78
+ # (boot chatter, or firmware that exposes the NUS char but doesn't speak the
79
+ # SQC/live protocol) — coalesce that noise instead of one debug line/packet
80
+ NUS_IDLE_LOG_WINDOW_S = 5.0
77
81
 
78
82
  # NOTE: the overall per-transaction backstop timeout stays DISABLED — the real
79
83
  # BLE data rate is far slower than the handoff doc's provisional 35/45 s
@@ -173,6 +177,11 @@ class MotionSenseHRV(PlasmaDevice):
173
177
  # addr -> live INFINITY-stream state (see _new_live_state); only ever
174
178
  # one NUS stream (SQC snapshot OR live) active per wristband at a time.
175
179
  self.live_state = {}
180
+ # addr -> {"count", "bytes", "window_start"} — coalesces the
181
+ # "ignored (no active stream)" debug line so a chatty/idle wristband
182
+ # doesn't flood the console one line per notification (see
183
+ # _log_nus_ignored / NUS_IDLE_LOG_WINDOW_S)
184
+ self._nus_idle_rx = {}
176
185
  # addr -> {"status": confirmed|unconfirmed|unverifiable|unknown,
177
186
  # "checked_at": ts} — set by collection_ctl(addr, False), see
178
187
  # _confirm_acq_stopped. Cleared on the next collection_ctl(addr, True).
@@ -1325,7 +1334,7 @@ class MotionSenseHRV(PlasmaDevice):
1325
1334
  elif live and live.get("session") and not live["session"].is_terminal:
1326
1335
  self._handle_live_notification(live, name, bytes(data), now, t_entry)
1327
1336
  else:
1328
- self._sqc_debug(name, f"rx {len(data)}B ignored (no active stream)")
1337
+ self._log_nus_ignored(name, len(data))
1329
1338
  except Exception as e:
1330
1339
  self.info(f"Error handling NUS data from {name}: {e}")
1331
1340
 
@@ -1520,6 +1529,28 @@ class MotionSenseHRV(PlasmaDevice):
1520
1529
  except queue.Full:
1521
1530
  pass # never block the BLE thread on a slow console
1522
1531
 
1532
+ def _log_nus_ignored(self, name, nbytes):
1533
+ # first notification in a window prints immediately (so isolated,
1534
+ # one-off chatter is still visible); anything after that within
1535
+ # NUS_IDLE_LOG_WINDOW_S is tallied silently and flushed as one
1536
+ # rolled-up line — keeps a continuously-chattering/no-session
1537
+ # wristband (e.g. firmware that exposes NUS but doesn't speak the
1538
+ # SQC/live protocol) from spamming a line per packet
1539
+ st = self._nus_idle_rx.setdefault(name, {"count": 0, "bytes": 0, "window_start": 0.0})
1540
+ now = time.time()
1541
+ st["count"] += 1
1542
+ st["bytes"] += nbytes
1543
+ if now - st["window_start"] < NUS_IDLE_LOG_WINDOW_S:
1544
+ return
1545
+ if st["count"] > 1:
1546
+ self._sqc_debug(
1547
+ name, f"rx {st['count']}x ({st['bytes']}B total) ignored (no active stream)")
1548
+ else:
1549
+ self._sqc_debug(name, f"rx {nbytes}B ignored (no active stream)")
1550
+ st["count"] = 0
1551
+ st["bytes"] = 0
1552
+ st["window_start"] = now
1553
+
1523
1554
  def _sqc_diag_summary(self, name, store=None):
1524
1555
  d = (store or self.sqc_state).get(name, {}).get("diag")
1525
1556
  if not d:
@@ -316,19 +316,19 @@ class DataExtractor():
316
316
  out_dir=self.out_dir, id=id)
317
317
 
318
318
  def generate_csv_for_pattern(self, in_dir, type_prefix: str, search_key: str, out_dir="./", id=-1):
319
- # 1. Ignore ID Parsing Handling
320
- if self.ignore_id_parsing:
321
- file_name = type_prefix # Defaults to id + "ac.csv" or ".pkl"
319
+ # An empty ID means the binary starts with its sensor tag (e.g.
320
+ # ppg1047.bin), so there is no participant encoding to expand.
321
+ if self.ignore_id_parsing or id == "":
322
+ file_name = type_prefix
322
323
  else:
323
324
  if str(id) in self.encoding_alias.keys():
324
325
  alias = self.encoding_alias[str(id)]
325
326
  print('=====', id, alias)
326
- file_name = f"{type_prefix}".replace(id, alias)
327
327
  else:
328
328
  sub_id = str(id)[:-2]
329
329
  ses_id = str(id)[-2:]
330
330
  alias = f"sub-{sub_id}_ses-{ses_id}_{self.note}_"
331
- file_name = f"{type_prefix}".replace(id, alias)
331
+ file_name = alias + type_prefix[len(str(id)):]
332
332
 
333
333
  print(type_prefix, search_key, '********')
334
334
  # session_dfs: one DataFrame per distinct recording/session sharing this
@@ -15,6 +15,27 @@ from ..extract.options import (
15
15
  from ..extract.pipeline import extract_dir, extract_zip, get_session_encoding
16
16
 
17
17
 
18
+ _SAVE_FORMATS = ("feather", "csv", "pickle")
19
+ _PREF_CONFIG_KEY = "msense_extraction"
20
+
21
+
22
+ def _saved_save_format():
23
+ from plasma.config import device_config
24
+ blob = device_config.get_plugin_config(_PREF_CONFIG_KEY)
25
+ saved = blob.get("save_format") if isinstance(blob, dict) else None
26
+ return saved if saved in _SAVE_FORMATS else ExtractionOptions().save_format
27
+
28
+
29
+ def _save_save_format(value):
30
+ from plasma.config import device_config
31
+ if value not in _SAVE_FORMATS:
32
+ raise ValueError(f"Unsupported extraction save format: {value}")
33
+ existing = device_config.get_plugin_config(_PREF_CONFIG_KEY)
34
+ blob = dict(existing) if isinstance(existing, dict) else {}
35
+ blob["save_format"] = value
36
+ device_config.update_plugin_config(_PREF_CONFIG_KEY, blob)
37
+
38
+
18
39
  class ExtractionOptionsPanel:
19
40
  """The advanced-options accordion. Build one inside each Blocks context.
20
41
 
@@ -32,8 +53,11 @@ class ExtractionOptionsPanel:
32
53
  with gr.Row():
33
54
  self.legacy_fs = gr.Checkbox(False, label="(Uncommon) legacy sampling rate")
34
55
  self.save_format = gr.Radio(
35
- ["feather", "csv", "pickle"], value="feather", label="Save format",
36
- info="feather (fast, binary) · csv (plain text, needed for clocksync) · pickle")
56
+ _SAVE_FORMATS, value=_saved_save_format(), label="Save format",
57
+ info="feather (fast, binary) · csv (plain text, needed for clocksync) · "
58
+ "pickle · selection saved for future launches")
59
+ self.save_format.change(_save_save_format, inputs=self.save_format,
60
+ outputs=None)
37
61
  self.ignore_id_parsing = gr.Checkbox(False, label="Ignore subject/session ID parsing")
38
62
  with gr.Row():
39
63
  self.ppg_format = gr.Dropdown(PPG_FORMAT_CHOICES, value="auto",
@@ -1,10 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plasma-app
3
- Version: 2.2.2
3
+ Version: 2.2.4
4
4
  Summary: PLASMA — Platform for LSL-based Acquisition of Sensor Metrics and Analytics
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/YuyiChang/PLASMA
7
7
  Project-URL: Repository, https://github.com/YuyiChang/PLASMA
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
8
13
  Requires-Python: >=3.10
9
14
  Description-Content-Type: text/markdown
10
15
  License-File: LICENSE
@@ -54,10 +59,30 @@ PLASMA: Platform for LSL-based Acquisition of Sensor Metrics and Analytics
54
59
  configure, pair MSense wristbands, run a session, read the memo panel, recover
55
60
  from faults.
56
61
 
62
+ [![PyPI version](https://img.shields.io/pypi/v/plasma-app.svg)](https://pypi.org/project/plasma-app/)
63
+ [![Python](https://img.shields.io/pypi/pyversions/plasma-app.svg)](https://pypi.org/project/plasma-app/)
64
+ [![License](https://img.shields.io/github/license/yuyichang/PLASMA.svg)](LICENSE)
65
+ [![GitHub Release](https://img.shields.io/github/v/release/yuyichang/PLASMA)](https://github.com/yuyichang/PLASMA/releases/latest)
66
+
67
+
68
+ ## Download
69
+
70
+ | Platform | Architecture | Download |
71
+ |---|---|---|
72
+ | macOS | Apple Silicon (arm64) | [Download `.dmg`](https://github.com/YuyiChang/PLASMA/releases/latest/download/PLASMA_MacOS_arm64.dmg) |
73
+ | Windows | x64 | [Download Installer `.exe`](https://github.com/YuyiChang/PLASMA/releases/latest/download/PLASMA_Windows_x64_Setup.exe) |
74
+ | Linux | x64 | [Download `.tar.gz`](https://github.com/YuyiChang/PLASMA/releases/latest/download/PLASMA_Linux_x64.tar.gz) |
75
+ | Linux | ARM64 | [Download `.tar.gz`](https://github.com/YuyiChang/PLASMA/releases/latest/download/PLASMA_Linux_arm64.tar.gz) |
76
+
77
+ > For other packages and previous versions, see [GitHub Releases](https://github.com/YuyiChang/PLASMA/releases).
78
+
79
+
57
80
  ## quickstart
58
81
 
59
82
  **macOS: `brew install --cask yuyichang/plasma/plasma`** is the recommended
60
- way to get PLASMA — no Python, no `liblsl`, no Gatekeeper dance. See
83
+ way to get PLASMA — no Python, no `liblsl`, no Gatekeeper dance. **Windows:
84
+ `scoop bucket add plasma https://github.com/YuyiChang/scoop-plasma` then
85
+ `scoop install plasma`** does the same, without the SmartScreen warning. See
61
86
  [Install & first launch](https://yuyichang.github.io/PLASMA/playbook/install/)
62
87
  for that and every other install path (prebuilt binaries, PyPI, from source).
63
88
 
@@ -101,7 +126,7 @@ plugins: `msense`, `qb2`, `pupil`, `shimmer`, `obs` (and `all`).
101
126
  - [x] linux aarch64 build — `PLASMA_Linux_arm64` for Jetson Orin (JetPack 6); CI job `build-linux-arm64`
102
127
  - [ ] infrequent stream (e.g., PLASMA journaler) shows green after recording stop
103
128
  - [ ] doc for msense workflow with better screenshots
104
- - [ ] memo `stage` transition logic optimization (e.g., timing of "ready to start" msg wrt sensor init status)
129
+ - [x] memo `stage` transition logic optimization (e.g., timing of "ready to start" msg wrt sensor init status)
105
130
 
106
131
  ## developing a sensor plugin
107
132
 
@@ -15,6 +15,13 @@ dependencies = [
15
15
  "pylsl",
16
16
  "numpy",
17
17
  ]
18
+ classifiers = [
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ ]
18
25
 
19
26
  [project.urls]
20
27
  Homepage = "https://github.com/YuyiChang/PLASMA"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes