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.
- StructuralGT/__init__.py +31 -0
- StructuralGT/apps/__init__.py +0 -0
- StructuralGT/apps/cli_main.py +258 -0
- StructuralGT/apps/gui_main.py +69 -0
- StructuralGT/apps/gui_mcw/__init__.py +0 -0
- StructuralGT/apps/gui_mcw/checkbox_model.py +91 -0
- StructuralGT/apps/gui_mcw/controller.py +1073 -0
- StructuralGT/apps/gui_mcw/image_provider.py +74 -0
- StructuralGT/apps/gui_mcw/imagegrid_model.py +75 -0
- StructuralGT/apps/gui_mcw/qthread_worker.py +102 -0
- StructuralGT/apps/gui_mcw/table_model.py +79 -0
- StructuralGT/apps/gui_mcw/tree_model.py +154 -0
- StructuralGT/apps/sgt_qml/CenterMainContent.qml +19 -0
- StructuralGT/apps/sgt_qml/LeftContent.qml +48 -0
- StructuralGT/apps/sgt_qml/MainWindow.qml +762 -0
- StructuralGT/apps/sgt_qml/RightLoggingPanel.qml +125 -0
- StructuralGT/apps/sgt_qml/assets/icons/.DS_Store +0 -0
- StructuralGT/apps/sgt_qml/assets/icons/back_icon.png +0 -0
- StructuralGT/apps/sgt_qml/assets/icons/brightness_icon.png +0 -0
- StructuralGT/apps/sgt_qml/assets/icons/cancel_icon.png +0 -0
- StructuralGT/apps/sgt_qml/assets/icons/crop_icon.png +0 -0
- StructuralGT/apps/sgt_qml/assets/icons/edit_icon.png +0 -0
- StructuralGT/apps/sgt_qml/assets/icons/graph_icon.png +0 -0
- StructuralGT/apps/sgt_qml/assets/icons/hide_panel.png +0 -0
- StructuralGT/apps/sgt_qml/assets/icons/next_icon.png +0 -0
- StructuralGT/apps/sgt_qml/assets/icons/notify_icon.png +0 -0
- StructuralGT/apps/sgt_qml/assets/icons/rescale_icon.png +0 -0
- StructuralGT/apps/sgt_qml/assets/icons/show_panel.png +0 -0
- StructuralGT/apps/sgt_qml/assets/icons/square_icon.png +0 -0
- StructuralGT/apps/sgt_qml/assets/icons/undo_icon.png +0 -0
- StructuralGT/apps/sgt_qml/components/ImageFilters.qml +82 -0
- StructuralGT/apps/sgt_qml/components/ImageProperties.qml +112 -0
- StructuralGT/apps/sgt_qml/components/ProjectNav.qml +127 -0
- StructuralGT/apps/sgt_qml/widgets/BinaryFilterWidget.qml +151 -0
- StructuralGT/apps/sgt_qml/widgets/BrightnessControlWidget.qml +103 -0
- StructuralGT/apps/sgt_qml/widgets/CreateProjectWidget.qml +112 -0
- StructuralGT/apps/sgt_qml/widgets/GTWidget.qml +94 -0
- StructuralGT/apps/sgt_qml/widgets/GraphComputeWidget.qml +77 -0
- StructuralGT/apps/sgt_qml/widgets/GraphExtractWidget.qml +175 -0
- StructuralGT/apps/sgt_qml/widgets/GraphPropertyWidget.qml +77 -0
- StructuralGT/apps/sgt_qml/widgets/ImageFilterWidget.qml +137 -0
- StructuralGT/apps/sgt_qml/widgets/ImagePropertyWidget.qml +78 -0
- StructuralGT/apps/sgt_qml/widgets/ImageViewWidget.qml +585 -0
- StructuralGT/apps/sgt_qml/widgets/MenuBarWidget.qml +137 -0
- StructuralGT/apps/sgt_qml/widgets/MicroscopyPropertyWidget.qml +80 -0
- StructuralGT/apps/sgt_qml/widgets/ProjectWidget.qml +141 -0
- StructuralGT/apps/sgt_qml/widgets/RescaleControlWidget.qml +83 -0
- StructuralGT/apps/sgt_qml/widgets/RibbonWidget.qml +406 -0
- StructuralGT/apps/sgt_qml/widgets/StatusBarWidget.qml +173 -0
- StructuralGT/compute/__init__.py +0 -0
- StructuralGT/compute/c_lang/include/sgt_base.h +21 -0
- StructuralGT/compute/graph_analyzer.py +1499 -0
- StructuralGT/entrypoints.py +49 -0
- StructuralGT/imaging/__init__.py +0 -0
- StructuralGT/imaging/base_image.py +403 -0
- StructuralGT/imaging/image_processor.py +780 -0
- StructuralGT/modules.py +29 -0
- StructuralGT/networks/__init__.py +0 -0
- StructuralGT/networks/fiber_network.py +490 -0
- StructuralGT/networks/graph_skeleton.py +425 -0
- StructuralGT/networks/sknw_mod.py +199 -0
- StructuralGT/utils/__init__.py +0 -0
- StructuralGT/utils/config_loader.py +244 -0
- StructuralGT/utils/configs.ini +97 -0
- StructuralGT/utils/progress_update.py +67 -0
- StructuralGT/utils/sgt_utils.py +291 -0
- sgtlib-3.3.9.dist-info/METADATA +789 -0
- sgtlib-3.3.9.dist-info/RECORD +72 -0
- sgtlib-3.3.9.dist-info/WHEEL +5 -0
- sgtlib-3.3.9.dist-info/entry_points.txt +3 -0
- sgtlib-3.3.9.dist-info/licenses/LICENSE +674 -0
- 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)
|