From 69f3df89214cfacd84abe87444d07f9edf3f7ca6 Mon Sep 17 00:00:00 2001 From: tyler Date: Fri, 1 May 2026 11:40:34 -0700 Subject: [PATCH] imptovements for blazes and fix fold again --- flares.py | 2 +- main.py | 149 +++++++++++++++--------------------------------------- 2 files changed, 41 insertions(+), 110 deletions(-) diff --git a/flares.py b/flares.py index a7762e7..bab68ef 100644 --- a/flares.py +++ b/flares.py @@ -1743,7 +1743,7 @@ def fold_channels(raw: BaseRaw) -> None: # Locate the fOLD excel files if getattr(sys, 'frozen', False): - set_config('MNE_NIRS_FOLD_PATH', resource_path("/mne_data/fOLD/fOLD-public-master/Supplementary")) # type: ignore + set_config('MNE_NIRS_FOLD_PATH', resource_path("./mne_data/fOLD/fOLD-public-master/Supplementary")) # type: ignore else: path = os.path.expanduser("~") + "/mne_data/fOLD/fOLD-public-master/Supplementary" set_config('MNE_NIRS_FOLD_PATH', resource_path(path)) # type: ignore diff --git a/main.py b/main.py index 4730763..740ba9f 100644 --- a/main.py +++ b/main.py @@ -46,7 +46,7 @@ from PySide6.QtGui import QAction, QKeySequence, QIcon, QIntValidator, QDoubleVa from PySide6.QtSvgWidgets import QSvgWidget # needed to show svgs when app is not frozen -CURRENT_VERSION = "1.4.1" +CURRENT_VERSION = "1.4.3" APP_NAME = "flares" API_URL = f"https://git.research.dezeeuw.ca/api/v1/repos/tyler/{APP_NAME}/releases" API_URL_SECONDARY = f"https://git.research2.dezeeuw.ca/api/v1/repos/tyler/{APP_NAME}/releases" @@ -1413,44 +1413,44 @@ class UpdateEventsBlazesWindow(QWidget): self.combo_snirf_events.setEnabled(False) def browse_file_b(self): - file_path, _ = QFileDialog.getOpenFileName(self, "Select BLAZES File", "", "BLAZES project Files (*.blaze)") + file_path, _ = QFileDialog.getOpenFileName(self, "Select JSON Timeline File", "", "JSON Files (*.json)") if file_path: self.line_edit_file_b.setText(file_path) try: with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) - self.blazes_data = data + self.json_data = data - obs_keys = self.extract_blazes_observation_strings(data) + obs_keys = self.extract_json_observation_strings(data) self.combo_events.clear() if obs_keys: self.combo_events.addItems(obs_keys) self.combo_events.setEnabled(True) else: - QMessageBox.information(self, "No Events", "No observation keys found in BLAZES file.") + QMessageBox.information(self, "No Events", "No events found in JSON file.") self.combo_events.setEnabled(False) except (json.JSONDecodeError, FileNotFoundError, KeyError, TypeError) as e: - QMessageBox.warning(self, "Error", f"Failed to parse BLAZES file:\n{e}") + QMessageBox.warning(self, "Error", f"Failed to parse JSON file:\n{e}") self.combo_events.clear() self.combo_events.setEnabled(False) - def extract_blazes_observation_strings(self, data): - if "obs" not in data: - raise KeyError("Missing 'obs' key in BLAZES file.") + def extract_json_observation_strings(self, data): + if "events" not in data: + raise KeyError("Missing 'events' key in JSON file.") - obs = data["obs"] event_strings = [] - for event_name, occurrences in obs.items(): - # occurrences is a list of dicts: [{"start_frame": 642, "start_time_sec": 26.777, ...}] - for entry in occurrences: - onset = entry.get("start_time_sec", 0.0) - # Formatting to match your SNIRF style: "Event Name @ 0.000s" - display_str = f"{event_name} @ {onset:.3f}s" - event_strings.append(display_str) + # The new format is a flat list chronologically ordered + for event in data["events"]: + track_name = event.get("track_name", "Unknown") + onset = event.get("start_sec", 0.0) + + # Formatting to match your SNIRF style: "Event Name @ 0.000s" + display_str = f"{track_name} @ {onset:.3f}s" + event_strings.append(display_str) return event_strings @@ -1465,16 +1465,16 @@ class UpdateEventsBlazesWindow(QWidget): file_b = self.line_edit_file_b.text() suffix = APP_NAME - if not hasattr(self, "blazes_data") or self.combo_events.count() == 0 or self.combo_snirf_events.count() == 0: - QMessageBox.warning(self, "Missing data", "Please make sure a BLAZES and SNIRF event are selected.") + if not hasattr(self, "json_data") or self.combo_events.count() == 0 or self.combo_snirf_events.count() == 0: + QMessageBox.warning(self, "Missing data", "Please make sure a JSON and SNIRF event are selected.") return try: - blaze_text = self.combo_events.currentText() - _, blaze_time_str = blaze_text.split(" @ ") - blaze_anchor_time = float(blaze_time_str.replace("s", "").strip()) + json_text = self.combo_events.currentText() + _, json_time_str = json_text.split(" @ ") + json_anchor_time = float(json_time_str.replace("s", "").strip()) except Exception as e: - QMessageBox.critical(self, "BLAZE Event Error", f"Could not parse BLAZE anchor:\n{e}") + QMessageBox.critical(self, "JSON Event Error", f"Could not parse JSON anchor:\n{e}") return try: @@ -1485,33 +1485,33 @@ class UpdateEventsBlazesWindow(QWidget): QMessageBox.critical(self, "SNIRF Event Error", f"Could not parse SNIRF anchor:\n{e}") return - time_shift = snirf_anchor_time - blaze_anchor_time + time_shift = snirf_anchor_time - json_anchor_time onsets, durations, descriptions = [], [], [] skipped_count = 0 try: - ai_data = self.blazes_data.get("ai_tracks", {}) + events_list = self.json_data.get("events", []) - for track_name, events in ai_data.items(): + for event in events_list: + track_name = event.get("track_name", "Unknown") clean_name = track_name.replace("AI: ", "").strip() - for event in events: - original_start = event.get("start_time_sec", 0.0) - original_end = event.get("end_time_sec", original_start) - duration = original_end - original_start + original_start = event.get("start_sec", 0.0) + original_end = event.get("end_sec", original_start) + duration = original_end - original_start - # FILTER: Minimum 0.1s duration - if duration < 0.1: - skipped_count += 1 - continue + # FILTER: Minimum 0.1s duration + if duration < 0.1: + skipped_count += 1 + continue - # Apply shift - adjusted_onset = original_start + time_shift + # Apply shift + adjusted_onset = original_start + time_shift - onsets.append(round(adjusted_onset, 6)) - durations.append(round(duration, 6)) - descriptions.append(clean_name) + onsets.append(round(adjusted_onset, 6)) + durations.append(round(duration, 6)) + descriptions.append(clean_name) except Exception as e: QMessageBox.critical(self, "Track Error", f"Failed to process tracks: {e}") @@ -1538,7 +1538,7 @@ class UpdateEventsBlazesWindow(QWidget): description=descriptions ) - # Replace existing annotations with the new aligned AI tracks + # Replace existing annotations with the new aligned JSON tracks raw.set_annotations(new_annotations) write_raw_snirf(raw, save_path) @@ -1548,75 +1548,6 @@ class UpdateEventsBlazesWindow(QWidget): QMessageBox.critical(self, "Error", f"Failed to update SNIRF file:\n{e}") - def update_optode_positions(self, file_a, file_b, save_path): - - fiducials = {} - ch_positions = {} - - # Read the lines from the optode file - with open(file_b, 'r') as f: - for line in f: - if line.strip(): - # Split by the semicolon and convert to meters - ch_name, coords_str = line.split(":") - coords = np.array(list(map(float, coords_str.strip().split()))) * 0.001 - - # The key we have is a fiducial - if ch_name.lower() in ['lpa', 'nz', 'rpa']: - fiducials[ch_name.lower()] = coords - - # The key we have is a source or detector - else: - ch_positions[ch_name.upper()] = coords - - # Create montage with updated coords in head space - initial_montage = make_dig_montage(ch_pos=ch_positions, nasion=fiducials.get('nz'), lpa=fiducials.get('lpa'), rpa=fiducials.get('rpa'), coord_frame='head') # type: ignore - - # Read the SNIRF file, set the montage, and write it back - # TODO: Bad! read_raw_snirf doesnt release memory properly! Should be spawned in a seperate process and killed once completed - raw = read_raw_snirf(file_a, preload=True) - raw.set_montage(initial_montage) - write_raw_snirf(raw, save_path) - - - def _apply_events_to_snirf(self, raw, new_annotations, save_path): - raw.set_annotations(new_annotations) - write_raw_snirf(raw, save_path) - - def _write_event_mapping_json( - self, - file_a, - file_b, - selected_obs, - snirf_anchor, - boris_anchor, - time_shift, - mapped_events, - save_path - ): - - payload = { - "source": { - "called_from": self.caller, - "snirf_file": os.path.basename(file_a), - "boris_file": os.path.basename(file_b), - "observation": selected_obs - }, - "alignment": { - "snirf_anchor": snirf_anchor, - "boris_anchor": boris_anchor, - "time_shift_seconds": time_shift - }, - "events": mapped_events, - "created_at": datetime.utcnow().isoformat() + "Z" - } - - with open(save_path, "w", encoding="utf-8") as f: - json.dump(payload, f, indent=2) - - return save_path - - class ProgressBubble(QWidget): """