Coverage for main/utils.py: 87%

70 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-03 15:51 +0000

1import os 

2import shutil 

3import subprocess 

4import platform 

5import sys 

6from PyQt6.QtCore import QStandardPaths, QCoreApplication 

7from pathlib import Path 

8 

9 

10def get_bin_path(name): 

11 """ 

12 Locate the executable path for a given binary name across different environments. 

13 

14 This function attempts to find the full path of an executable following these steps: 

15 1. Check if running from a PyInstaller bundle (using `sys._MEIPASS`). 

16 2. Check if the binary exists in the same directory as the current script. 

17 3. Search the system PATH using `shutil.which()`. 

18 

19 Args: 

20 name (str): The base name of the executable (without extension). 

21 

22 Returns: 

23 str: The absolute path to the executable. 

24 

25 Raises: 

26 FileNotFoundError: If the executable cannot be found. 

27 """ 

28 if not name or not str(name).strip(): 

29 raise ValueError(QCoreApplication.translate("Utils", "Tool name not provided")) 

30 

31 exe_name = f"{name}.exe" if platform.system() == "Windows" else name 

32 

33 # Case 1: Running from a PyInstaller bundle 

34 if hasattr(sys, "_MEIPASS"): 

35 candidate = os.path.join(sys._MEIPASS, name, exe_name) 

36 if os.path.exists(candidate): 

37 return candidate 

38 

39 # Case 2: Located next to the script or executable 

40 candidate = os.path.join(os.path.dirname(__file__), name, exe_name) 

41 if os.path.exists(candidate): 

42 return candidate 

43 

44 # Case 3: Found in the system PATH 

45 path = shutil.which(exe_name) 

46 if path: 

47 return path 

48 

49 raise FileNotFoundError(QCoreApplication.translate("Utils", "Could not find executable: {exe_name}").format(exe_name=exe_name)) 

50 

51 

52def get_app_dir(): 

53 """ 

54 Get the writable directory used to store user-specific application data. 

55 

56 On all operating systems, this directory is located under the user's home folder 

57 (e.g., `~/GliAAns-UI` on Linux and macOS, or `C:\\Users\\<User>\\GliAAns-UI` on Windows). 

58 

59 Returns: 

60 pathlib.Path: The absolute path to the application data directory. 

61 """ 

62 base = Path(QStandardPaths.writableLocation(QStandardPaths.StandardLocation.HomeLocation)) / "GliAAns-UI" 

63 

64 try: 

65 base.mkdir(parents=True, exist_ok=True) 

66 except PermissionError: 

67 raise 

68 except OSError as e: 

69 # Errno 30 = read-only filesystem, 13 = forbidden 

70 if getattr(e, "errno", None) in (13, 30): 

71 raise PermissionError(QCoreApplication.translate("Utils", "Error while creating the app working directory")) from e 

72 raise 

73 

74 return base 

75 

76 

77def resource_path(relative_path): 

78 """ 

79 Resolve the absolute path to a resource file. 

80 

81 Works in both development environments and PyInstaller-built executables. 

82 

83 Args: 

84 relative_path (str): The relative path to the resource file. 

85 

86 Returns: 

87 str: The absolute path to the resource file. 

88 """ 

89 if hasattr(sys, "_MEIPASS"): 

90 return os.path.join(sys._MEIPASS, relative_path) 

91 return os.path.join(os.path.dirname(__file__), relative_path) 

92 

93 

94def get_shell_path(): 

95 """ 

96 Retrieve the system PATH from the user's login shell environment. 

97 

98 This is necessary because on Unix-based systems (macOS, Linux), GUI applications 

99 do not always inherit the user's shell environment (especially when launched from 

100 the desktop or a shortcut). 

101 

102 Behavior by platform: 

103 - **macOS:** Uses the default login shell (`zsh`) to read PATH. 

104 - **Linux:** Reads from the user's current shell (usually `bash`). 

105 - **Windows:** Returns the PATH from the current environment. 

106 

107 Returns: 

108 str: The PATH environment variable as a string. 

109 """ 

110 system = platform.system() 

111 

112 try: 

113 if system == "Darwin": # macOS 

114 result = subprocess.run( 

115 ["/bin/zsh", "-l", "-c", "echo $PATH"], 

116 capture_output=True, 

117 text=True, 

118 check=True 

119 ) 

120 return result.stdout.strip() 

121 

122 elif system == "Linux": # Linux 

123 shell = os.environ.get("SHELL", "/bin/bash") 

124 result = subprocess.run( 

125 [shell, "-l", "-c", "echo $PATH"], 

126 capture_output=True, 

127 text=True, 

128 check=True 

129 ) 

130 return result.stdout.strip() 

131 

132 elif system == "Windows": # Windows 

133 return os.environ.get("PATH", "") 

134 

135 # Fallback for unknown platforms 

136 return os.environ.get("PATH", "") 

137 

138 except Exception as e: 

139 print(f"Error while retrieving PATH from shell: {e}") 

140 return os.environ.get("PATH", "") 

141 

142 

143def setup_fsl_env(): 

144 """ 

145 Load environment variables for FSL (FMRIB Software Library). 

146 

147 This function runs a shell command to source the FSL configuration script 

148 (`fsl.sh`) and extract the environment variables `FSLDIR` and `FSLOUTPUTTYPE`. 

149 

150 Returns: 

151 tuple[str, str]: A tuple containing: 

152 - `FSLDIR`: The root installation directory of FSL. 

153 - `FSLOUTPUTTYPE`: The default FSL output format (e.g., NIFTI_GZ). 

154 

155 Raises: 

156 RuntimeError: If FSLDIR or FSLOUTPUTTYPE cannot be retrieved. 

157 """ 

158 result = subprocess.run( 

159 ["/bin/zsh", "-l", "-c", "source $FSLDIR/etc/fslconf/fsl.sh && echo $FSLDIR && echo $FSLOUTPUTTYPE"], 

160 capture_output=True, 

161 text=True, 

162 check=True 

163 ) 

164 

165 lines = result.stdout.strip().splitlines() 

166 if len(lines) < 2: 

167 raise RuntimeError("Could not read FSLDIR and FSLOUTPUTTYPE") 

168 

169 fsldir, fsloutputtype = lines[0], lines[1] 

170 return fsldir, fsloutputtype 

171 

172def get_dl_python_executable(): 

173 """Returns the path of the Python in venv deep learning.""" 

174 base_dir = Path(sys.executable).parent 

175 

176 python_path = base_dir / ".venv-dl" / "bin" / "python" 

177 

178 if not python_path.exists(): 

179 raise FileNotFoundError(f"Python DL environment not found: {python_path}") 

180 

181 return str(python_path) 

182 

183def get_script_path(script_relative_path): 

184 """Returns the absolute path of a deep learning script.""" 

185 base_dir = Path(sys.executable).parent 

186 script_path = base_dir / script_relative_path 

187 return str(script_path)