Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ecafe09ac | ||
|
|
8270d7b7a8 | ||
|
|
2e9820336d |
No files matched your search
@@ -1,3 +1,8 @@
|
|||||||
|
# Version 1.8.1
|
||||||
|
|
||||||
|
- Quick fix to plugins not loading on certain platforms
|
||||||
|
|
||||||
|
|
||||||
# Version 1.8.0
|
# Version 1.8.0
|
||||||
|
|
||||||
- Plugin Manager now supports checking and downloading plugin updates to ensure plugins can be updated
|
- Plugin Manager now supports checking and downloading plugin updates to ensure plugins can be updated
|
||||||
|
|||||||
+20
-51
@@ -1,55 +1,24 @@
|
|||||||
# Version 1.7.2
|
# Version 1.8.1
|
||||||
|
|
||||||
- Fixed the Linux build not starting if shared library files were missing from the host machine
|
- Quick fix to plugins not loading on certain platforms
|
||||||
- Fixed the macOS version not loading plugins due to not having an HTTPS certificate
|
|
||||||
|
|
||||||
|
|
||||||
# Version 1.7.1
|
# Version 1.8.0
|
||||||
|
|
||||||
- Added support for Plugins! Plugins can be downloaded from the official repository or from custom ones. Support for creating a plugin can be found on the wiki
|
- Plugin Manager now supports checking and downloading plugin updates to ensure plugins can be updated
|
||||||
- Files and folders can now be dragged and dropped into the loaded files area to be loaded instead of relying on the file menu
|
- Plugin Manager now properly uninstalls plugins when requested rather than doing nothing
|
||||||
- Fixed an issue where shadow snirf files would attempt to be loaded by the application. Fixes [Issue 92](https://git.research.dezeeuw.ca/tyler/flares/issues/92)
|
- Installing plugins now displays a progress bar when downloading and no longer hangs the application when downloading a big plugin
|
||||||
- Fixed an issue where the application could not automatically update between versions 1.5.1 to 1.7.1. Sorry! 1.7.1 upgrading onward should be fixed and more robust
|
- Project loading now displays a loading message when loading big projects and no longer hangs the application when loading
|
||||||
- Fixed an issue where some log files would not generate, or would generate in an incorrect location
|
- New preferences options and menus are now available! All existing preferences have been moved into new sub categories
|
||||||
- Fixed an issue where updating events in a snirf file would not release memory properly
|
- The UI preference category now has the Show Advanced Parameters preference
|
||||||
- Fixed an issue where loading multiple files in quick succession could cause some of the files to never finish loading
|
- The Updates preference category has two ney options to prevent the automatic checking of application or plugin updates
|
||||||
- Fixed an issue where loading a saved project, pushing clear, and re-opening snirf files would cause them to appear incorrectly in the loaded files area
|
- The Theme category has remain untouched and all other preferences have been moved to the Processing category
|
||||||
- Fixed a crucial bug where short channels were still being sent to the donor pool to assist in interpolating long channels from. Fixes [Issue 80](https://git.research.dezeeuw.ca/tyler/flares/issues/80)
|
- Added a new preference in the Processing category called "Separate Events". This allows events to have their own values, and not be collapsed into a single value
|
||||||
|
- Export To CSV Viewer export options now perform the actions they claim rather than having no functionality
|
||||||
|
- Logs on a per participant basis are now available to be viewed from within the application
|
||||||
# Version 1.7.0
|
- They can be accessed by right clicking a participant bubble and selecting "Show Logs". Logs will be empty if they have not been processed
|
||||||
|
- A notification now appears when processing completes even if the application is not focused
|
||||||
- This is potentially a save-changing release due to adding more data into the save file. Please update your project files to ensure compatibility
|
- Fixed a regression where opening a folder would no longer load all the files in that folder
|
||||||
- Renamed all instances of "Inter" to properly read as "Intra" and changed "Cross" to now read as "Inter"
|
- Added inspirational quotes to the About window
|
||||||
- Changed RESAMPLE to only apply where it is required to avoid having Functional Connectivity analysis methods running on data that has been resampled
|
- Licence is now clickable from the About window
|
||||||
- Added parameters that appear when attempting to generate results from the Participant and Intra-Group Functional Connectivity viewers and removed the non-functional placeholder parameters
|
- Improved code stability and functionality
|
||||||
- Modified the Participant and Intra-Group Functional Connectivity analysis options to better perform their expected tasks. This remains as a BETA feature
|
|
||||||
- Removed the existing Intra-Group Functional Connectivity option and replaced it with two new ones: Beta-Series Correlation and Spectral Coherence (epochs)
|
|
||||||
- Updated the names of the methods provided for the Participant Functional Connectivity Viewer to better match the actions they perform
|
|
||||||
- Updated the warnings for the Functional Connectivity Viewers to better represent the challenges these analysis options now face
|
|
||||||
- Added basic unit testing to hopefully prevent any accidental processing changes from occurring in the future
|
|
||||||
- Added a new parameter to the PSP section on the right side of the screen: PSP_USE_HEART_RATE_BAND. This functions similarly to the existing SCI_USE_HEART_RATE_BAND
|
|
||||||
- Added description text to the Inter-Group and Intra-Group Brain and Image Viewers, as well as the Functional Connectivity windows to explain what output can be expected
|
|
||||||
- Added a new Preference option of Theme. Allows from selecting Auto (System default), Light, or Dark. Fixes [Issue 7](https://git.research.dezeeuw.ca/tyler/flares/issues/7)
|
|
||||||
- Optode updater can now update optode locations in multiple snirf files at once
|
|
||||||
- Removed image index 1 (Significance) from the Intra-Group Brain and Image Viewer as it is now provided more in depth with the Stats viewers
|
|
||||||
- Modified the timeout when waiting for the application to close while performing updates down to a reasonable number
|
|
||||||
- Modified the help messages for parameters in the SCI and PSP areas to better reflect how the parameters are used
|
|
||||||
- Modified the heart rate calculation to be more precise and correct when dealing with good data and not messy data
|
|
||||||
- Modified the heart rate calculation to not take only one channel in the data to use, but rather an average of channels. This still prefers short channels if they are present
|
|
||||||
- Fixed an issue where loading a project from a saved project file would not allow the data to be reprocessed with new parameters
|
|
||||||
- Fixed an issue where the "Hand" BIDS metadata value would not populate on bubbles correctly
|
|
||||||
- Fixed an issue where performing PSP could ignore the bad channels that were marked by SCI and SNR
|
|
||||||
- Fixed an issue where the heart rate calculation would disregard all calculations and fall back to an extremely rudimentary calculation
|
|
||||||
- Fixed an issue that could prevent log file generation while the application was in the middle of an update
|
|
||||||
- Fixed an issue where a rare crash could occur while the application was in the middle of an update
|
|
||||||
- Fixed an issue that could have passed multiple conditions when generating an Intra-Group Stats image
|
|
||||||
- Fixed an issue that could pass NaN values when attempting to collapse channels
|
|
||||||
- Fixed an issue that was causing the OLS model to always be used for brain images with multiple participants, and not the MixedLM model
|
|
||||||
- Fixed an issue that could cause the Wavelet filtering step to crash
|
|
||||||
- Fixed an issue where file associations appeared to work as intended but would not load the project on macOS and only open the application
|
|
||||||
- Fixed an issue where file associations would refuse to associate on macOS once they have attempted to be associated
|
|
||||||
- Fixed an issue where certain parameters would not enable or disable depending on other parameters when they should've
|
|
||||||
- Fixed an issue where not all widgets would close when attempting to close the application causing the application to crash
|
|
||||||
- Fixed an issue where events were not created correctly after the data had been resampled by the design matrix
|
|
||||||
- Fixed an issue where progress bar colors would not reset if the data was reprocessed
|
|
||||||
@@ -14,6 +14,7 @@ import sys
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
import logging.handlers
|
||||||
import warnings
|
import warnings
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
@@ -310,21 +311,36 @@ script_dir = os.path.dirname(os.path.abspath(__file__))
|
|||||||
os.chdir(script_dir)
|
os.chdir(script_dir)
|
||||||
|
|
||||||
|
|
||||||
# Configure logging to file with timestamps and realtime flush
|
# Configure logging with rotation to prevent indefinite growth
|
||||||
if PLATFORM_NAME == 'darwin':
|
if PLATFORM_NAME == 'darwin':
|
||||||
log_path = os.path.abspath(os.path.join(os.path.dirname(sys.executable), "../../../fnirs_analysis.log"))
|
log_path = os.path.abspath(os.path.join(os.path.dirname(sys.executable), "../../../fnirs_analysis.log"))
|
||||||
else:
|
else:
|
||||||
log_path = os.path.join(get_app_dir(), "fnirs_analysis.log")
|
log_path = os.path.join(get_app_dir(), "fnirs_analysis.log")
|
||||||
|
|
||||||
logging.basicConfig(
|
# Create logger
|
||||||
filename=log_path,
|
logger = logging.getLogger()
|
||||||
level=logging.INFO,
|
logger.setLevel(logging.INFO)
|
||||||
format='%(asctime)s - %(processName)s - %(levelname)s - %(message)s',
|
|
||||||
datefmt='%Y-%m-%d %H:%M:%S',
|
# Clear any existing handlers
|
||||||
filemode='a'
|
logger.handlers.clear()
|
||||||
|
|
||||||
|
# Create rotating file handler (max 10MB per file, keep 5 backups = ~50MB max total)
|
||||||
|
rotating_handler = logging.handlers.RotatingFileHandler(
|
||||||
|
log_path,
|
||||||
|
maxBytes=10*1024*1024, # 10 MB
|
||||||
|
backupCount=5, # Keep 5 backup files
|
||||||
|
encoding='utf-8'
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger()
|
# Set formatter
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
'%(asctime)s - %(processName)s - %(levelname)s - %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
rotating_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
# Add handler to logger
|
||||||
|
logger.addHandler(rotating_handler)
|
||||||
|
|
||||||
|
|
||||||
def set_config_me(config: dict[str, Any]) -> None:
|
def set_config_me(config: dict[str, Any]) -> None:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
Filename: inspiration.py
|
Filename: inspiration.py
|
||||||
Description: Contains inspiration quotes to be shown at random
|
Description: Contains inspirational quotes to be shown at random
|
||||||
Note: Compliant with pylance strict type checking
|
Note: Compliant with pylance strict type checking
|
||||||
|
|
||||||
Author: Tyler de Zeeuw
|
Author: Tyler de Zeeuw
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
winreg = None
|
winreg = None
|
||||||
|
|
||||||
CURRENT_VERSION = "1.8.0"
|
CURRENT_VERSION = "1.8.2"
|
||||||
APP_NAME = "flares"
|
APP_NAME = "flares"
|
||||||
APP_NAME_EXPANDED = "fNIRS Lightweight Analysis, Research, & Evaluation Suite"
|
APP_NAME_EXPANDED = "fNIRS Lightweight Analysis, Research, & Evaluation Suite"
|
||||||
API_URL = f"https://git.research.dezeeuw.ca/api/v1/repos/tyler/{APP_NAME}/releases"
|
API_URL = f"https://git.research.dezeeuw.ca/api/v1/repos/tyler/{APP_NAME}/releases"
|
||||||
|
|||||||
+90
-1
@@ -462,6 +462,88 @@ def _remove_readonly(func: Callable[[str], Any], path: str, exc: Any) -> None:
|
|||||||
func(path)
|
func(path)
|
||||||
|
|
||||||
|
|
||||||
|
def files_are_identical(src_path: str, dst_path: str) -> bool:
|
||||||
|
"""
|
||||||
|
Check if two files are identical by comparing size and modification time.
|
||||||
|
|
||||||
|
This is much faster than hashing and catches all real changes for update purposes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
src_path (str): Path to source file.
|
||||||
|
dst_path (str): Path to destination file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if files are identical, False otherwise.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not os.path.exists(src_path) or not os.path.exists(dst_path):
|
||||||
|
return False
|
||||||
|
|
||||||
|
src_stat = os.stat(src_path)
|
||||||
|
dst_stat = os.stat(dst_path)
|
||||||
|
|
||||||
|
# Compare size first
|
||||||
|
if src_stat.st_size != dst_stat.st_size:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Compare modification time (rounded to 1 second to handle cross-platform differences)
|
||||||
|
src_mtime = int(src_stat.st_mtime)
|
||||||
|
dst_mtime = int(dst_stat.st_mtime)
|
||||||
|
|
||||||
|
return src_mtime == dst_mtime
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def smart_replace_folder(source_dir: str, dest_dir: str) -> Tuple[int, int]:
|
||||||
|
"""
|
||||||
|
Replace files in dest_dir with files from source_dir, but only if they differ.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_dir (str): Source directory containing new files.
|
||||||
|
dest_dir (str): Destination directory to update.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple[int, int]: (files_updated, files_skipped) counts.
|
||||||
|
"""
|
||||||
|
files_updated = 0
|
||||||
|
files_skipped = 0
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(source_dir):
|
||||||
|
relative_path = os.path.relpath(root, source_dir)
|
||||||
|
dest_root = os.path.join(dest_dir, relative_path)
|
||||||
|
|
||||||
|
# Create directory if it doesn't exist
|
||||||
|
if not os.path.exists(dest_root):
|
||||||
|
os.makedirs(dest_root)
|
||||||
|
|
||||||
|
for filename in files:
|
||||||
|
src_file = os.path.join(root, filename)
|
||||||
|
dst_file = os.path.join(dest_root, filename)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Skip if file is identical
|
||||||
|
if files_are_identical(src_file, dst_file):
|
||||||
|
files_skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Remove destination if it exists (handles locked files on some systems)
|
||||||
|
if os.path.exists(dst_file):
|
||||||
|
if os.path.isdir(dst_file):
|
||||||
|
shutil.rmtree(dst_file, onexc=_remove_readonly)
|
||||||
|
else:
|
||||||
|
os.remove(dst_file)
|
||||||
|
|
||||||
|
# Copy the file
|
||||||
|
shutil.copy2(src_file, dst_file)
|
||||||
|
files_updated += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Warning: Failed to update {filename}: {e}")
|
||||||
|
|
||||||
|
return files_updated, files_skipped
|
||||||
|
|
||||||
|
|
||||||
def finish_update_if_needed(platform_name: str, app_name: str, cfg_path: str, finish_update: bool) -> None:
|
def finish_update_if_needed(platform_name: str, app_name: str, cfg_path: str, finish_update: bool) -> None:
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
@@ -565,7 +647,14 @@ def finish_update_if_needed(platform_name: str, app_name: str, cfg_path: str, fi
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to replace {app_name}_updater: {e}")
|
print(f"Failed to replace {app_name}_updater: {e}")
|
||||||
|
|
||||||
# 5. Clean up all temporary update folders (with retries for Windows file locks)
|
# 5. Use smart replacement - only update files that have changed
|
||||||
|
print("Using smart update: only replacing files that have changed...")
|
||||||
|
files_updated, files_skipped = smart_replace_folder(target_updater_folder, target_base)
|
||||||
|
print(f"\nUpdate summary:")
|
||||||
|
print(f" Files updated: {files_updated}")
|
||||||
|
print(f" Files skipped (unchanged): {files_skipped}")
|
||||||
|
|
||||||
|
# 6. Clean up all temporary update folders (with retries for Windows file locks)
|
||||||
for folder in update_folders:
|
for folder in update_folders:
|
||||||
for attempt in range(3):
|
for attempt in range(3):
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in new issue
Block a user