redbirdpy 0.1.0__py3-none-any.whl → 0.2.0__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.
- redbirdpy/__init__.py +1 -1
- redbirdpy/solver.py +183 -23
- {redbirdpy-0.1.0.dist-info → redbirdpy-0.2.0.dist-info}/METADATA +25 -27
- redbirdpy-0.2.0.dist-info/RECORD +13 -0
- redbirdpy-0.1.0.dist-info/RECORD +0 -13
- {redbirdpy-0.1.0.dist-info → redbirdpy-0.2.0.dist-info}/WHEEL +0 -0
- {redbirdpy-0.1.0.dist-info → redbirdpy-0.2.0.dist-info}/licenses/LICENSE.txt +0 -0
- {redbirdpy-0.1.0.dist-info → redbirdpy-0.2.0.dist-info}/top_level.txt +0 -0
- {redbirdpy-0.1.0.dist-info → redbirdpy-0.2.0.dist-info}/zip-safe +0 -0
redbirdpy/__init__.py
CHANGED
redbirdpy/solver.py
CHANGED
|
@@ -3,11 +3,11 @@ Redbird Solver Module - Linear system solvers for FEM.
|
|
|
3
3
|
|
|
4
4
|
Provides:
|
|
5
5
|
femsolve: Main solver interface with automatic method selection
|
|
6
|
-
|
|
6
|
+
solverinfo: Query available solver backends
|
|
7
7
|
|
|
8
8
|
Supported solvers:
|
|
9
9
|
Direct: pardiso, umfpack, cholmod, superlu
|
|
10
|
-
Iterative: blqmr, cg, cg+amg, gmres, bicgstab
|
|
10
|
+
Iterative: blqmr, cg, cg+amg, gmres, bicgstab, qmr, minres
|
|
11
11
|
|
|
12
12
|
Dependencies:
|
|
13
13
|
- blocksolver: For BLQMR iterative solver (complex symmetric systems)
|
|
@@ -15,16 +15,17 @@ Dependencies:
|
|
|
15
15
|
|
|
16
16
|
__all__ = [
|
|
17
17
|
"femsolve",
|
|
18
|
-
"
|
|
18
|
+
"solverinfo",
|
|
19
19
|
]
|
|
20
20
|
|
|
21
21
|
import numpy as np
|
|
22
22
|
from scipy import sparse
|
|
23
|
-
from scipy.sparse.linalg import spsolve, cg, gmres, bicgstab, splu
|
|
23
|
+
from scipy.sparse.linalg import spsolve, cg, gmres, bicgstab, qmr, minres, splu
|
|
24
24
|
from typing import Dict, Tuple, Optional, Union, List, Any
|
|
25
25
|
import warnings
|
|
26
|
-
from
|
|
26
|
+
from multiprocessing import Pool
|
|
27
27
|
import multiprocessing
|
|
28
|
+
import sys
|
|
28
29
|
|
|
29
30
|
# =============================================================================
|
|
30
31
|
# Solver Backend Detection
|
|
@@ -40,6 +41,21 @@ _HAS_BLQMR = False
|
|
|
40
41
|
_pardiso_solve = None
|
|
41
42
|
_pardiso_factorized = None
|
|
42
43
|
|
|
44
|
+
|
|
45
|
+
# Multiprocessing context - use 'spawn' for Python 3.14+ compatibility
|
|
46
|
+
# 'spawn' is safer and more portable, but 'fork' is faster on Unix (Python < 3.14)
|
|
47
|
+
def _get_mp_context():
|
|
48
|
+
"""Get appropriate multiprocessing context based on Python version and platform."""
|
|
49
|
+
# Python 3.14+ or macOS: always use spawn for safety
|
|
50
|
+
if sys.version_info >= (3, 14) or sys.platform == "darwin":
|
|
51
|
+
return multiprocessing.get_context("spawn")
|
|
52
|
+
# Older Python on Linux: try fork first (faster), fallback to spawn
|
|
53
|
+
try:
|
|
54
|
+
return multiprocessing.get_context("fork")
|
|
55
|
+
except ValueError:
|
|
56
|
+
return multiprocessing.get_context("spawn")
|
|
57
|
+
|
|
58
|
+
|
|
43
59
|
try:
|
|
44
60
|
from pypardiso import spsolve as pardiso_solve, factorized as pardiso_factorized
|
|
45
61
|
|
|
@@ -121,11 +137,18 @@ def _solve_blqmr_batch(args):
|
|
|
121
137
|
start_col,
|
|
122
138
|
) = args
|
|
123
139
|
|
|
124
|
-
# Import
|
|
125
|
-
|
|
140
|
+
# Import dependencies in worker process
|
|
141
|
+
try:
|
|
142
|
+
from blocksolver import blqmr as worker_blqmr
|
|
143
|
+
from scipy import sparse as worker_sparse
|
|
144
|
+
import numpy as worker_np
|
|
145
|
+
except ImportError as e:
|
|
146
|
+
raise ImportError(f"Worker process missing required imports: {e}")
|
|
126
147
|
|
|
127
148
|
# Reconstruct sparse matrix in worker process
|
|
128
|
-
A =
|
|
149
|
+
A = worker_sparse.csc_matrix(
|
|
150
|
+
(A_data, A_indices, A_indptr), shape=A_shape, dtype=A_dtype
|
|
151
|
+
)
|
|
129
152
|
|
|
130
153
|
result = worker_blqmr(
|
|
131
154
|
A,
|
|
@@ -146,7 +169,7 @@ def _solve_blqmr_batch(args):
|
|
|
146
169
|
|
|
147
170
|
def _solve_iterative_column(args):
|
|
148
171
|
"""
|
|
149
|
-
Worker function for parallel iterative solving (gmres, bicgstab, cg).
|
|
172
|
+
Worker function for parallel iterative solving (gmres, bicgstab, cg, qmr, minres).
|
|
150
173
|
|
|
151
174
|
Solves a single RHS column using the specified iterative method.
|
|
152
175
|
"""
|
|
@@ -164,15 +187,17 @@ def _solve_iterative_column(args):
|
|
|
164
187
|
use_amg,
|
|
165
188
|
) = args
|
|
166
189
|
|
|
167
|
-
|
|
190
|
+
# Import dependencies in worker process
|
|
191
|
+
from scipy.sparse.linalg import gmres, bicgstab, cg, qmr, minres
|
|
168
192
|
from scipy import sparse
|
|
193
|
+
import numpy as np
|
|
169
194
|
|
|
170
195
|
# Reconstruct sparse matrix in worker process
|
|
171
196
|
A = sparse.csc_matrix((A_data, A_indices, A_indptr), shape=A_shape, dtype=A_dtype)
|
|
172
197
|
|
|
173
198
|
# Setup preconditioner if AMG requested
|
|
174
199
|
M = None
|
|
175
|
-
if use_amg and solver_type
|
|
200
|
+
if use_amg and solver_type in ("cg", "minres"):
|
|
176
201
|
try:
|
|
177
202
|
import pyamg
|
|
178
203
|
|
|
@@ -188,6 +213,10 @@ def _solve_iterative_column(args):
|
|
|
188
213
|
solver_func = bicgstab
|
|
189
214
|
elif solver_type == "cg":
|
|
190
215
|
solver_func = cg
|
|
216
|
+
elif solver_type == "qmr":
|
|
217
|
+
solver_func = qmr
|
|
218
|
+
elif solver_type == "minres":
|
|
219
|
+
solver_func = minres
|
|
191
220
|
else:
|
|
192
221
|
raise ValueError(f"Unknown solver type: {solver_type}")
|
|
193
222
|
|
|
@@ -252,12 +281,14 @@ def _blqmr_parallel(
|
|
|
252
281
|
)
|
|
253
282
|
)
|
|
254
283
|
|
|
255
|
-
# Solve in parallel
|
|
284
|
+
# Solve in parallel with proper context
|
|
256
285
|
x = np.zeros((n, ncol), dtype=out_dtype)
|
|
257
286
|
max_flag = 0
|
|
258
287
|
|
|
259
|
-
|
|
260
|
-
|
|
288
|
+
# Get appropriate multiprocessing context
|
|
289
|
+
ctx = _get_mp_context()
|
|
290
|
+
with Pool(processes=nthread) as pool:
|
|
291
|
+
results = pool.map(_solve_blqmr_batch, batches)
|
|
261
292
|
|
|
262
293
|
for batch_id, start_col, x_batch, batch_flag, niter, relres in results:
|
|
263
294
|
end_col = start_col + x_batch.shape[1]
|
|
@@ -269,7 +300,7 @@ def _blqmr_parallel(
|
|
|
269
300
|
|
|
270
301
|
if verbose:
|
|
271
302
|
print(
|
|
272
|
-
f"blqmr [{start_col+1}:{end_col}] (worker {batch_id}): "
|
|
303
|
+
f"blqmr [{start_col + 1}:{end_col}] (worker {batch_id}): "
|
|
273
304
|
f"iter={niter}, relres={relres:.2e}, flag={batch_flag}"
|
|
274
305
|
)
|
|
275
306
|
|
|
@@ -288,7 +319,7 @@ def _iterative_parallel(
|
|
|
288
319
|
verbose: bool = False,
|
|
289
320
|
) -> Tuple[np.ndarray, int]:
|
|
290
321
|
"""
|
|
291
|
-
Solve iterative methods (gmres, bicgstab, cg) in parallel.
|
|
322
|
+
Solve iterative methods (gmres, bicgstab, cg, qmr, minres) in parallel.
|
|
292
323
|
|
|
293
324
|
Each RHS column is solved independently in a separate process.
|
|
294
325
|
"""
|
|
@@ -326,12 +357,14 @@ def _iterative_parallel(
|
|
|
326
357
|
)
|
|
327
358
|
)
|
|
328
359
|
|
|
329
|
-
# Solve in parallel
|
|
360
|
+
# Solve in parallel with proper context
|
|
330
361
|
x = np.zeros((n, ncol), dtype=out_dtype)
|
|
331
362
|
max_flag = 0
|
|
332
363
|
|
|
333
|
-
|
|
334
|
-
|
|
364
|
+
# Get appropriate multiprocessing context
|
|
365
|
+
ctx = _get_mp_context()
|
|
366
|
+
with Pool(processes=nthread) as pool:
|
|
367
|
+
results = pool.map(_solve_iterative_column, tasks)
|
|
335
368
|
|
|
336
369
|
for col_idx, x_col, info in results:
|
|
337
370
|
x[:, col_idx] = x_col
|
|
@@ -339,7 +372,7 @@ def _iterative_parallel(
|
|
|
339
372
|
|
|
340
373
|
if verbose:
|
|
341
374
|
status = "converged" if info == 0 else f"flag={info}"
|
|
342
|
-
print(f"{solver_type} [col {col_idx+1}]: {status}")
|
|
375
|
+
print(f"{solver_type} [col {col_idx + 1}]: {status}")
|
|
343
376
|
|
|
344
377
|
return x, max_flag
|
|
345
378
|
|
|
@@ -376,13 +409,16 @@ def femsolve(
|
|
|
376
409
|
'cg+amg': CG with AMG preconditioner (SPD, requires pyamg)
|
|
377
410
|
'gmres': GMRES
|
|
378
411
|
'bicgstab': BiCGSTAB
|
|
412
|
+
'qmr': Quasi-Minimal Residual
|
|
413
|
+
'minres': MINRES (symmetric/Hermitian only)
|
|
414
|
+
'minres+amg': MINRES with AMG preconditioner (symmetric, requires pyamg)
|
|
379
415
|
**kwargs : dict
|
|
380
416
|
tol : float - convergence tolerance (default: 1e-10)
|
|
381
417
|
maxiter : int - maximum iterations (default: 1000)
|
|
382
418
|
rhsblock : int - block size for blqmr (default: 8)
|
|
383
419
|
nthread : int - parallel workers for iterative solvers
|
|
384
420
|
(default: min(ncol, cpu_count), set to 1 to disable)
|
|
385
|
-
Supported by: blqmr, gmres, bicgstab, cg, cg+amg
|
|
421
|
+
Supported by: blqmr, gmres, bicgstab, cg, cg+amg, qmr, minres, minres+amg
|
|
386
422
|
verbose : bool - print solver progress (default: False)
|
|
387
423
|
spd : bool - True if matrix is symmetric positive definite
|
|
388
424
|
M, M1, M2 : preconditioners (disables parallel for gmres/bicgstab/cg)
|
|
@@ -468,6 +504,11 @@ def femsolve(
|
|
|
468
504
|
# Solve real system (batch solve for all RHS at once)
|
|
469
505
|
x_real = _pardiso_solve(A_real, rhs_real)
|
|
470
506
|
|
|
507
|
+
# Handle both 1D and 2D results from pardiso
|
|
508
|
+
if x_real.ndim == 1:
|
|
509
|
+
# Single RHS case - reshape to 2D for consistent indexing
|
|
510
|
+
x_real = x_real.reshape(-1, 1)
|
|
511
|
+
|
|
471
512
|
# Reconstruct complex solution: x = x_r + j*x_i
|
|
472
513
|
x = x_real[:n, :] + 1j * x_real[n:, :]
|
|
473
514
|
else:
|
|
@@ -782,6 +823,121 @@ def femsolve(
|
|
|
782
823
|
status = "converged" if info == 0 else f"flag={info}"
|
|
783
824
|
print(f"bicgstab [col {i+1}]: {status}")
|
|
784
825
|
|
|
826
|
+
elif method == "qmr":
|
|
827
|
+
nthread = kwargs.get("nthread", None)
|
|
828
|
+
if nthread is None:
|
|
829
|
+
nthread = min(ncol, multiprocessing.cpu_count())
|
|
830
|
+
M = kwargs.get("M", None)
|
|
831
|
+
|
|
832
|
+
if nthread > 1 and ncol > 1 and M is None:
|
|
833
|
+
# Parallel solving (without custom preconditioner)
|
|
834
|
+
x, flag = _iterative_parallel(
|
|
835
|
+
Amat,
|
|
836
|
+
rhs,
|
|
837
|
+
"qmr",
|
|
838
|
+
tol=tol,
|
|
839
|
+
maxiter=maxiter,
|
|
840
|
+
nthread=nthread,
|
|
841
|
+
use_amg=False,
|
|
842
|
+
verbose=verbose,
|
|
843
|
+
)
|
|
844
|
+
else:
|
|
845
|
+
# Sequential solving
|
|
846
|
+
for i in range(ncol):
|
|
847
|
+
if np.any(rhs[:, i] != 0):
|
|
848
|
+
try:
|
|
849
|
+
x[:, i], info = qmr(
|
|
850
|
+
Amat, rhs[:, i], M1=M, rtol=tol, maxiter=maxiter
|
|
851
|
+
)
|
|
852
|
+
except TypeError:
|
|
853
|
+
x[:, i], info = qmr(
|
|
854
|
+
Amat, rhs[:, i], M1=M, tol=tol, maxiter=maxiter
|
|
855
|
+
)
|
|
856
|
+
flag = max(flag, info)
|
|
857
|
+
if verbose:
|
|
858
|
+
status = "converged" if info == 0 else f"flag={info}"
|
|
859
|
+
print(f"qmr [col {i+1}]: {status}")
|
|
860
|
+
|
|
861
|
+
elif method == "minres+amg":
|
|
862
|
+
if not _HAS_AMG:
|
|
863
|
+
warnings.warn("pyamg not available, falling back to MINRES")
|
|
864
|
+
return femsolve(Amat, rhs, method="minres", **kwargs)
|
|
865
|
+
|
|
866
|
+
if is_complex:
|
|
867
|
+
warnings.warn("minres+amg doesn't support complex, falling back to gmres")
|
|
868
|
+
return femsolve(Amat, rhs, method="gmres", **kwargs)
|
|
869
|
+
|
|
870
|
+
nthread = kwargs.get("nthread", None)
|
|
871
|
+
if nthread is None:
|
|
872
|
+
nthread = min(ncol, multiprocessing.cpu_count())
|
|
873
|
+
|
|
874
|
+
if nthread > 1 and ncol > 1:
|
|
875
|
+
# Parallel solving
|
|
876
|
+
x, flag = _iterative_parallel(
|
|
877
|
+
Amat,
|
|
878
|
+
rhs,
|
|
879
|
+
"minres",
|
|
880
|
+
tol=tol,
|
|
881
|
+
maxiter=maxiter,
|
|
882
|
+
nthread=nthread,
|
|
883
|
+
use_amg=True,
|
|
884
|
+
verbose=verbose,
|
|
885
|
+
)
|
|
886
|
+
else:
|
|
887
|
+
# Sequential solving
|
|
888
|
+
ml = _pyamg.smoothed_aggregation_solver(Amat.tocsr())
|
|
889
|
+
M = ml.aspreconditioner()
|
|
890
|
+
|
|
891
|
+
for i in range(ncol):
|
|
892
|
+
if np.any(rhs[:, i] != 0):
|
|
893
|
+
try:
|
|
894
|
+
x[:, i], info = minres(
|
|
895
|
+
Amat, rhs[:, i], M=M, rtol=tol, maxiter=maxiter
|
|
896
|
+
)
|
|
897
|
+
except TypeError:
|
|
898
|
+
x[:, i], info = minres(
|
|
899
|
+
Amat, rhs[:, i], M=M, tol=tol, maxiter=maxiter
|
|
900
|
+
)
|
|
901
|
+
flag = max(flag, info)
|
|
902
|
+
if verbose:
|
|
903
|
+
status = "converged" if info == 0 else f"flag={info}"
|
|
904
|
+
print(f"minres+amg [col {i+1}]: {status}")
|
|
905
|
+
|
|
906
|
+
elif method == "minres":
|
|
907
|
+
nthread = kwargs.get("nthread", None)
|
|
908
|
+
if nthread is None:
|
|
909
|
+
nthread = min(ncol, multiprocessing.cpu_count())
|
|
910
|
+
M = kwargs.get("M", None)
|
|
911
|
+
|
|
912
|
+
if nthread > 1 and ncol > 1 and M is None:
|
|
913
|
+
# Parallel solving (without custom preconditioner)
|
|
914
|
+
x, flag = _iterative_parallel(
|
|
915
|
+
Amat,
|
|
916
|
+
rhs,
|
|
917
|
+
"minres",
|
|
918
|
+
tol=tol,
|
|
919
|
+
maxiter=maxiter,
|
|
920
|
+
nthread=nthread,
|
|
921
|
+
use_amg=False,
|
|
922
|
+
verbose=verbose,
|
|
923
|
+
)
|
|
924
|
+
else:
|
|
925
|
+
# Sequential solving
|
|
926
|
+
for i in range(ncol):
|
|
927
|
+
if np.any(rhs[:, i] != 0):
|
|
928
|
+
try:
|
|
929
|
+
x[:, i], info = minres(
|
|
930
|
+
Amat, rhs[:, i], M=M, rtol=tol, maxiter=maxiter
|
|
931
|
+
)
|
|
932
|
+
except TypeError:
|
|
933
|
+
x[:, i], info = minres(
|
|
934
|
+
Amat, rhs[:, i], M=M, tol=tol, maxiter=maxiter
|
|
935
|
+
)
|
|
936
|
+
flag = max(flag, info)
|
|
937
|
+
if verbose:
|
|
938
|
+
status = "converged" if info == 0 else f"flag={info}"
|
|
939
|
+
print(f"minres [col {i+1}]: {status}")
|
|
940
|
+
|
|
785
941
|
else:
|
|
786
942
|
raise ValueError(f"Unknown solver: {method}")
|
|
787
943
|
|
|
@@ -792,7 +948,7 @@ def femsolve(
|
|
|
792
948
|
return x, flag
|
|
793
949
|
|
|
794
950
|
|
|
795
|
-
def
|
|
951
|
+
def solverinfo() -> dict:
|
|
796
952
|
"""Return information about available solvers."""
|
|
797
953
|
info = {
|
|
798
954
|
"direct_solver": _DIRECT_SOLVER,
|
|
@@ -802,7 +958,8 @@ def get_solver_info() -> dict:
|
|
|
802
958
|
"has_amg": _HAS_AMG,
|
|
803
959
|
"has_blqmr": _HAS_BLQMR,
|
|
804
960
|
"complex_direct": "umfpack" if _HAS_UMFPACK else "superlu",
|
|
805
|
-
"complex_iterative": ["gmres", "bicgstab"],
|
|
961
|
+
"complex_iterative": ["gmres", "bicgstab", "qmr"],
|
|
962
|
+
"symmetric_iterative": ["cg", "minres"],
|
|
806
963
|
"cpu_count": multiprocessing.cpu_count(),
|
|
807
964
|
}
|
|
808
965
|
|
|
@@ -811,4 +968,7 @@ def get_solver_info() -> dict:
|
|
|
811
968
|
info["blqmr_has_numba"] = HAS_NUMBA
|
|
812
969
|
info["complex_iterative"].insert(0, "blqmr")
|
|
813
970
|
|
|
971
|
+
if _HAS_AMG:
|
|
972
|
+
info["amg_iterative"] = ["cg+amg", "minres+amg"]
|
|
973
|
+
|
|
814
974
|
return info
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: redbirdpy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A Python toolbox for Diffuse Optical Tomography (DOT) and Near-Infrared Spectroscopy (NIRS)
|
|
5
5
|
Home-page: https://github.com/fangq/redbirdpy
|
|
6
6
|
Author: Qianqian Fang
|
|
@@ -14,7 +14,7 @@ Project-URL: Repository, https://github.com/fangq/redbirdpy
|
|
|
14
14
|
Project-URL: Bug Tracker, https://github.com/fangq/redbirdpy/issues
|
|
15
15
|
Keywords: Diffuse Optical Tomography,DOT,NIRS,Near-Infrared Spectroscopy,FEM,Finite Element Method,Biomedical Optics,Image Reconstruction,Inverse Problem,Photon Migration,Diffusion Equation,Tissue Optics
|
|
16
16
|
Platform: any
|
|
17
|
-
Classifier: Development Status ::
|
|
17
|
+
Classifier: Development Status :: 4 - Beta
|
|
18
18
|
Classifier: Intended Audience :: Science/Research
|
|
19
19
|
Classifier: Intended Audience :: Developers
|
|
20
20
|
Classifier: Intended Audience :: Healthcare Industry
|
|
@@ -37,14 +37,12 @@ Description-Content-Type: text/markdown
|
|
|
37
37
|
License-File: LICENSE.txt
|
|
38
38
|
Requires-Dist: numpy>=1.15.0
|
|
39
39
|
Requires-Dist: scipy>=1.0.0
|
|
40
|
-
|
|
41
|
-
Requires-Dist: iso2mesh>=0.5.4; extra == "mesh"
|
|
40
|
+
Requires-Dist: iso2mesh>=0.5.0
|
|
42
41
|
Provides-Extra: dev
|
|
43
42
|
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
44
43
|
Requires-Dist: pytest-cov>=2.0; extra == "dev"
|
|
45
44
|
Requires-Dist: flake8>=3.0; extra == "dev"
|
|
46
45
|
Provides-Extra: all
|
|
47
|
-
Requires-Dist: iso2mesh>=0.5.4; extra == "all"
|
|
48
46
|
Requires-Dist: matplotlib>=3.0; extra == "all"
|
|
49
47
|
Dynamic: author
|
|
50
48
|
Dynamic: home-page
|
|
@@ -53,13 +51,13 @@ Dynamic: maintainer
|
|
|
53
51
|
Dynamic: platform
|
|
54
52
|
Dynamic: requires-python
|
|
55
53
|
|
|
56
|
-

|
|
57
55
|
|
|
58
56
|
# RedbirdPy - A Model-Based Diffuse Optical Imaging Toolbox for Python
|
|
59
57
|
|
|
60
|
-
* **Copyright**: (C) Qianqian Fang (2005–
|
|
58
|
+
* **Copyright**: (C) Qianqian Fang (2005–2026) \<q.fang at neu.edu>
|
|
61
59
|
* **License**: GNU Public License V3 or later
|
|
62
|
-
* **Version**: 0.
|
|
60
|
+
* **Version**: 0.2.0 (Flamingo)
|
|
63
61
|
* **GitHub**: [https://github.com/fangq/redbirdpy](https://github.com/fangq/redbirdpy)
|
|
64
62
|
* **Acknowledgement**: This project is supported by the US National Institute of Health (NIH)
|
|
65
63
|
grant [R01-CA204443](https://reporter.nih.gov/project-details/10982160)
|
|
@@ -88,7 +86,7 @@ Dynamic: requires-python
|
|
|
88
86
|
|
|
89
87
|
## Introduction
|
|
90
88
|
|
|
91
|
-
**
|
|
89
|
+
**RedbirdPy** is a Python translation of the [Redbird MATLAB toolbox](https://github.com/fangq/redbird) for diffuse optical imaging (DOI) and diffuse optical tomography (DOT). It provides a fast, experimentally-validated forward solver for the diffusion equation using the finite-element method (FEM), along with advanced non-linear image reconstruction algorithms.
|
|
92
90
|
|
|
93
91
|
Redbird is the result of over two decades of active research in DOT and image reconstruction. It has been the core data analysis tool in numerous publications related to optical breast imaging, prior-guided reconstruction techniques, multi-modal imaging, and wide-field DOT systems.
|
|
94
92
|
|
|
@@ -104,7 +102,7 @@ Redbird is the result of over two decades of active research in DOT and image re
|
|
|
104
102
|
|
|
105
103
|
### Validation
|
|
106
104
|
|
|
107
|
-
The forward solver is carefully validated against Monte Carlo solvers
|
|
105
|
+
The forward solver is carefully validated against Monte Carlo solvers - **MCX** and **MMC**. The diffusion approximation is valid in high-scattering media where the reduced scattering coefficient (μs') is much greater than the absorption coefficient (μa).
|
|
108
106
|
|
|
109
107
|
---
|
|
110
108
|
|
|
@@ -112,15 +110,15 @@ The forward solver is carefully validated against Monte Carlo solvers—**MCX**
|
|
|
112
110
|
|
|
113
111
|
### Requirements
|
|
114
112
|
|
|
115
|
-
- Python 3.
|
|
113
|
+
- Python 3.6+
|
|
116
114
|
- NumPy
|
|
117
115
|
- SciPy
|
|
116
|
+
- Iso2Mesh
|
|
118
117
|
|
|
119
118
|
### Basic Installation
|
|
120
119
|
|
|
121
120
|
```bash
|
|
122
121
|
pip install numpy scipy
|
|
123
|
-
# For mesh generation
|
|
124
122
|
pip install iso2mesh # or from https://github.com/NeuroJSON/pyiso2mesh
|
|
125
123
|
```
|
|
126
124
|
|
|
@@ -131,7 +129,7 @@ pip install iso2mesh # or from https://github.com/NeuroJSON/pyiso2mesh
|
|
|
131
129
|
pip install numba # JIT compilation
|
|
132
130
|
|
|
133
131
|
# For accelerated solvers
|
|
134
|
-
pip install blocksolver # or from https://github.com/fangq/
|
|
132
|
+
pip install blocksolver # or from https://github.com/fangq/blit
|
|
135
133
|
|
|
136
134
|
# For other linear solvers
|
|
137
135
|
pip install pypardiso # Intel MKL PARDISO (fastest direct solver)
|
|
@@ -147,7 +145,7 @@ cd redbirdpy
|
|
|
147
145
|
pip install -e .
|
|
148
146
|
```
|
|
149
147
|
|
|
150
|
-
Or simply
|
|
148
|
+
Or simply import `redbirdpy` from inside the repository's top folder.
|
|
151
149
|
|
|
152
150
|
---
|
|
153
151
|
|
|
@@ -201,12 +199,12 @@ Redbird performs two main tasks:
|
|
|
201
199
|
|
|
202
200
|
Redbird supports four types of image reconstructions:
|
|
203
201
|
|
|
204
|
-
| Mode | Description
|
|
205
|
-
|
|
206
|
-
| **Bulk Fitting** | Estimate single set of properties for entire domain
|
|
207
|
-
| **Segmented** | One property set per labeled tissue segment ("hard-prior")
|
|
202
|
+
| Mode | Description |
|
|
203
|
+
|------|---------------------------------------------------------------|
|
|
204
|
+
| **Bulk Fitting** | Estimate single set of properties for the entire domain |
|
|
205
|
+
| **Segmented** | One property set per labeled tissue segment ("hard-prior") |
|
|
208
206
|
| **Soft-Prior** | Spatial priors as soft constraints (Laplacian, compositional) |
|
|
209
|
-
| **Unconstrained** | Independent properties per node with Tikhonov regularization
|
|
207
|
+
| **Unconstrained** | Independent properties per node with Tikhonov regularization |
|
|
210
208
|
|
|
211
209
|
---
|
|
212
210
|
|
|
@@ -265,9 +263,9 @@ Redbird supports four types of image reconstructions:
|
|
|
265
263
|
| Function | Description |
|
|
266
264
|
|----------|-------------|
|
|
267
265
|
| `femsolve(A, b, method)` | Solve linear system with auto-selection |
|
|
268
|
-
| `
|
|
266
|
+
| `solverinfo()` | Query available solver backends |
|
|
269
267
|
|
|
270
|
-
Supported solvers: `pardiso`, `umfpack`, `cholmod`, `superlu`, `blqmr`, `cg`, `cg+amg`, `gmres`, `bicgstab`
|
|
268
|
+
Supported solvers: `pardiso`, `umfpack`, `cholmod`, `superlu`, `blqmr`, `cg`, `cg+amg`, `gmres`, `minres`, `minres+amg`, `qmr`, `bicgstab`
|
|
271
269
|
|
|
272
270
|
### `redbirdpy.analytical` - Analytical Solutions
|
|
273
271
|
|
|
@@ -395,8 +393,8 @@ Configure similarly using `dettype`, `detparam1`, `detparam2`, `detpattern`.
|
|
|
395
393
|
|
|
396
394
|
```python
|
|
397
395
|
cfg['prop'] = {
|
|
398
|
-
'690':
|
|
399
|
-
'830':
|
|
396
|
+
'690': [[0, 0, 1, 1], [0.012, 1.1, 0, 1.37]],
|
|
397
|
+
'830': [[0, 0, 1, 1], [0.008, 0.9, 0, 1.37]]
|
|
400
398
|
}
|
|
401
399
|
```
|
|
402
400
|
|
|
@@ -498,7 +496,7 @@ recon['prop'] = np.tile(cfg['prop'][1,:], (recon['node'].shape[0], 1))
|
|
|
498
496
|
newrecon, resid = rb.run(cfg, recon, detphi0, lambda_=1e-4)[:2]
|
|
499
497
|
```
|
|
500
498
|
|
|
501
|
-
### Wide-Field Reconstruction
|
|
499
|
+
### Wide-Field Forward and Reconstruction
|
|
502
500
|
|
|
503
501
|
```python
|
|
504
502
|
# Create illumination patterns
|
|
@@ -585,12 +583,12 @@ If you use Redbird in your research, please cite:
|
|
|
585
583
|
|
|
586
584
|
## License
|
|
587
585
|
|
|
588
|
-
GNU General Public License v3.0 or later - see [LICENSE](LICENSE) file for details.
|
|
586
|
+
GNU General Public License v3.0 or later - see [LICENSE](LICENSE.txt) file for details.
|
|
589
587
|
|
|
590
588
|
## Author
|
|
591
589
|
|
|
592
590
|
**Qianqian Fang** (q.fang@neu.edu)
|
|
593
|
-
Computational Optics & Translational Imaging Lab
|
|
591
|
+
Computational Optics & Translational Imaging (COTI) Lab
|
|
594
592
|
Northeastern University
|
|
595
593
|
|
|
596
|
-
Python translation based on the [Redbird MATLAB toolbox](https://github.com/fangq/
|
|
594
|
+
Python translation based on the [Redbird MATLAB toolbox](https://github.com/fangq/redbird).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
redbirdpy/__init__.py,sha256=-NzQLn3eHX7KZBmJOUN9IxLitR9GcmBTkEyu8S_k5Yc,3379
|
|
2
|
+
redbirdpy/analytical.py,sha256=9mMTU8EwDbRn5JqPsFgSdh0LL4WXc4BaXQpsECl_Wg4,26681
|
|
3
|
+
redbirdpy/forward.py,sha256=Gtu7v41h_AVMQ5sfyMSDkOIzzYTP2vn0SoxUQDR_lS8,18785
|
|
4
|
+
redbirdpy/property.py,sha256=fb8tOc7TLS7h6GQ61GjAbEwawJb5kLFULFm6-4U3MT0,18804
|
|
5
|
+
redbirdpy/recon.py,sha256=tnsVIYc6dMB5p9iWXLdOtdMnAe_YLHruci9g-WmvvS8,30290
|
|
6
|
+
redbirdpy/solver.py,sha256=0lF41cOv0rv3W1JvAYhkgVWiO3hP2QWdltmZl5UH_Y8,32100
|
|
7
|
+
redbirdpy/utility.py,sha256=F8JYSUex0zWPds-f1lerdcPRT3bvGEPVw2CinDvFzu0,35360
|
|
8
|
+
redbirdpy-0.2.0.dist-info/licenses/LICENSE.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
9
|
+
redbirdpy-0.2.0.dist-info/METADATA,sha256=GpCSlcR76xneykT8NM802lgjREa27_Cp4KeNH9kSYio,19640
|
|
10
|
+
redbirdpy-0.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
11
|
+
redbirdpy-0.2.0.dist-info/top_level.txt,sha256=BbzExHReBzfre6x0TePxSeA7BZK4skcjnnTkHOTDoyA,10
|
|
12
|
+
redbirdpy-0.2.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
13
|
+
redbirdpy-0.2.0.dist-info/RECORD,,
|
redbirdpy-0.1.0.dist-info/RECORD
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
redbirdpy/__init__.py,sha256=I0dQ6LYcRWSL0Z525n52VE2BLEDQeTOQqM_EUwilgxk,3379
|
|
2
|
-
redbirdpy/analytical.py,sha256=9mMTU8EwDbRn5JqPsFgSdh0LL4WXc4BaXQpsECl_Wg4,26681
|
|
3
|
-
redbirdpy/forward.py,sha256=Gtu7v41h_AVMQ5sfyMSDkOIzzYTP2vn0SoxUQDR_lS8,18785
|
|
4
|
-
redbirdpy/property.py,sha256=fb8tOc7TLS7h6GQ61GjAbEwawJb5kLFULFm6-4U3MT0,18804
|
|
5
|
-
redbirdpy/recon.py,sha256=tnsVIYc6dMB5p9iWXLdOtdMnAe_YLHruci9g-WmvvS8,30290
|
|
6
|
-
redbirdpy/solver.py,sha256=mhhbyxxZATm8v_lVRCsYnwug4fsVyOU2ym665jmFO2E,26049
|
|
7
|
-
redbirdpy/utility.py,sha256=F8JYSUex0zWPds-f1lerdcPRT3bvGEPVw2CinDvFzu0,35360
|
|
8
|
-
redbirdpy-0.1.0.dist-info/licenses/LICENSE.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
9
|
-
redbirdpy-0.1.0.dist-info/METADATA,sha256=1FeYa9cvr6KwvOK-_nr9yfblMydktDCLQuv1mQDvAW8,19510
|
|
10
|
-
redbirdpy-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
11
|
-
redbirdpy-0.1.0.dist-info/top_level.txt,sha256=BbzExHReBzfre6x0TePxSeA7BZK4skcjnnTkHOTDoyA,10
|
|
12
|
-
redbirdpy-0.1.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
13
|
-
redbirdpy-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|