sgtlib 3.3.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. StructuralGT/__init__.py +31 -0
  2. StructuralGT/apps/__init__.py +0 -0
  3. StructuralGT/apps/cli_main.py +258 -0
  4. StructuralGT/apps/gui_main.py +69 -0
  5. StructuralGT/apps/gui_mcw/__init__.py +0 -0
  6. StructuralGT/apps/gui_mcw/checkbox_model.py +91 -0
  7. StructuralGT/apps/gui_mcw/controller.py +1073 -0
  8. StructuralGT/apps/gui_mcw/image_provider.py +74 -0
  9. StructuralGT/apps/gui_mcw/imagegrid_model.py +75 -0
  10. StructuralGT/apps/gui_mcw/qthread_worker.py +102 -0
  11. StructuralGT/apps/gui_mcw/table_model.py +79 -0
  12. StructuralGT/apps/gui_mcw/tree_model.py +154 -0
  13. StructuralGT/apps/sgt_qml/CenterMainContent.qml +19 -0
  14. StructuralGT/apps/sgt_qml/LeftContent.qml +48 -0
  15. StructuralGT/apps/sgt_qml/MainWindow.qml +762 -0
  16. StructuralGT/apps/sgt_qml/RightLoggingPanel.qml +125 -0
  17. StructuralGT/apps/sgt_qml/assets/icons/.DS_Store +0 -0
  18. StructuralGT/apps/sgt_qml/assets/icons/back_icon.png +0 -0
  19. StructuralGT/apps/sgt_qml/assets/icons/brightness_icon.png +0 -0
  20. StructuralGT/apps/sgt_qml/assets/icons/cancel_icon.png +0 -0
  21. StructuralGT/apps/sgt_qml/assets/icons/crop_icon.png +0 -0
  22. StructuralGT/apps/sgt_qml/assets/icons/edit_icon.png +0 -0
  23. StructuralGT/apps/sgt_qml/assets/icons/graph_icon.png +0 -0
  24. StructuralGT/apps/sgt_qml/assets/icons/hide_panel.png +0 -0
  25. StructuralGT/apps/sgt_qml/assets/icons/next_icon.png +0 -0
  26. StructuralGT/apps/sgt_qml/assets/icons/notify_icon.png +0 -0
  27. StructuralGT/apps/sgt_qml/assets/icons/rescale_icon.png +0 -0
  28. StructuralGT/apps/sgt_qml/assets/icons/show_panel.png +0 -0
  29. StructuralGT/apps/sgt_qml/assets/icons/square_icon.png +0 -0
  30. StructuralGT/apps/sgt_qml/assets/icons/undo_icon.png +0 -0
  31. StructuralGT/apps/sgt_qml/components/ImageFilters.qml +82 -0
  32. StructuralGT/apps/sgt_qml/components/ImageProperties.qml +112 -0
  33. StructuralGT/apps/sgt_qml/components/ProjectNav.qml +127 -0
  34. StructuralGT/apps/sgt_qml/widgets/BinaryFilterWidget.qml +151 -0
  35. StructuralGT/apps/sgt_qml/widgets/BrightnessControlWidget.qml +103 -0
  36. StructuralGT/apps/sgt_qml/widgets/CreateProjectWidget.qml +112 -0
  37. StructuralGT/apps/sgt_qml/widgets/GTWidget.qml +94 -0
  38. StructuralGT/apps/sgt_qml/widgets/GraphComputeWidget.qml +77 -0
  39. StructuralGT/apps/sgt_qml/widgets/GraphExtractWidget.qml +175 -0
  40. StructuralGT/apps/sgt_qml/widgets/GraphPropertyWidget.qml +77 -0
  41. StructuralGT/apps/sgt_qml/widgets/ImageFilterWidget.qml +137 -0
  42. StructuralGT/apps/sgt_qml/widgets/ImagePropertyWidget.qml +78 -0
  43. StructuralGT/apps/sgt_qml/widgets/ImageViewWidget.qml +585 -0
  44. StructuralGT/apps/sgt_qml/widgets/MenuBarWidget.qml +137 -0
  45. StructuralGT/apps/sgt_qml/widgets/MicroscopyPropertyWidget.qml +80 -0
  46. StructuralGT/apps/sgt_qml/widgets/ProjectWidget.qml +141 -0
  47. StructuralGT/apps/sgt_qml/widgets/RescaleControlWidget.qml +83 -0
  48. StructuralGT/apps/sgt_qml/widgets/RibbonWidget.qml +406 -0
  49. StructuralGT/apps/sgt_qml/widgets/StatusBarWidget.qml +173 -0
  50. StructuralGT/compute/__init__.py +0 -0
  51. StructuralGT/compute/c_lang/include/sgt_base.h +21 -0
  52. StructuralGT/compute/graph_analyzer.py +1499 -0
  53. StructuralGT/entrypoints.py +49 -0
  54. StructuralGT/imaging/__init__.py +0 -0
  55. StructuralGT/imaging/base_image.py +403 -0
  56. StructuralGT/imaging/image_processor.py +780 -0
  57. StructuralGT/modules.py +29 -0
  58. StructuralGT/networks/__init__.py +0 -0
  59. StructuralGT/networks/fiber_network.py +490 -0
  60. StructuralGT/networks/graph_skeleton.py +425 -0
  61. StructuralGT/networks/sknw_mod.py +199 -0
  62. StructuralGT/utils/__init__.py +0 -0
  63. StructuralGT/utils/config_loader.py +244 -0
  64. StructuralGT/utils/configs.ini +97 -0
  65. StructuralGT/utils/progress_update.py +67 -0
  66. StructuralGT/utils/sgt_utils.py +291 -0
  67. sgtlib-3.3.9.dist-info/METADATA +789 -0
  68. sgtlib-3.3.9.dist-info/RECORD +72 -0
  69. sgtlib-3.3.9.dist-info/WHEEL +5 -0
  70. sgtlib-3.3.9.dist-info/entry_points.txt +3 -0
  71. sgtlib-3.3.9.dist-info/licenses/LICENSE +674 -0
  72. sgtlib-3.3.9.dist-info/top_level.txt +1 -0
@@ -0,0 +1,291 @@
1
+ # SPDX-License-Identifier: GNU GPL v3
2
+
3
+ """
4
+ StructuralGT utility functions.
5
+ """
6
+
7
+ import os
8
+ import io
9
+ import sys
10
+ import csv
11
+ import cv2
12
+ import base64
13
+ # import socket
14
+ import logging
15
+ # import platform
16
+ import subprocess
17
+ import gsd.hoomd
18
+ import numpy as np
19
+ import multiprocessing as mp
20
+ import matplotlib.pyplot as plt
21
+ from PIL import Image
22
+ from typing import LiteralString
23
+ from cv2.typing import MatLike
24
+
25
+
26
+ class AbortException(Exception):
27
+ """Custom exception to handle task cancellation initiated by the user or an error."""
28
+ pass
29
+
30
+
31
+ def get_num_cores():
32
+ """
33
+ Finds the count of CPU cores in a computer or a SLURM supercomputer.
34
+ :return: Number of cpu cores (int)
35
+ """
36
+
37
+ def __get_slurm_cores__():
38
+ """
39
+ Test the computer to see if it is a SLURM environment, then gets the number of CPU cores.
40
+ :return: Count of CPUs (int) or False
41
+ """
42
+ try:
43
+ cores = int(os.environ['SLURM_JOB_CPUS_PER_NODE'])
44
+ return cores
45
+ except ValueError:
46
+ try:
47
+ str_cores = str(os.environ['SLURM_JOB_CPUS_PER_NODE'])
48
+ temp = str_cores.split('(', 1)
49
+ cpus = int(temp[0])
50
+ str_nodes = temp[1]
51
+ temp = str_nodes.split('x', 1)
52
+ str_temp = str(temp[1]).split(')', 1)
53
+ nodes = int(str_temp[0])
54
+ cores = cpus * nodes
55
+ return cores
56
+ except ValueError:
57
+ return False
58
+ except KeyError:
59
+ return False
60
+
61
+ num_cores = __get_slurm_cores__()
62
+ if not num_cores:
63
+ num_cores = mp.cpu_count()
64
+ return int(num_cores)
65
+
66
+
67
+ def verify_path(a_path):
68
+ if not a_path:
69
+ return False, "No folder/file selected."
70
+
71
+ # Convert QML "file:///" path format to a proper OS path
72
+ if a_path.startswith("file:///"):
73
+ if sys.platform.startswith("win"):
74
+ # Windows Fix (remove extra '/')
75
+ a_path = a_path[8:]
76
+ else:
77
+ # macOS/Linux (remove "file://")
78
+ a_path = a_path[7:]
79
+
80
+ # Normalize the path
81
+ a_path = os.path.normpath(a_path)
82
+
83
+ if not os.path.exists(a_path):
84
+ return False, f"File/Folder in {a_path} does not exist. Try again."
85
+ return True, a_path
86
+
87
+
88
+ def install_package(package):
89
+ try:
90
+ subprocess.check_call([sys.executable, "-m", "pip", "install", package])
91
+ logging.info(f"Successfully installed {package}", extra={'user': 'SGT Logs'})
92
+ except subprocess.CalledProcessError:
93
+ logging.info(f"Failed to install {package}: ", extra={'user': 'SGT Logs'})
94
+
95
+
96
+ def detect_cuda_version():
97
+ """Check if CUDA is installed and return its version."""
98
+ try:
99
+ output = subprocess.check_output(['nvcc', '--version']).decode()
100
+ if 'release 12' in output:
101
+ return '12'
102
+ elif 'release 11' in output:
103
+ return '11'
104
+ else:
105
+ return None
106
+ except (subprocess.CalledProcessError, FileNotFoundError):
107
+ logging.info(f"Please install 'NVIDIA GPU Computing Toolkit' via: https://developer.nvidia.com/cuda-downloads", extra={'user': 'SGT Logs'})
108
+ return None
109
+
110
+
111
+ """
112
+ def detect_cuda_and_install_cupy():
113
+ try:
114
+ import cupy
115
+ logging.info(f"CuPy is already installed: {cupy.__version__}", extra={'user': 'SGT Logs'})
116
+ return
117
+ except ImportError:
118
+ logging.info("CuPy is not installed.", extra={'user': 'SGT Logs'})
119
+
120
+ def is_connected(host="8.8.8.8", port=53, timeout=3):
121
+ # Check if the system has an active internet connection.
122
+ try:
123
+ socket.setdefaulttimeout(timeout)
124
+ socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port))
125
+ return True
126
+ except socket.error:
127
+ return False
128
+
129
+ if not is_connected():
130
+ logging.info("No internet connection. Cannot install CuPy.", extra={'user': 'SGT Logs'})
131
+ return
132
+
133
+ # Handle macOS (Apple Silicon) - CPU only
134
+ if platform.system() == "Darwin" and platform.processor().startswith("arm"):
135
+ logging.info("Detected MacOS with Apple Silicon (M1/M2/M3). Installing CPU-only version of CuPy.", extra={'user': 'SGT Logs'})
136
+ # install_package('cupy') # CPU-only version
137
+ return
138
+
139
+ # Handle CUDA systems (Linux/Windows with GPU)
140
+ cuda_version = detect_cuda_version()
141
+
142
+ if cuda_version:
143
+ logging.info(f"CUDA detected: {cuda_version}", extra={'user': 'SGT Logs'})
144
+ if cuda_version == '12':
145
+ install_package('cupy-cuda12x')
146
+ elif cuda_version == '11':
147
+ install_package('cupy-cuda11x')
148
+ else:
149
+ logging.info("CUDA version not supported. Installing CPU-only CuPy.", extra={'user': 'SGT Logs'})
150
+ install_package('cupy')
151
+ else:
152
+ # No CUDA found, fall back to the CPU-only version
153
+ logging.info("CUDA not found. Installing CPU-only CuPy.", extra={'user': 'SGT Logs'})
154
+ install_package('cupy')
155
+
156
+ # Proceed with installation if connected
157
+ cuda_version = detect_cuda_version()
158
+ if cuda_version == '12':
159
+ install_package('cupy-cuda12x')
160
+ elif cuda_version == '11':
161
+ install_package('cupy-cuda11x')
162
+ else:
163
+ logging.info("No CUDA detected or NVIDIA GPU Toolkit not installed. Installing CPU-only CuPy.", extra={'user': 'SGT Logs'})
164
+ install_package('cupy')
165
+ """
166
+
167
+
168
+ def write_txt_file(data: str, path: LiteralString | str | bytes, wr=True):
169
+ """Description
170
+ Writes data into a txt file.
171
+
172
+ :param data: Information to be written
173
+ :param path: name of the file and storage path
174
+ :param wr: writes data into file if True
175
+ :return:
176
+ """
177
+ if wr:
178
+ with open(path, 'w') as f:
179
+ f.write(data)
180
+ f.close()
181
+ else:
182
+ pass
183
+
184
+
185
+ def write_csv_file(csv_file: LiteralString | str | bytes, column_tiles: list, data):
186
+ """
187
+ Write data to a csv file.
188
+ Args:
189
+ csv_file: name of the csv file
190
+ column_tiles: list of column names
191
+ data: list of data
192
+ Returns:
193
+
194
+ """
195
+ with open(csv_file, 'w', newline='') as csvfile:
196
+ writer = csv.writer(csvfile, delimiter=',')
197
+ writer.writerow(column_tiles)
198
+ for line in data:
199
+ line = str(line)
200
+ row = line.split(',')
201
+ try:
202
+ writer.writerow(row)
203
+ except csv.Error:
204
+ pass
205
+ csvfile.close()
206
+
207
+
208
+ def write_gsd_file(f_name: str, skeleton: np.ndarray):
209
+ """
210
+ A function that writes graph particles to a GSD file. Visualize with OVITO software.
211
+
212
+ :param f_name: gsd.hoomd file name
213
+ :param skeleton: skimage.morphology skeleton
214
+ """
215
+ # pos_count = int(sum(skeleton.ravel()))
216
+ particle_positions = np.asarray(np.where(np.asarray(skeleton) != 0)).T
217
+ with gsd.hoomd.open(name=f_name, mode="w") as f:
218
+ s = gsd.hoomd.Frame()
219
+ s.particles.N = len(particle_positions) # OR pos_count
220
+ s.particles.position = particle_positions
221
+ s.particles.types = ["A"]
222
+ s.particles.typeid = ["0"] * s.particles.N
223
+ f.append(s)
224
+
225
+
226
+ def img_to_base64(img: MatLike | Image.Image):
227
+ """ Converts a Numpy/OpenCV or PIL image to a base64 encoded string."""
228
+ if img is None:
229
+ return None
230
+
231
+ if type(img) == np.ndarray:
232
+ return opencv_to_base64(img)
233
+
234
+ if type(img) == Image.Image:
235
+ # Convert to numpy, apply safe conversion
236
+ np_img = np.array(img)
237
+ img_norm = safe_uint8_image(np_img)
238
+ return opencv_to_base64(img_norm)
239
+
240
+ return None
241
+
242
+
243
+ def opencv_to_base64(img_arr: MatLike):
244
+ """Convert an OpenCV/Numpy image to a base64 string."""
245
+ success, encoded_img = cv2.imencode('.png', img_arr)
246
+ if success:
247
+ buffer = io.BytesIO(encoded_img.tobytes())
248
+ buffer.seek(0)
249
+ base64_data = base64.b64encode(buffer.getvalue()).decode("utf-8")
250
+ return base64_data
251
+ else:
252
+ return None
253
+
254
+
255
+ def plot_to_opencv(fig: plt.Figure):
256
+ """Convert a Matplotlib figure to an OpenCV BGR image (Numpy array), retaining colors."""
257
+ if fig:
258
+ buf = io.BytesIO()
259
+ fig.savefig(buf, format='png', bbox_inches='tight')
260
+ buf.seek(0)
261
+
262
+ # Convert bytes to a Numpy array
263
+ img_array = np.frombuffer(buf.getvalue(), dtype=np.uint8)
264
+ buf.close()
265
+
266
+ # Decode image as BGR (color), discard alpha channel
267
+ img_cv = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
268
+ return img_cv
269
+ return None
270
+
271
+
272
+ def safe_uint8_image(img: MatLike):
273
+ """
274
+ Converts an image to uint8 safely:
275
+ - If already uint8, returns as is.
276
+ - If float or other type, normalizes to 0–255 and converts to uint8.
277
+ """
278
+ if img.dtype == np.uint8:
279
+ return img
280
+
281
+ # Handle float or other types
282
+ min_val = np.min(img)
283
+ max_val = np.max(img)
284
+
285
+ if min_val == max_val:
286
+ # Avoid divide by zero; return constant grayscale
287
+ return np.full(img.shape, 0 if min_val == 0 else 255, dtype=np.uint8)
288
+
289
+ # Normalize to 0–255
290
+ norm_img = ((img - min_val) / (max_val - min_val)) * 255.0
291
+ return norm_img.astype(np.uint8)