Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 45c6176dba | |||
| a4bbdb90c8 | |||
| 953ea90c67 | |||
| 20b255321b | |||
| b5afcec37d | |||
| 5361f6ea21 | |||
| ee023c26c1 | |||
| 06c9ff0ecf | |||
| 542dd85a78 |
38
changelog.md
38
changelog.md
@@ -1,3 +1,40 @@
|
|||||||
|
# Version 1.1.6
|
||||||
|
|
||||||
|
- Fixed Process button from appearing when no files are selected
|
||||||
|
- Fix for instand child process crash on Windows
|
||||||
|
- Added L_FREQ and H_FREQ parameters for more user control over low and high pass filtering
|
||||||
|
|
||||||
|
|
||||||
|
# Version 1.1.5
|
||||||
|
|
||||||
|
- Fixed Windows saves not being able to be opened by a Mac (hopefully the other way too!)
|
||||||
|
- Added the option to right click loaded snirf files to reveal them in a file browser or delete them if they are no longer desired
|
||||||
|
- Changed the way folders are opened to store the files seperately rather than the folder as a whole to allow for the removal of files
|
||||||
|
- Fixed issues with dropdowns and bubbles not populating correctly when opening a single file and temporarily removed the option to open multiple folders
|
||||||
|
- Improved crash handling and the message that is displayed to the user if the application crashes
|
||||||
|
- Progress bar will now colour the stage that fails as red if a file fails during processing
|
||||||
|
- A warning message will be displayed when a file fails to process with information on what went wrong. This message does not halt the rest of the processing of the other files
|
||||||
|
- Fixed the number of rectangles in the progress bar to 20 (was incorrect in v1.1.1)
|
||||||
|
- Added validation to ensure loaded files do not have 2 dimensional data when clicking process to prevent inaccurate results from being generated
|
||||||
|
- Added more metadata information to the top left information panel
|
||||||
|
- Changed the Status Bar message when processing is complete to state how many were successful and how many were not
|
||||||
|
- Added a clickable link below the selected file's metadata explaining the independent parameters and why they are useful
|
||||||
|
- Updated some tooltips to provide better, more accurate information
|
||||||
|
- Added details about the processing steps and their order into the user guide
|
||||||
|
- Changed the default bandpass filtering parameters
|
||||||
|
|
||||||
|
|
||||||
|
# Version 1.1.4
|
||||||
|
|
||||||
|
- Fixed some display text to now display the correct information
|
||||||
|
- A new option under Analysis has been added to export the data from a specified participant as a csv file. Fixes [Issue 19](https://git.research.dezeeuw.ca/tyler/flares/issues/19), [Issue 27](https://git.research.dezeeuw.ca/tyler/flares/issues/27)
|
||||||
|
- Added 2 new parameters - TIME_WINDOW_START and TIME_WINDOW_END. Fixes [Issue 29](https://git.research.dezeeuw.ca/tyler/flares/issues/29)
|
||||||
|
- These parameters affect the visualization of the significance and contrast images but do not change the total time modeled underneath
|
||||||
|
- Fixed the duration of annotations edited from a BORIS file from 0 seconds to their proper duration
|
||||||
|
- Added the annotation information to each participant under their "File information" window
|
||||||
|
- Fixed Macs not being able to save snirfs attempting to be updated from BORIS files, and in general the updated files not respecting the path chosen by the user
|
||||||
|
|
||||||
|
|
||||||
# Version 1.1.3
|
# Version 1.1.3
|
||||||
|
|
||||||
- Added back the ability to use the fOLD dataset. Fixes [Issue 23](https://git.research.dezeeuw.ca/tyler/flares/issues/23)
|
- Added back the ability to use the fOLD dataset. Fixes [Issue 23](https://git.research.dezeeuw.ca/tyler/flares/issues/23)
|
||||||
@@ -9,6 +46,7 @@
|
|||||||
- Added a terminal to interact with the app in a more command-like form
|
- Added a terminal to interact with the app in a more command-like form
|
||||||
- Currently the terminal has no functionality but some features for batch operations will be coming soon!
|
- Currently the terminal has no functionality but some features for batch operations will be coming soon!
|
||||||
- Inter-Group viewer now has the option to visualize the average response on the brain of all participants in the group. Fixes [Issue 26](https://git.research.dezeeuw.ca/tyler/flares/issues/24)
|
- Inter-Group viewer now has the option to visualize the average response on the brain of all participants in the group. Fixes [Issue 26](https://git.research.dezeeuw.ca/tyler/flares/issues/24)
|
||||||
|
- Fixed the description under "Update events in snirf file..."
|
||||||
|
|
||||||
|
|
||||||
# Version 1.1.2
|
# Version 1.1.2
|
||||||
|
|||||||
142
flares.py
142
flares.py
@@ -50,8 +50,8 @@ from scipy.spatial.distance import cdist
|
|||||||
|
|
||||||
# Backen visualization needed to be defined for pyinstaller
|
# Backen visualization needed to be defined for pyinstaller
|
||||||
import pyvistaqt # type: ignore
|
import pyvistaqt # type: ignore
|
||||||
# import vtkmodules.util.data_model
|
import vtkmodules.util.data_model
|
||||||
# import vtkmodules.util.execution_model
|
import vtkmodules.util.execution_model
|
||||||
|
|
||||||
# External library imports for mne
|
# External library imports for mne
|
||||||
from mne import (
|
from mne import (
|
||||||
@@ -130,10 +130,16 @@ TDDR: bool
|
|||||||
|
|
||||||
ENHANCE_NEGATIVE_CORRELATION: bool
|
ENHANCE_NEGATIVE_CORRELATION: bool
|
||||||
|
|
||||||
|
L_FREQ: float
|
||||||
|
H_FREQ: float
|
||||||
|
|
||||||
SHORT_CHANNEL: bool
|
SHORT_CHANNEL: bool
|
||||||
|
|
||||||
REMOVE_EVENTS: list
|
REMOVE_EVENTS: list
|
||||||
|
|
||||||
|
TIME_WINDOW_START: int
|
||||||
|
TIME_WINDOW_END: int
|
||||||
|
|
||||||
VERBOSITY = True
|
VERBOSITY = True
|
||||||
|
|
||||||
# FIXME: Shouldn't need each ordering - just order it before checking
|
# FIXME: Shouldn't need each ordering - just order it before checking
|
||||||
@@ -182,6 +188,10 @@ REQUIRED_KEYS: dict[str, Any] = {
|
|||||||
|
|
||||||
"SHORT_CHANNEL": bool,
|
"SHORT_CHANNEL": bool,
|
||||||
"REMOVE_EVENTS": list,
|
"REMOVE_EVENTS": list,
|
||||||
|
"TIME_WINDOW_START": int,
|
||||||
|
"TIME_WINDOW_END": int,
|
||||||
|
"L_FREQ": float,
|
||||||
|
"H_FREQ": float,
|
||||||
# "REJECT_PAIRS": bool,
|
# "REJECT_PAIRS": bool,
|
||||||
# "FORCE_DROP_ANNOTATIONS": list,
|
# "FORCE_DROP_ANNOTATIONS": list,
|
||||||
# "FILTER_LOW_PASS": float,
|
# "FILTER_LOW_PASS": float,
|
||||||
@@ -263,40 +273,42 @@ def set_metadata(file_path, metadata: dict[str, Any]) -> None:
|
|||||||
val = file_metadata.get(key, None)
|
val = file_metadata.get(key, None)
|
||||||
if val not in (None, '', [], {}, ()): # check for "empty" values
|
if val not in (None, '', [], {}, ()): # check for "empty" values
|
||||||
globals()[key] = val
|
globals()[key] = val
|
||||||
|
from queue import Empty # This works with multiprocessing.Manager().Queue()
|
||||||
|
|
||||||
|
|
||||||
def gui_entry(config: dict[str, Any], gui_queue: Queue, progress_queue: Queue) -> None:
|
def gui_entry(config: dict[str, Any], gui_queue: Queue, progress_queue: Queue) -> None:
|
||||||
try:
|
def forward_progress():
|
||||||
# Start a thread to forward progress messages back to GUI
|
while True:
|
||||||
def forward_progress():
|
try:
|
||||||
while True:
|
msg = progress_queue.get(timeout=1)
|
||||||
try:
|
if msg == "__done__":
|
||||||
msg = progress_queue.get(timeout=1)
|
break
|
||||||
if msg == "__done__":
|
gui_queue.put(msg)
|
||||||
break
|
except Empty:
|
||||||
gui_queue.put(msg)
|
continue
|
||||||
except:
|
except Exception as e:
|
||||||
continue
|
gui_queue.put({
|
||||||
|
"type": "error",
|
||||||
|
"error": f"Forwarding thread crashed: {e}",
|
||||||
|
"traceback": traceback.format_exc()
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
t = threading.Thread(target=forward_progress, daemon=True)
|
t = threading.Thread(target=forward_progress, daemon=True)
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
|
try:
|
||||||
file_paths = config['SNIRF_FILES']
|
file_paths = config['SNIRF_FILES']
|
||||||
file_params = config['PARAMS']
|
file_params = config['PARAMS']
|
||||||
file_metadata = config['METADATA']
|
file_metadata = config['METADATA']
|
||||||
|
|
||||||
max_workers = file_params.get("MAX_WORKERS", int(os.cpu_count()/4))
|
max_workers = file_params.get("MAX_WORKERS", int(os.cpu_count()/4))
|
||||||
|
|
||||||
# Run the actual processing, with progress_queue passed down
|
results = process_multiple_participants(
|
||||||
print("actual call")
|
file_paths, file_params, file_metadata, progress_queue, max_workers
|
||||||
results = process_multiple_participants(file_paths, file_params, file_metadata, progress_queue, max_workers)
|
)
|
||||||
|
|
||||||
# Signal end of progress
|
|
||||||
progress_queue.put("__done__")
|
|
||||||
t.join()
|
|
||||||
|
|
||||||
gui_queue.put({"success": True, "result": results})
|
gui_queue.put({"success": True, "result": results})
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
gui_queue.put({
|
gui_queue.put({
|
||||||
"success": False,
|
"success": False,
|
||||||
@@ -304,6 +316,14 @@ def gui_entry(config: dict[str, Any], gui_queue: Queue, progress_queue: Queue) -
|
|||||||
"traceback": traceback.format_exc()
|
"traceback": traceback.format_exc()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Always send done to the thread and avoid hanging
|
||||||
|
try:
|
||||||
|
progress_queue.put("__done__")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
t.join(timeout=5) # prevent permanent hang
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def process_participant_worker(args):
|
def process_participant_worker(args):
|
||||||
@@ -338,9 +358,16 @@ def process_multiple_participants(file_paths, file_params, file_metadata, progre
|
|||||||
try:
|
try:
|
||||||
file_path, result, error = future.result()
|
file_path, result, error = future.result()
|
||||||
if error:
|
if error:
|
||||||
print(f"Error processing {file_path}: {error[0]}")
|
error_message, error_traceback = error
|
||||||
print(error[1])
|
if progress_queue:
|
||||||
|
progress_queue.put({
|
||||||
|
"type": "error",
|
||||||
|
"file": file_path,
|
||||||
|
"error": error_message,
|
||||||
|
"traceback": error_traceback
|
||||||
|
})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
results_by_file[file_path] = result
|
results_by_file[file_path] = result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Unexpected error processing {file_path}: {e}")
|
print(f"Unexpected error processing {file_path}: {e}")
|
||||||
@@ -1048,7 +1075,16 @@ def filter_the_data(raw_haemo):
|
|||||||
average=True, xscale="log", color="r", show=False, amplitude=False
|
average=True, xscale="log", color="r", show=False, amplitude=False
|
||||||
)
|
)
|
||||||
|
|
||||||
raw_haemo = raw_haemo.filter(l_freq=None, h_freq=0.4, h_trans_bandwidth=0.2)
|
if L_FREQ == 0 and H_FREQ != 0:
|
||||||
|
raw_haemo = raw_haemo.filter(l_freq=None, h_freq=H_FREQ, h_trans_bandwidth=0.02)
|
||||||
|
elif L_FREQ != 0 and H_FREQ == 0:
|
||||||
|
raw_haemo = raw_haemo.filter(l_freq=L_FREQ, h_freq=None, l_trans_bandwidth=0.002)
|
||||||
|
elif L_FREQ != 0 and H_FREQ == 0:
|
||||||
|
raw_haemo = raw_haemo.filter(l_freq=L_FREQ, h_freq=H_FREQ, l_trans_bandwidth=0.002, h_trans_bandwidth=0.02)
|
||||||
|
|
||||||
|
#raw_haemo = raw_haemo.filter(l_freq=None, h_freq=0.4, h_trans_bandwidth=0.2)
|
||||||
|
#raw_haemo = raw_haemo.filter(l_freq=None, h_freq=0.7, h_trans_bandwidth=0.2)
|
||||||
|
#raw_haemo = raw_haemo.filter(0.005, 0.7, h_trans_bandwidth=0.02, l_trans_bandwidth=0.002)
|
||||||
|
|
||||||
raw_haemo.compute_psd(fmax=2).plot(
|
raw_haemo.compute_psd(fmax=2).plot(
|
||||||
average=True, xscale="log", axes=fig_filter.axes, color="g", amplitude=False, show=False
|
average=True, xscale="log", axes=fig_filter.axes, color="g", amplitude=False, show=False
|
||||||
@@ -1494,7 +1530,6 @@ def fold_channels(raw: BaseRaw) -> None:
|
|||||||
# Format the output to make it slightly easier to read
|
# Format the output to make it slightly easier to read
|
||||||
|
|
||||||
if True:
|
if True:
|
||||||
|
|
||||||
num_channels = len(hbo_channel_names)
|
num_channels = len(hbo_channel_names)
|
||||||
rows, cols = 4, 7 # 6 rows and 4 columns of pie charts
|
rows, cols = 4, 7 # 6 rows and 4 columns of pie charts
|
||||||
fig, axes = plt.subplots(rows, cols, figsize=(16, 10), constrained_layout=True)
|
fig, axes = plt.subplots(rows, cols, figsize=(16, 10), constrained_layout=True)
|
||||||
@@ -2254,21 +2289,25 @@ def brain_landmarks_3d(raw_haemo: BaseRaw, show_optodes: Literal['sensors', 'lab
|
|||||||
|
|
||||||
|
|
||||||
if show_brodmann:# Add Brodmann labels
|
if show_brodmann:# Add Brodmann labels
|
||||||
labels = cast(list[Label], read_labels_from_annot("fsaverage", "PALS_B12_Brodmann", "rh", verbose=False)) # type: ignore
|
labels = cast(list[Label], read_labels_from_annot("fsaverage", "PALS_B12_Brodmann", "lh", verbose=False)) # type: ignore
|
||||||
|
|
||||||
label_colors = {
|
label_colors = {
|
||||||
"Brodmann.39-rh": "blue",
|
"Brodmann.1-lh": "red",
|
||||||
"Brodmann.40-rh": "green",
|
"Brodmann.2-lh": "red",
|
||||||
"Brodmann.6-rh": "pink",
|
"Brodmann.3-lh": "red",
|
||||||
"Brodmann.7-rh": "orange",
|
"Brodmann.4-lh": "orange",
|
||||||
"Brodmann.17-rh": "red",
|
"Brodmann.5-lh": "green",
|
||||||
"Brodmann.1-rh": "yellow",
|
"Brodmann.6-lh": "yellow",
|
||||||
"Brodmann.2-rh": "yellow",
|
"Brodmann.7-lh": "green",
|
||||||
"Brodmann.3-rh": "yellow",
|
"Brodmann.17-lh": "blue",
|
||||||
"Brodmann.18-rh": "red",
|
"Brodmann.18-lh": "blue",
|
||||||
"Brodmann.19-rh": "red",
|
"Brodmann.19-lh": "blue",
|
||||||
"Brodmann.4-rh": "purple",
|
"Brodmann.39-lh": "purple",
|
||||||
"Brodmann.8-rh": "white"
|
"Brodmann.40-lh": "pink",
|
||||||
|
"Brodmann.42-lh": "white",
|
||||||
|
"Brodmann.44-lh": "white",
|
||||||
|
"Brodmann.48-lh": "white",
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for label in labels:
|
for label in labels:
|
||||||
@@ -2805,7 +2844,7 @@ def calculate_dpf(file_path):
|
|||||||
# order is hbo / hbr
|
# order is hbo / hbr
|
||||||
with h5py.File(file_path, 'r') as f:
|
with h5py.File(file_path, 'r') as f:
|
||||||
wavelengths = f['/nirs/probe/wavelengths'][:]
|
wavelengths = f['/nirs/probe/wavelengths'][:]
|
||||||
logger.info("Wavelengths (nm):", wavelengths)
|
logger.info(f"Wavelengths (nm): {wavelengths}")
|
||||||
wavelengths = sorted(wavelengths, reverse=True)
|
wavelengths = sorted(wavelengths, reverse=True)
|
||||||
age = float(AGE)
|
age = float(AGE)
|
||||||
logger.info(f"Their age was {AGE}")
|
logger.info(f"Their age was {AGE}")
|
||||||
@@ -2921,7 +2960,7 @@ def process_participant(file_path, progress_callback=None):
|
|||||||
|
|
||||||
# Step 11: Get short / long channels
|
# Step 11: Get short / long channels
|
||||||
if SHORT_CHANNEL:
|
if SHORT_CHANNEL:
|
||||||
short_chans = get_short_channels(raw_haemo, max_dist=0.015)
|
short_chans = get_short_channels(raw_haemo, max_dist=0.02)
|
||||||
fig_short_chans = short_chans.plot(duration=raw_haemo.times[-1], n_channels=raw_haemo.info['nchan'], title="Short Channels Only", show=False)
|
fig_short_chans = short_chans.plot(duration=raw_haemo.times[-1], n_channels=raw_haemo.info['nchan'], title="Short Channels Only", show=False)
|
||||||
fig_individual["short"] = fig_short_chans
|
fig_individual["short"] = fig_short_chans
|
||||||
else:
|
else:
|
||||||
@@ -3031,7 +3070,11 @@ def process_participant(file_path, progress_callback=None):
|
|||||||
contrast_dict = {}
|
contrast_dict = {}
|
||||||
|
|
||||||
for condition in all_conditions:
|
for condition in all_conditions:
|
||||||
delay_cols = [col for col in all_delay_cols if col.startswith(f"{condition}_delay_")]
|
delay_cols = [
|
||||||
|
col for col in all_delay_cols
|
||||||
|
if col.startswith(f"{condition}_delay_") and
|
||||||
|
TIME_WINDOW_START <= int(col.split("_delay_")[-1]) <= TIME_WINDOW_END
|
||||||
|
]
|
||||||
|
|
||||||
if not delay_cols:
|
if not delay_cols:
|
||||||
continue # skip if no columns found (shouldn't happen?)
|
continue # skip if no columns found (shouldn't happen?)
|
||||||
@@ -3061,4 +3104,15 @@ def process_participant(file_path, progress_callback=None):
|
|||||||
if progress_callback: progress_callback(20)
|
if progress_callback: progress_callback(20)
|
||||||
logger.info("20")
|
logger.info("20")
|
||||||
|
|
||||||
|
sanitize_paths_for_pickle(raw_haemo, epochs)
|
||||||
|
|
||||||
return raw_haemo, epochs, fig_bytes, cha, contrast_results, df_ind, design_matrix, AGE, GENDER, GROUP, True
|
return raw_haemo, epochs, fig_bytes, cha, contrast_results, df_ind, design_matrix, AGE, GENDER, GROUP, True
|
||||||
|
|
||||||
|
def sanitize_paths_for_pickle(raw_haemo, epochs):
|
||||||
|
# Fix raw_haemo._filenames
|
||||||
|
if hasattr(raw_haemo, '_filenames'):
|
||||||
|
raw_haemo._filenames = [str(p) for p in raw_haemo._filenames]
|
||||||
|
|
||||||
|
# Fix epochs._raw._filenames
|
||||||
|
if hasattr(epochs, '_raw') and hasattr(epochs._raw, '_filenames'):
|
||||||
|
epochs._raw._filenames = [str(p) for p in epochs._raw._filenames]
|
||||||
1
icons/folder_eye_24dp_1F1F1F.svg
Normal file
1
icons/folder_eye_24dp_1F1F1F.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h240l80 80h320q33 0 56.5 23.5T880-640v242q-18-14-38-23t-42-19v-200H447l-80-80H160v480h120v80H160ZM640-40q-91 0-168-48T360-220q35-84 112-132t168-48q91 0 168 48t112 132q-35 84-112 132T640-40Zm0-80q57 0 107.5-26t82.5-74q-32-48-82.5-74T640-320q-57 0-107.5 26T450-220q32 48 82.5 74T640-120Zm0-40q-25 0-42.5-17.5T580-220q0-25 17.5-42.5T640-280q25 0 42.5 17.5T700-220q0 25-17.5 42.5T640-160Zm-480-80v-480 277-37 240Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 593 B |
1
icons/remove_24dp_1F1F1F.svg
Normal file
1
icons/remove_24dp_1F1F1F.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M200-440v-80h560v80H200Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 149 B |
Reference in New Issue
Block a user