massive changes for 1.5.0

This commit is contained in:
2026-06-28 08:17:36 -07:00
parent 57b7082564
commit 766bf75dd7
11 changed files with 1603 additions and 261 deletions
+1
View File
@@ -181,3 +181,4 @@ cython_debug/
*.json *.json
flares-* flares-*
*.flare *.flare
*.cfg
+17
View File
@@ -1,3 +1,20 @@
# Version 1.5.0
- This release introduces a new configuration file that may break existing installs. If your application does not update correctly, please download fresh from [this link.](https://git.research.dezeeuw.ca/tyler/flares/releases/)
- New configuration file has been added! Now your choices of preferences will be saved when the application is closed and re-opened. If the configuration file is missing, a new one will be generated
- The new option "Reset to Default Configuration" will reset the configuration file to it's default values
- Recent files and recent projects are now saved and appear under the File menu for quick resuming
- A welcome dialog will now display the changelog after every update. This popup will only appear once but can be reopened under the Options menu through "Show Update Changelog"
- Changed the hotkey for "Update optodes in snirf file..." to be F9 instead of F6
- Revamped the fOLD channels window. Images containing the pie charts are now interactable! Click whitespace to expand the whole image and click a chart to expand it.
- fOLD progress bar when processing now updates the percentages live. Fixes [Issue 76](https://git.research.dezeeuw.ca/tyler/flares/issues/76)
- Overall pie charts on an individal and global basis are now genereted. Fixes [Issue 78](https://git.research.dezeeuw.ca/tyler/flares/issues/78)
- Brodmann images are now available when examining a pie chart to understand which area is being reported. Fixes [Issue 77](https://git.research.dezeeuw.ca/tyler/flares/issues/77)
- Added a new option 'Folding Bypass' to the Preferences Menu. This skips most processing steps and the only analysis option available will be to fold. Parameters on the right will be ignored. Fixes [Issue 75](https://git.research.dezeeuw.ca/tyler/flares/issues/75)
- Added a feature to hover over the 28 stage progress bar and see which state the progress bar is at. Fixes [Issue 74](https://git.research.dezeeuw.ca/tyler/flares/issues/74)
- Loading a broken snirf file no longer hangs its processing and can now be removed from the list. Fixes [Issue 73](https://git.research.dezeeuw.ca/tyler/flares/issues/73)
# Version 1.4.3 # Version 1.4.3
- Fixed an issue where the fOLD files could not be located - Fixed an issue where the fOLD files could not be located
+207 -164
View File
@@ -213,6 +213,8 @@ AGE: int = 25 # Assume 25 if not set from the GUI. This will result in a reason
GENDER: str = "" GENDER: str = ""
GROUP: str = "Default" GROUP: str = "Default"
FOLDING_BYP: bool = False
# These are parameters that are required for the analysis # These are parameters that are required for the analysis
REQUIRED_KEYS: dict[str, Any] = { REQUIRED_KEYS: dict[str, Any] = {
@@ -1487,7 +1489,7 @@ def make_design_matrix(raw_haemo, short_chans):
pass pass
# 2) Create design matrix # 2) Create design matrix
if SHORT_CHANNEL_REGRESSION: if SHORT_CHANNEL_REGRESSION and not FOLDING_BYP:
design_matrix = make_first_level_design_matrix( design_matrix = make_first_level_design_matrix(
raw=raw_haemo, raw=raw_haemo,
stim_dur=STIM_DUR, stim_dur=STIM_DUR,
@@ -1739,160 +1741,196 @@ def resource_path(relative_path):
def fold_channels(raw: BaseRaw) -> None: # def fold_channels(raw: BaseRaw) -> None:
# Locate the fOLD excel files # # 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
# else:
# path = os.path.expanduser("~") + "/mne_data/fOLD/fOLD-public-master/Supplementary"
# set_config('MNE_NIRS_FOLD_PATH', resource_path(path)) # type: ignore
# output = None
# # List to store the results
# landmark_specificity_data: list[dict[str, Any]] = []
# # Filter the data to only what we want
# hbo_channel_names = cast(list[str], getattr(raw.copy().pick(picks='hbo'), "ch_names")) # type: ignore
# # Format the output to make it slightly easier to read
# if True:
# num_channels = len(hbo_channel_names)
# rows, cols = 4, 7 # 6 rows and 4 columns of pie charts
# fig, axes = plt.subplots(rows, cols, figsize=(16, 10), constrained_layout=True)
# axes = axes.flatten() # Flatten the axes array for easier indexing
# # If more pie charts than subplots, create extra subplots
# if num_channels > rows * cols:
# fig, axes = plt.subplots((num_channels // cols) + 1, cols, figsize=(16, 10), constrained_layout=True)
# axes = axes.flatten()
# # Create a list for consistent color mapping
# landmarks = [
# "1 - Primary Somatosensory Cortex",
# "2 - Primary Somatosensory Cortex",
# "3 - Primary Somatosensory Cortex",
# "4 - Primary Motor Cortex",
# "5 - Somatosensory Association Cortex",
# "6 - Pre-Motor and Supplementary Motor Cortex",
# "7 - Somatosensory Association Cortex",
# "8 - Includes Frontal eye fields",
# "9 - Dorsolateral prefrontal cortex",
# "10 - Frontopolar area",
# "11 - Orbitofrontal area",
# "17 - Primary Visual Cortex (V1)",
# "18 - Visual Association Cortex (V2)",
# "19 - V3",
# "20 - Inferior Temporal gyrus",
# "21 - Middle Temporal gyrus",
# "22 - Superior Temporal Gyrus",
# "23 - Ventral Posterior cingulate cortex",
# "24 - Ventral Anterior cingulate cortex",
# "25 - Subgenual cortex",
# "32 - Dorsal anterior cingulate cortex",
# "37 - Fusiform gyrus",
# "38 - Temporopolar area",
# "39 - Angular gyrus, part of Wernicke's area",
# "40 - Supramarginal gyrus part of Wernicke's area",
# "41 - Primary and Auditory Association Cortex",
# "42 - Primary and Auditory Association Cortex",
# "43 - Subcentral area",
# "44 - pars opercularis, part of Broca's area",
# "45 - pars triangularis Broca's area",
# "46 - Dorsolateral prefrontal cortex",
# "47 - Inferior prefrontal gyrus",
# "48 - Retrosubicular area",
# "Brain_Outside",
# ]
# cmap1 = plt.get_cmap('tab20') # First 20 colors
# cmap2 = plt.get_cmap('tab20b') # Next 20 colors
# # Combine the colors from both colormaps
# colors = [cmap1(i) for i in range(20)] + [cmap2(i) for i in range(20)] # Total 40 colors
# landmarks.sort(key=lambda x: (int(x.split(" - ")[0]) if x.split(" - ")[0].isdigit() else float('inf')))
# landmark_color_map = {landmark: colors[i % len(colors)] for i, landmark in enumerate(landmarks)}
# # Iterate over each channel
# print(len(hbo_channel_names))
# for idx, channel_name in enumerate(hbo_channel_names):
# print(idx, channel_name)
# # Run the fOLD on the selected channel
# channel_data = raw.copy().pick(picks=channel_name) # type: ignore
# output = cast(list[DataFrame], fold_channel_specificity_normal(channel_data, interpolate=True, atlas='Brodmann'))
# # Process each DataFrame that fold_channel_specificity returns
# for df_data in output:
# # Extract the relevant columns
# useful_data = df_data[['Landmark', 'Specificity']]
# # Store the results
# landmark_specificity_data.append({
# 'Channel': channel_name,
# 'Data': useful_data,
# })
# # Plot the results
# # TODO: Fix this
# if True:
# unique_landmarks = sorted(useful_data['Landmark'].unique())
# color_list = [landmark_color_map[landmark] for landmark in useful_data['Landmark']]
# # Plot specificity for each channel
# ax = axes[idx]
# labels = [f'{landmark.split(" - ")[0]}' if landmark != 'Brain_Outside' else 'B' for landmark in useful_data['Landmark']]
# wedges, texts, autotexts = ax.pie(
# useful_data['Specificity'],
# autopct='%1.1f%%',
# startangle=90,
# labels=labels,
# labeldistance=1.05,
# colors=color_list)
# ax.set_title(f'{channel_name}')
# ax.axis('equal')
# landmark_specificity_data = []
# # TODO: Fix this
# if True:
# handles = [
# plt.Line2D([0], [0], marker='o', color='w', label=landmark, markersize=10,
# markerfacecolor=landmark_color_map[landmark])
# for landmark in landmarks
# ]
# n_landmarks = len(landmarks)
# # Calculate the figure size based on number of rows and columns
# fig_width = 5
# fig_height = n_landmarks / 4
# # Create a new figure window for the legend
# legend_fig = plt.figure(figsize=(fig_width, fig_height))
# legend_axes = legend_fig.add_subplot(111)
# legend_axes.axis('off') # Turn off axis for the legend window
# legend_axes.legend(handles=handles, loc='center', fontsize=10, title="Landmarks")
# for ax in axes[len(hbo_channel_names):]:
# ax.axis('off')
# #plt.show()
# fig_dict = {"main": fig, "legend": legend_fig}
# return convert_fig_dict_to_png_bytes(fig_dict)
def fold_channels(raw: BaseRaw, p_name: str, progress_queue=None) -> dict[str, list[dict[str, Any]]]:
"""Runs in background process.
Does only heavy math/lookup. Returns data instead of a static image.
"""
if getattr(sys, 'frozen', False): 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"))
else: else:
path = os.path.expanduser("~") + "/mne_data/fOLD/fOLD-public-master/Supplementary" path = os.path.expanduser("~") + "/mne_data/fOLD/fOLD-public-master/Supplementary"
set_config('MNE_NIRS_FOLD_PATH', resource_path(path)) # type: ignore set_config('MNE_NIRS_FOLD_PATH', resource_path(path))
output = None hbo_channel_names = cast(list[str], getattr(raw.copy().pick(picks='hbo'), "ch_names"))
# List to store the results # Store clean, picklable data lists instead of complex DataFrames
landmark_specificity_data: list[dict[str, Any]] = [] channel_results = {}
# Filter the data to only what we want step_idx = 0
hbo_channel_names = cast(list[str], getattr(raw.copy().pick(picks='hbo'), "ch_names")) # type: ignore
# Format the output to make it slightly easier to read
if True:
num_channels = len(hbo_channel_names)
rows, cols = 4, 7 # 6 rows and 4 columns of pie charts
fig, axes = plt.subplots(rows, cols, figsize=(16, 10), constrained_layout=True)
axes = axes.flatten() # Flatten the axes array for easier indexing
# If more pie charts than subplots, create extra subplots
if num_channels > rows * cols:
fig, axes = plt.subplots((num_channels // cols) + 1, cols, figsize=(16, 10), constrained_layout=True)
axes = axes.flatten()
# Create a list for consistent color mapping
landmarks = [
"1 - Primary Somatosensory Cortex",
"2 - Primary Somatosensory Cortex",
"3 - Primary Somatosensory Cortex",
"4 - Primary Motor Cortex",
"5 - Somatosensory Association Cortex",
"6 - Pre-Motor and Supplementary Motor Cortex",
"7 - Somatosensory Association Cortex",
"8 - Includes Frontal eye fields",
"9 - Dorsolateral prefrontal cortex",
"10 - Frontopolar area",
"11 - Orbitofrontal area",
"17 - Primary Visual Cortex (V1)",
"18 - Visual Association Cortex (V2)",
"19 - V3",
"20 - Inferior Temporal gyrus",
"21 - Middle Temporal gyrus",
"22 - Superior Temporal Gyrus",
"23 - Ventral Posterior cingulate cortex",
"24 - Ventral Anterior cingulate cortex",
"25 - Subgenual cortex",
"32 - Dorsal anterior cingulate cortex",
"37 - Fusiform gyrus",
"38 - Temporopolar area",
"39 - Angular gyrus, part of Wernicke's area",
"40 - Supramarginal gyrus part of Wernicke's area",
"41 - Primary and Auditory Association Cortex",
"42 - Primary and Auditory Association Cortex",
"43 - Subcentral area",
"44 - pars opercularis, part of Broca's area",
"45 - pars triangularis Broca's area",
"46 - Dorsolateral prefrontal cortex",
"47 - Inferior prefrontal gyrus",
"48 - Retrosubicular area",
"Brain_Outside",
]
cmap1 = plt.get_cmap('tab20') # First 20 colors
cmap2 = plt.get_cmap('tab20b') # Next 20 colors
# Combine the colors from both colormaps
colors = [cmap1(i) for i in range(20)] + [cmap2(i) for i in range(20)] # Total 40 colors
landmarks.sort(key=lambda x: (int(x.split(" - ")[0]) if x.split(" - ")[0].isdigit() else float('inf')))
landmark_color_map = {landmark: colors[i % len(colors)] for i, landmark in enumerate(landmarks)}
# Iterate over each channel
print(len(hbo_channel_names))
for idx, channel_name in enumerate(hbo_channel_names):
print(idx, channel_name)
# Run the fOLD on the selected channel
channel_data = raw.copy().pick(picks=channel_name) # type: ignore
for channel_name in hbo_channel_names:
channel_data = raw.copy().pick(picks=channel_name)
output = cast(list[DataFrame], fold_channel_specificity_normal(channel_data, interpolate=True, atlas='Brodmann')) output = cast(list[DataFrame], fold_channel_specificity_normal(channel_data, interpolate=True, atlas='Brodmann'))
# Process each DataFrame that fold_channel_specificity returns channel_results[channel_name] = []
for df_data in output: for df_data in output:
# Extract just raw primitive types so they transfer over process channels flawlessly
for _, row in df_data.iterrows():
channel_results[channel_name].append({
'Landmark': str(row['Landmark']),
'Specificity': float(row['Specificity'])
})
step_idx += 1
if progress_queue is not None:
progress_queue.put((p_name, step_idx))
# Extract the relevant columns # Return raw data dictionary to the result_queue
useful_data = df_data[['Landmark', 'Specificity']] return channel_results
# Store the results
landmark_specificity_data.append({
'Channel': channel_name,
'Data': useful_data,
})
# Plot the results
# TODO: Fix this
if True:
unique_landmarks = sorted(useful_data['Landmark'].unique())
color_list = [landmark_color_map[landmark] for landmark in useful_data['Landmark']]
# Plot specificity for each channel
ax = axes[idx]
labels = [f'{landmark.split(" - ")[0]}' if landmark != 'Brain_Outside' else 'B' for landmark in useful_data['Landmark']]
wedges, texts, autotexts = ax.pie(
useful_data['Specificity'],
autopct='%1.1f%%',
startangle=90,
labels=labels,
labeldistance=1.05,
colors=color_list)
ax.set_title(f'{channel_name}')
ax.axis('equal')
landmark_specificity_data = []
# TODO: Fix this
if True:
handles = [
plt.Line2D([0], [0], marker='o', color='w', label=landmark, markersize=10,
markerfacecolor=landmark_color_map[landmark])
for landmark in landmarks
]
n_landmarks = len(landmarks)
# Calculate the figure size based on number of rows and columns
fig_width = 5
fig_height = n_landmarks / 4
# Create a new figure window for the legend
legend_fig = plt.figure(figsize=(fig_width, fig_height))
legend_axes = legend_fig.add_subplot(111)
legend_axes.axis('off') # Turn off axis for the legend window
legend_axes.legend(handles=handles, loc='center', fontsize=10, title="Landmarks")
for ax in axes[len(hbo_channel_names):]:
ax.axis('off')
#plt.show()
fig_dict = {"main": fig, "legend": legend_fig}
return convert_fig_dict_to_png_bytes(fig_dict)
def individual_significance(raw_haemo, glm_est): def individual_significance(raw_haemo, glm_est):
@@ -3939,6 +3977,7 @@ def hr_calc(raw):
def process_participant(file_path, progress_callback=None): def process_participant(file_path, progress_callback=None):
fig_individual: dict[str, Figure] = {} fig_individual: dict[str, Figure] = {}
logger.info(f"Folding Bypass: {FOLDING_BYP}")
# Step 1: Preprocessing # Step 1: Preprocessing
raw = load_snirf(file_path) raw = load_snirf(file_path)
@@ -3949,7 +3988,7 @@ def process_participant(file_path, progress_callback=None):
# Step 2: Trimming # Step 2: Trimming
# TODO: Clean this into a method # TODO: Clean this into a method
if TRIM: if TRIM and not FOLDING_BYP:
if hasattr(raw, 'annotations') and len(raw.annotations) > 0: if hasattr(raw, 'annotations') and len(raw.annotations) > 0:
# Get time of first event # Get time of first event
first_event_time = raw.annotations.onset[0] first_event_time = raw.annotations.onset[0]
@@ -3985,7 +4024,7 @@ def process_participant(file_path, progress_callback=None):
logger.info("Step 3 Completed.") logger.info("Step 3 Completed.")
# Step 4: Short/Long Channels # Step 4: Short/Long Channels
if SHORT_CHANNEL: if SHORT_CHANNEL and not FOLDING_BYP:
short_chans = get_short_channels(raw, max_dist=SHORT_CHANNEL_THRESH) short_chans = get_short_channels(raw, max_dist=SHORT_CHANNEL_THRESH)
fig_short_chans = short_chans.plot(duration=raw.times[-1], n_channels=raw.info['nchan'], title="Short Channels Only", show=False) fig_short_chans = short_chans.plot(duration=raw.times[-1], n_channels=raw.info['nchan'], title="Short Channels Only", show=False)
fig_individual["short"] = fig_short_chans fig_individual["short"] = fig_short_chans
@@ -3996,7 +4035,7 @@ def process_participant(file_path, progress_callback=None):
logger.info("Step 4 Completed.") logger.info("Step 4 Completed.")
# Step 5: Heart Rate # Step 5: Heart Rate
if HEART_RATE: if HEART_RATE and not FOLDING_BYP:
fig, hr1, hr2, low, high = hr_calc(raw) fig, hr1, hr2, low, high = hr_calc(raw)
fig_individual["PSD"] = fig fig_individual["PSD"] = fig
fig_individual['HeartRate_PSD'] = hr1 fig_individual['HeartRate_PSD'] = hr1
@@ -4017,7 +4056,7 @@ def process_participant(file_path, progress_callback=None):
# Step 6: Scalp Coupling Index # Step 6: Scalp Coupling Index
bad_sci = [] bad_sci = []
if SCI: if SCI and not FOLDING_BYP:
if HEART_RATE: if HEART_RATE:
bad_sci, fig_sci_1, fig_sci_2 = calculate_scalp_coupling(raw, low, high) bad_sci, fig_sci_1, fig_sci_2 = calculate_scalp_coupling(raw, low, high)
else: else:
@@ -4029,7 +4068,7 @@ def process_participant(file_path, progress_callback=None):
# Step 7: Signal to Noise Ratio # Step 7: Signal to Noise Ratio
bad_snr = [] bad_snr = []
if SNR: if SNR and not FOLDING_BYP:
bad_snr, fig_snr = calculate_signal_noise_ratio(raw) bad_snr, fig_snr = calculate_signal_noise_ratio(raw)
fig_individual["SNR1"] = fig_snr fig_individual["SNR1"] = fig_snr
if progress_callback: progress_callback(7) if progress_callback: progress_callback(7)
@@ -4037,7 +4076,7 @@ def process_participant(file_path, progress_callback=None):
# Step 8: Peak Spectral Power # Step 8: Peak Spectral Power
bad_psp = [] bad_psp = []
if PSP: if PSP and not FOLDING_BYP:
bad_psp, fig_psp1, fig_psp2 = calculate_peak_power(raw) bad_psp, fig_psp1, fig_psp2 = calculate_peak_power(raw)
fig_individual["PSP1"] = fig_psp1 fig_individual["PSP1"] = fig_psp1
fig_individual["PSP2"] = fig_psp2 fig_individual["PSP2"] = fig_psp2
@@ -4045,35 +4084,35 @@ def process_participant(file_path, progress_callback=None):
logger.info("Step 8 Completed.") logger.info("Step 8 Completed.")
bad_cv = [] bad_cv = []
if CV: if CV and not FOLDING_BYP:
bad_cv, fig_cv = find_bad_channels_cv(raw, cv_threshold=CV_THRESHOLD) bad_cv, fig_cv = find_bad_channels_cv(raw, cv_threshold=CV_THRESHOLD)
fig_individual['cv'] = fig_cv fig_individual['cv'] = fig_cv
if progress_callback: progress_callback(9) if progress_callback: progress_callback(9)
logger.info("Step 9 Completed.") logger.info("Step 9 Completed.")
bad_range = [] bad_range = []
if MAD: if MAD and not FOLDING_BYP:
bad_range, fig_range = find_bad_channels_range(raw, threshold=MAD_THRESHOLD) bad_range, fig_range = find_bad_channels_range(raw, threshold=MAD_THRESHOLD)
fig_individual['range'] = fig_range fig_individual['range'] = fig_range
if progress_callback: progress_callback(10) if progress_callback: progress_callback(10)
logger.info("Step 10 Completed.") logger.info("Step 10 Completed.")
bad_noise = [] bad_noise = []
if PSD_NOISE: if PSD_NOISE and not FOLDING_BYP:
bad_noise, fig_noise = detect_high_freq_noise(raw, db_limit=DB_LIMIT, freq_div=TARGET_FREQ_DIV) bad_noise, fig_noise = detect_high_freq_noise(raw, db_limit=DB_LIMIT, freq_div=TARGET_FREQ_DIV)
fig_individual['psd_noise'] = fig_noise fig_individual['psd_noise'] = fig_noise
if progress_callback: progress_callback(11) if progress_callback: progress_callback(11)
logger.info("Step 11 Completed.") logger.info("Step 11 Completed.")
bad_disp = [] bad_disp = []
if CHANNEL_VAR: if CHANNEL_VAR and not FOLDING_BYP:
bad_disp, fig_disp = detect_sensor_displacement(raw, threshold_ratio=CHANNEL_THRESH) bad_disp, fig_disp = detect_sensor_displacement(raw, threshold_ratio=CHANNEL_THRESH)
fig_individual['displacement'] = fig_disp fig_individual['displacement'] = fig_disp
if progress_callback: progress_callback(12) if progress_callback: progress_callback(12)
logger.info("Step 12 Completed.") logger.info("Step 12 Completed.")
# Step 9: Bad Channels Handling # Step 9: Bad Channels Handling
if BAD_CHANNELS_HANDLING != "None": if BAD_CHANNELS_HANDLING != "None" and not FOLDING_BYP:
raw, fig_dropped, fig_raw_before, bad_channels = mark_bads(raw, bad_sci, bad_snr, bad_psp, bad_cv, bad_range, bad_noise, bad_disp) raw, fig_dropped, fig_raw_before, bad_channels = mark_bads(raw, bad_sci, bad_snr, bad_psp, bad_cv, bad_range, bad_noise, bad_disp)
if fig_dropped and fig_raw_before is not None: if fig_dropped and fig_raw_before is not None:
fig_individual["fig2"] = fig_dropped fig_individual["fig2"] = fig_dropped
@@ -4108,7 +4147,7 @@ def process_participant(file_path, progress_callback=None):
logger.info("Step 14 Completed.") logger.info("Step 14 Completed.")
# Step 11: Temporal Derivative Distribution Repair Filtering # Step 11: Temporal Derivative Distribution Repair Filtering
if TDDR: if TDDR and not FOLDING_BYP:
raw_od = temporal_derivative_distribution_repair(raw_od) raw_od = temporal_derivative_distribution_repair(raw_od)
fig_raw_od_tddr = raw_od.plot(duration=raw.times[-1], n_channels=raw.info['nchan'], title="After TDDR (Motion Correction)", show=False) fig_raw_od_tddr = raw_od.plot(duration=raw.times[-1], n_channels=raw.info['nchan'], title="After TDDR (Motion Correction)", show=False)
fig_individual["TDDR"] = fig_raw_od_tddr fig_individual["TDDR"] = fig_raw_od_tddr
@@ -4116,7 +4155,7 @@ def process_participant(file_path, progress_callback=None):
logger.info("Step 15 Completed.") logger.info("Step 15 Completed.")
# Step 12: Wavelet Filtering # Step 12: Wavelet Filtering
if WAVELET: if WAVELET and not FOLDING_BYP:
raw_od, fig = calculate_and_apply_wavelet(raw_od) raw_od, fig = calculate_and_apply_wavelet(raw_od)
fig_individual["Wavelet"] = fig fig_individual["Wavelet"] = fig
if progress_callback: progress_callback(16) if progress_callback: progress_callback(16)
@@ -4130,7 +4169,7 @@ def process_participant(file_path, progress_callback=None):
logger.info("Step 17 Completed.") logger.info("Step 17 Completed.")
# Step 14: Enhance Negative Correlation # Step 14: Enhance Negative Correlation
if ENHANCE_NEGATIVE_CORRELATION: if ENHANCE_NEGATIVE_CORRELATION and not FOLDING_BYP:
raw_haemo = enhance_negative_correlation(raw_haemo) raw_haemo = enhance_negative_correlation(raw_haemo)
fig_raw_haemo_enc = raw_haemo.plot(duration=raw_haemo.times[-1], n_channels=raw_haemo.info['nchan'], title="Enhance Negative Correlation", show=False) fig_raw_haemo_enc = raw_haemo.plot(duration=raw_haemo.times[-1], n_channels=raw_haemo.info['nchan'], title="Enhance Negative Correlation", show=False)
fig_individual["ENC"] = fig_raw_haemo_enc fig_individual["ENC"] = fig_raw_haemo_enc
@@ -4138,7 +4177,7 @@ def process_participant(file_path, progress_callback=None):
logger.info("Step 18 Completed.") logger.info("Step 18 Completed.")
# Step 15: Filter # Step 15: Filter
if FILTER: if FILTER and not FOLDING_BYP:
raw_haemo, fig_filter, fig_raw_haemo_filter = filter_the_data(raw_haemo) raw_haemo, fig_filter, fig_raw_haemo_filter = filter_the_data(raw_haemo)
fig_individual["filter1"] = fig_filter fig_individual["filter1"] = fig_filter
fig_individual["filter2"] = fig_raw_haemo_filter fig_individual["filter2"] = fig_raw_haemo_filter
@@ -4146,16 +4185,18 @@ def process_participant(file_path, progress_callback=None):
logger.info("Step 19 Completed.") logger.info("Step 19 Completed.")
# Step 16: Extracting Events # Step 16: Extracting Events
events, event_dict = events_from_annotations(raw_haemo) if not FOLDING_BYP:
fig_events = plot_events(events, event_id=event_dict, sfreq=raw_haemo.info["sfreq"], show=False) events, event_dict = events_from_annotations(raw_haemo)
fig_individual["events"] = fig_events fig_events = plot_events(events, event_id=event_dict, sfreq=raw_haemo.info["sfreq"], show=False)
fig_individual["events"] = fig_events
if progress_callback: progress_callback(20) if progress_callback: progress_callback(20)
logger.info("Step 20 Completed.") logger.info("Step 20 Completed.")
# Step 17: Epoch Calculations # Step 17: Epoch Calculations
epochs, fig_epochs = epochs_calculations(raw_haemo, events, event_dict) if not FOLDING_BYP:
for name, fig in fig_epochs: epochs, fig_epochs = epochs_calculations(raw_haemo, events, event_dict)
fig_individual[f"epochs_{name}"] = fig for name, fig in fig_epochs:
fig_individual[f"epochs_{name}"] = fig
if progress_callback: progress_callback(21) if progress_callback: progress_callback(21)
logger.info("Step 21 Completed.") logger.info("Step 21 Completed.")
@@ -4274,6 +4315,8 @@ def process_participant(file_path, progress_callback=None):
# Step 24: Finishing Up # Step 24: Finishing Up
fig_bytes = convert_fig_dict_to_png_bytes(fig_individual) fig_bytes = convert_fig_dict_to_png_bytes(fig_individual)
if FOLDING_BYP:
epochs = None
sanitize_paths_for_pickle(raw_haemo, epochs) sanitize_paths_for_pickle(raw_haemo, epochs)
if progress_callback: progress_callback(28) if progress_callback: progress_callback(28)
+1
View 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="M400-280h160v-80H400v80Zm0-160h280v-80H400v80ZM280-600h400v-80H280v80Zm200 120ZM265-80q-79 0-134.5-55.5T75-270q0-57 29.5-102t77.5-68H80v-80h240v240h-80v-97q-37 8-61 38t-24 69q0 46 32.5 78t77.5 32v80Zm135-40v-80h360v-560H200v160h-80v-160q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H400Z"/></svg>

After

Width:  |  Height:  |  Size: 443 B

+1
View 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="M480-120q-138 0-240.5-91.5T122-440h82q14 104 92.5 172T480-200q117 0 198.5-81.5T760-480q0-117-81.5-198.5T480-760q-69 0-129 32t-101 88h110v80H120v-240h80v94q51-64 124.5-99T480-840q75 0 140.5 28.5t114 77q48.5 48.5 77 114T840-480q0 75-28.5 140.5t-77 114q-48.5 48.5-114 77T480-120Zm112-192L440-464v-216h80v184l128 128-56 56Z"/></svg>

After

Width:  |  Height:  |  Size: 444 B

+1
View 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="M480-80q-155 0-269-103T82-440h81q15 121 105.5 200.5T480-160q134 0 227-93t93-227q0-134-93-227t-227-93q-86 0-159.5 42.5T204-640h116v80H88q29-140 139-230t253-90q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm112-232L440-464v-216h80v184l128 128-56 56Z"/></svg>

After

Width:  |  Height:  |  Size: 416 B

+1
View 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="M520-330v-60h160v60H520Zm60 210v-50h-60v-60h60v-50h60v160h-60Zm100-50v-60h160v60H680Zm40-110v-160h60v50h60v60h-60v50h-60Zm111-280h-83q-26-88-99-144t-169-56q-117 0-198.5 81.5T200-480q0 72 32.5 132t87.5 98v-110h80v240H160v-80h94q-62-50-98-122.5T120-480q0-75 28.5-140.5t77-114q48.5-48.5 114-77T480-840q129 0 226.5 79.5T831-560Z"/></svg>

After

Width:  |  Height:  |  Size: 449 B

+1
View 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="M451.5-251.5Q440-263 440-280t11.5-28.5Q463-320 480-320t28.5 11.5Q520-297 520-280t-11.5 28.5Q497-240 480-240t-28.5-11.5ZM440-360v-161l80 80v81h-80Zm433 158L655-419 480-720l-47 80-58-58 105-182 393 678Zm-695 2h469L350-497 178-200ZM819-28l-92-92H40l252-435L27-820l57-57L876-85l-57 57ZM499-348Zm45-181Z"/></svg>

After

Width:  |  Height:  |  Size: 423 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

+1376 -111
View File
File diff suppressed because it is too large Load Diff
+13 -2
View File
@@ -16,6 +16,7 @@ import shutil
import zipfile import zipfile
import traceback import traceback
import subprocess import subprocess
import configparser
# External library imports # External library imports
import psutil import psutil
@@ -415,7 +416,7 @@ def wait_for_process_to_exit(process_name, timeout=10):
return False return False
def finish_update_if_needed(platform_name, app_name): def finish_update_if_needed(platform_name, app_name, cfg_path):
""" """
Completes a pending application update if '--finish-update' is present in the command-line arguments. Completes a pending application update if '--finish-update' is present in the command-line arguments.
""" """
@@ -423,6 +424,17 @@ def finish_update_if_needed(platform_name, app_name):
if "--finish-update" in sys.argv: if "--finish-update" in sys.argv:
print("Finishing update...") print("Finishing update...")
update_cfg = configparser.ConfigParser()
try:
update_cfg.read(cfg_path)
update_cfg.set("Options", "show_welcome_dialog", "true")
with open(cfg_path, "w") as f:
update_cfg.write(f)
print("Welcome dialog flag successfully reset to 'true' for next run.")
except Exception as e:
print(f"Warning: Could not update welcome dialog preference flag: {e}")
if platform_name == 'darwin': if platform_name == 'darwin':
app_dir = f'/tmp/{app_name}tempupdate' app_dir = f'/tmp/{app_name}tempupdate'
else: else:
@@ -519,7 +531,6 @@ def finish_update_if_needed(platform_name, app_name):
except Exception as e: except Exception as e:
print(f"Failed to delete update folder: {e}") print(f"Failed to delete update folder: {e}")
QMessageBox.information(None, "Update Complete", "The application has been successfully updated.")
sys.argv.remove("--finish-update") sys.argv.remove("--finish-update")