seqseg 2.2.0__tar.gz → 2.2.1__tar.gz
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.
- {seqseg-2.2.0 → seqseg-2.2.1}/PKG-INFO +1 -1
- {seqseg-2.2.0 → seqseg-2.2.1}/pyproject.toml +1 -1
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/assembly.py +0 -1
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/centerline.py +118 -30
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/initialization.py +0 -1
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/tracing.py +0 -1
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_centerline_backtracking.py +40 -1
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg.egg-info/PKG-INFO +1 -1
- {seqseg-2.2.0 → seqseg-2.2.1}/LICENSE +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/README.md +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/__init__.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/api.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/citations.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/cli.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/config/__init__.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/config/aorta_tutorial.yaml +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/config/global.yaml +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/config/global_aorta.yaml +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/config/global_cereb.yaml +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/config/global_coro.yaml +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/config/global_coro_retrace.yaml +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/config/global_debug.yaml +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/config/global_default.yaml +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/config/global_pulm.yaml +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/config/global_seg.yaml +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/config/global_test.yaml +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/config/sampler/default_cm.yaml +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/config/sampler/default_mm.yaml +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/config/sampler/test.yml +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/config_models.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/__init__.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/capping.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/checkpoint.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/datasets.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/evaluation.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/local_assembly.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/nnunet.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/params.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/prediction.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/simvascular.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/simvascular_contour.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/sitk_functions.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/sweep.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/tracing_functions.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/vmtk_functions.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/modules/vtk_functions.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/parallel/old_auto_centerline.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/parallel/parallel_auto_centerline.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/parallel/parallel_auto_centerline2.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/parallel/parallel_auto_centerline3.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/parallel/parallel_auto_centerline4.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/pipeline/__init__.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/pipeline/classic.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/pipeline/config.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/pipeline/continue_run.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/pipeline/directories.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/pipeline/nnunet_cli.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/pipeline/plus.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/pipeline/post.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/pipeline/predict.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/pipeline/single_trace.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/pipeline/train.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/scripts/__init__.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/scripts/prepare_casx_dataset.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/scripts/taubin_smooth.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/seqseg.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/seqseg_plus.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/conftest.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_api_entry.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_centerline_get_end_points.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_centerline_gradient.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_checkpoint.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_cli_helpers.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_datasets_paths.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_global_prediction.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_nnunet2.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_nnunet_cli.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_nnunet_device.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_prepare_casx_dataset.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_resample_spacing.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_segmentation.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_single_trace_staging.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_start_seg.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_train_data.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_train_pipeline.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_user_paths.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/tests/test_vtk.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg/user_paths.py +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg.egg-info/SOURCES.txt +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg.egg-info/dependency_links.txt +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg.egg-info/entry_points.txt +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg.egg-info/requires.txt +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/seqseg.egg-info/top_level.txt +0 -0
- {seqseg-2.2.0 → seqseg-2.2.1}/setup.cfg +0 -0
|
@@ -1004,7 +1004,6 @@ def calc_centerline_global(predicted_vessels, initial_seeds,
|
|
|
1004
1004
|
centerline_poly, success_info = calc_multi_component_centerlines(
|
|
1005
1005
|
predicted_vessels,
|
|
1006
1006
|
nr_seeds=nr_seeds,
|
|
1007
|
-
min_res=500,
|
|
1008
1007
|
out_dir=None,
|
|
1009
1008
|
write_files=False,
|
|
1010
1009
|
move_target_if_fail=False,
|
|
@@ -1339,8 +1339,116 @@ def backtracking_gradient(gradient, distance_map_surf_np,
|
|
|
1339
1339
|
return points, success
|
|
1340
1340
|
|
|
1341
1341
|
|
|
1342
|
+
# Centerline grids are isotropic. Three voxels across the inscribed radius
|
|
1343
|
+
# keeps the lumen wide enough for the finite-difference gradient. The
|
|
1344
|
+
# spacing clamp refuses both a one-voxel tube and a needlessly fine grid,
|
|
1345
|
+
# and the voxel budget caps the float64 gradient.
|
|
1346
|
+
_VOXELS_PER_RADIUS = 3.0
|
|
1347
|
+
_MIN_SPACING_MM = 0.35
|
|
1348
|
+
_MAX_SPACING_MM = 1.0
|
|
1349
|
+
_MAX_CENTERLINE_VOXELS = 20_000_000
|
|
1350
|
+
_GRID_SIZE_TOLERANCE = 0.10
|
|
1351
|
+
|
|
1352
|
+
|
|
1353
|
+
def centerline_grid_size(size, spacing, radius_mm):
|
|
1354
|
+
"""Choose an isotropic centerline grid from the inscribed radius.
|
|
1355
|
+
|
|
1356
|
+
Parameters
|
|
1357
|
+
----------
|
|
1358
|
+
size : sequence of int
|
|
1359
|
+
Current voxel counts, one per axis.
|
|
1360
|
+
spacing : sequence of float
|
|
1361
|
+
Current spacing in millimeters, one per axis.
|
|
1362
|
+
radius_mm : float
|
|
1363
|
+
Maximum inscribed radius in millimeters. A non-positive or
|
|
1364
|
+
non-finite radius keeps the input grid.
|
|
1365
|
+
|
|
1366
|
+
Returns
|
|
1367
|
+
-------
|
|
1368
|
+
new_size : tuple of int
|
|
1369
|
+
new_spacing : tuple of float
|
|
1370
|
+
Millimeters. Equal to ``spacing`` when the input grid is kept.
|
|
1371
|
+
target_spacing : float or None
|
|
1372
|
+
Spacing the radius and voxel budget asked for, before the
|
|
1373
|
+
10% size check. None when the radius does not define a grid.
|
|
1374
|
+
resample : bool
|
|
1375
|
+
False when ``new_size`` is the input size.
|
|
1376
|
+
"""
|
|
1377
|
+
size_in = tuple(int(v) for v in np.asarray(size, dtype=np.int64).reshape(3))
|
|
1378
|
+
spacing_in = tuple(
|
|
1379
|
+
float(v) for v in np.asarray(spacing, dtype=np.float64).reshape(3))
|
|
1380
|
+
radius = float(radius_mm)
|
|
1381
|
+
if not np.isfinite(radius) or radius <= 0.0:
|
|
1382
|
+
return size_in, spacing_in, None, False
|
|
1383
|
+
|
|
1384
|
+
size_f = np.asarray(size_in, dtype=np.float64)
|
|
1385
|
+
spacing_f = np.asarray(spacing_in, dtype=np.float64)
|
|
1386
|
+
extent = size_f * spacing_f
|
|
1387
|
+
target = float(np.clip(
|
|
1388
|
+
radius / _VOXELS_PER_RADIUS, _MIN_SPACING_MM, _MAX_SPACING_MM))
|
|
1389
|
+
extent_prod = float(np.prod(extent))
|
|
1390
|
+
if extent_prod > 0.0 and target > 0.0:
|
|
1391
|
+
if extent_prod / (target ** 3) > _MAX_CENTERLINE_VOXELS:
|
|
1392
|
+
target = (extent_prod / _MAX_CENTERLINE_VOXELS) ** (1.0 / 3.0)
|
|
1393
|
+
new_size = np.maximum(1, np.rint(extent / target)).astype(np.int64)
|
|
1394
|
+
# Rounding can land a hair over the budget the closed form aimed at.
|
|
1395
|
+
while (int(np.prod(new_size, dtype=np.int64)) > _MAX_CENTERLINE_VOXELS
|
|
1396
|
+
and target < 1.0e6):
|
|
1397
|
+
target *= 1.01
|
|
1398
|
+
new_size = np.maximum(1, np.rint(extent / target)).astype(np.int64)
|
|
1399
|
+
if np.all(np.abs(new_size.astype(np.float64) - size_f)
|
|
1400
|
+
<= _GRID_SIZE_TOLERANCE * np.maximum(size_f, 1.0)):
|
|
1401
|
+
return size_in, spacing_in, float(target), False
|
|
1402
|
+
return (tuple(int(v) for v in new_size),
|
|
1403
|
+
(float(target), float(target), float(target)),
|
|
1404
|
+
float(target), True)
|
|
1405
|
+
|
|
1406
|
+
|
|
1407
|
+
def resample_for_centerline(segmentation, verbose=False):
|
|
1408
|
+
"""Resample ``segmentation`` onto the centerline grid.
|
|
1409
|
+
|
|
1410
|
+
The inscribed-radius distance map (positive inside) is returned when
|
|
1411
|
+
the grid is unchanged, so the caller can skip a second distance
|
|
1412
|
+
transform. A resampled image returns ``None`` for that map.
|
|
1413
|
+
|
|
1414
|
+
Returns
|
|
1415
|
+
-------
|
|
1416
|
+
segmentation : sitk.Image
|
|
1417
|
+
distance_map : sitk.Image or None
|
|
1418
|
+
was_resampled : bool
|
|
1419
|
+
"""
|
|
1420
|
+
distance = distance_map_from_seg(segmentation) * -1
|
|
1421
|
+
radius = float(sitk.GetArrayViewFromImage(distance).max())
|
|
1422
|
+
old_size = tuple(int(v) for v in segmentation.GetSize())
|
|
1423
|
+
old_spacing = tuple(float(v) for v in segmentation.GetSpacing())
|
|
1424
|
+
new_size, new_spacing, target, resample = centerline_grid_size(
|
|
1425
|
+
old_size, old_spacing, radius)
|
|
1426
|
+
n_voxels = int(np.prod(new_size, dtype=np.int64))
|
|
1427
|
+
if verbose:
|
|
1428
|
+
target_text = "n/a" if target is None else f"{target:.4f} mm"
|
|
1429
|
+
print(
|
|
1430
|
+
f" Centerline grid: size {old_size} spacing {old_spacing} "
|
|
1431
|
+
f"radius {radius:.3f} mm target spacing {target_text} "
|
|
1432
|
+
f"-> size {new_size} ({n_voxels} voxels)"
|
|
1433
|
+
)
|
|
1434
|
+
if not resample:
|
|
1435
|
+
return segmentation, distance, False
|
|
1436
|
+
|
|
1437
|
+
resampled = sitk.Resample(
|
|
1438
|
+
segmentation,
|
|
1439
|
+
list(new_size),
|
|
1440
|
+
sitk.Transform(),
|
|
1441
|
+
sitk.sitkNearestNeighbor,
|
|
1442
|
+
segmentation.GetOrigin(),
|
|
1443
|
+
list(new_spacing),
|
|
1444
|
+
segmentation.GetDirection(),
|
|
1445
|
+
0,
|
|
1446
|
+
segmentation.GetPixelID())
|
|
1447
|
+
return resampled, None, True
|
|
1448
|
+
|
|
1449
|
+
|
|
1342
1450
|
def calc_centerline_fmm(segmentation, seed=None, targets=None,
|
|
1343
|
-
|
|
1451
|
+
out_dir=None, write_files=False,
|
|
1344
1452
|
move_target_if_fail=False,
|
|
1345
1453
|
relax_factor=1, return_target=False,
|
|
1346
1454
|
return_target_all=False,
|
|
@@ -1381,27 +1489,11 @@ def calc_centerline_fmm(segmentation, seed=None, targets=None,
|
|
|
1381
1489
|
"""
|
|
1382
1490
|
output = None
|
|
1383
1491
|
original_segmentation = segmentation
|
|
1384
|
-
was_resampled = False
|
|
1385
1492
|
if verbose:
|
|
1386
1493
|
print(f"Resolution of segmentation: {segmentation.GetSize()}")
|
|
1387
1494
|
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
print(f" Resampling segmentation from size {segmentation.GetSize()} to size {min_res}")
|
|
1391
|
-
# Divide spacing so that the size is at least 50
|
|
1392
|
-
divide = segmentation.GetSize()[2] / min_res
|
|
1393
|
-
segmentation = sitk.Resample(segmentation,
|
|
1394
|
-
[int(x/divide) for x in segmentation
|
|
1395
|
-
.GetSize()],
|
|
1396
|
-
sitk.Transform(),
|
|
1397
|
-
sitk.sitkNearestNeighbor,
|
|
1398
|
-
segmentation.GetOrigin(),
|
|
1399
|
-
[x*divide for x in segmentation
|
|
1400
|
-
.GetSpacing()],
|
|
1401
|
-
segmentation.GetDirection(), 0,
|
|
1402
|
-
segmentation.GetPixelID())
|
|
1403
|
-
was_resampled = True
|
|
1404
|
-
print(f" New size: {segmentation.GetSize()}")
|
|
1495
|
+
segmentation, distance_map_surf, was_resampled = resample_for_centerline(
|
|
1496
|
+
segmentation, verbose=verbose)
|
|
1405
1497
|
|
|
1406
1498
|
# Keep user-provided index coordinates anatomically consistent with
|
|
1407
1499
|
# the resampled grid by mapping old-index -> physical -> new-index.
|
|
@@ -1424,10 +1516,9 @@ def calc_centerline_fmm(segmentation, seed=None, targets=None,
|
|
|
1424
1516
|
segmentation.TransformPhysicalPointToIndex(target_phys))
|
|
1425
1517
|
targets_resampled.append(target_resampled)
|
|
1426
1518
|
targets = targets_resampled
|
|
1427
|
-
#
|
|
1428
|
-
distance_map_surf
|
|
1429
|
-
|
|
1430
|
-
distance_map_surf = distance_map_surf * -1
|
|
1519
|
+
# The radius check already built this map when the grid was kept.
|
|
1520
|
+
if distance_map_surf is None:
|
|
1521
|
+
distance_map_surf = distance_map_from_seg(segmentation) * -1
|
|
1431
1522
|
distance_map_surf_np = sitk.GetArrayFromImage(distance_map_surf).transpose(2, 1, 0)
|
|
1432
1523
|
# Get maximum value of distance map
|
|
1433
1524
|
max_surf = distance_map_surf_np.max()
|
|
@@ -3392,7 +3483,7 @@ def snap_end_points_to_lumen(end_points, radius_np, spacing, direction=None):
|
|
|
3392
3483
|
|
|
3393
3484
|
|
|
3394
3485
|
def calc_multi_component_centerlines(segmentation, nr_seeds=None,
|
|
3395
|
-
|
|
3486
|
+
out_dir=None, write_files=False,
|
|
3396
3487
|
move_target_if_fail=False, relax_factor=1,
|
|
3397
3488
|
verbose=False, return_failed=False,
|
|
3398
3489
|
post_process_kwargs=None):
|
|
@@ -3403,7 +3494,9 @@ def calc_multi_component_centerlines(segmentation, nr_seeds=None,
|
|
|
3403
3494
|
This function:
|
|
3404
3495
|
1. Analyzes the segmentation to find disconnected bodies
|
|
3405
3496
|
2. Creates optimal seed points for each disconnected body
|
|
3406
|
-
3. Calculates centerlines using fast marching method for each seed
|
|
3497
|
+
3. Calculates centerlines using fast marching method for each seed.
|
|
3498
|
+
Each component's grid is chosen from its inscribed radius and a
|
|
3499
|
+
voxel budget; see :func:`centerline_grid_size`.
|
|
3407
3500
|
4. Combines all centerlines into a unified polydata structure
|
|
3408
3501
|
|
|
3409
3502
|
Parameters
|
|
@@ -3414,9 +3507,6 @@ def calc_multi_component_centerlines(segmentation, nr_seeds=None,
|
|
|
3414
3507
|
Number of seeds to generate. If None, creates one seed per disconnected body.
|
|
3415
3508
|
If the requested number exceeds the number of disconnected bodies, it will be
|
|
3416
3509
|
automatically reduced to match the number of available bodies with a warning.
|
|
3417
|
-
min_res : int, optional
|
|
3418
|
-
Minimum resolution for centerline calculation. If segmentation resolution
|
|
3419
|
-
is lower, it will be resampled. Defaults to 300.
|
|
3420
3510
|
out_dir : str, optional
|
|
3421
3511
|
Output directory for intermediate files. Defaults to None.
|
|
3422
3512
|
write_files : bool, optional
|
|
@@ -3592,7 +3682,6 @@ def calc_multi_component_centerlines(segmentation, nr_seeds=None,
|
|
|
3592
3682
|
segmentation_body,
|
|
3593
3683
|
seed=seed,
|
|
3594
3684
|
targets=None, # Let calc_centerline_fmm find targets automatically
|
|
3595
|
-
min_res=min_res,
|
|
3596
3685
|
out_dir=out_dir,
|
|
3597
3686
|
write_files=write_files,
|
|
3598
3687
|
move_target_if_fail=move_target_if_fail,
|
|
@@ -3944,7 +4033,6 @@ if __name__ == '__main__':
|
|
|
3944
4033
|
centerline_multi, success_info = calc_multi_component_centerlines(
|
|
3945
4034
|
segmentation,
|
|
3946
4035
|
nr_seeds=None,
|
|
3947
|
-
min_res=700,
|
|
3948
4036
|
out_dir=out_dir,
|
|
3949
4037
|
write_files=write_files,
|
|
3950
4038
|
move_target_if_fail=False,
|
|
@@ -6,6 +6,7 @@ import SimpleITK as sitk
|
|
|
6
6
|
from seqseg.modules.centerline import (
|
|
7
7
|
backtracking_gradient,
|
|
8
8
|
calc_centerline_fmm,
|
|
9
|
+
centerline_grid_size,
|
|
9
10
|
cluster_map,
|
|
10
11
|
masked_world_frame_gradient,
|
|
11
12
|
snap_end_points_to_lumen,
|
|
@@ -160,8 +161,46 @@ def test_automatic_targets_reach_a_seed_away_from_the_widest_voxel():
|
|
|
160
161
|
seed = np.array([10.0, 6.0, 6.0])
|
|
161
162
|
|
|
162
163
|
_centerline, success, successes = calc_centerline_fmm(
|
|
163
|
-
image, seed=seed, targets=None,
|
|
164
|
+
image, seed=seed, targets=None,
|
|
164
165
|
return_success_list=True)
|
|
165
166
|
|
|
166
167
|
assert success
|
|
167
168
|
assert all(successes)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_centerline_grid_keeps_a_fine_isotropic_crop():
|
|
172
|
+
"""A 0.5 mm crop with a 1.18 mm radius is already within 10% of the target."""
|
|
173
|
+
size, spacing, _target, resample = centerline_grid_size(
|
|
174
|
+
(320, 289, 184), (0.5, 0.5, 0.5), 1.18)
|
|
175
|
+
assert resample is False
|
|
176
|
+
assert size == (320, 289, 184)
|
|
177
|
+
assert spacing == (0.5, 0.5, 0.5)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_centerline_grid_refines_a_thick_slice_axis():
|
|
181
|
+
size, spacing, _target, resample = centerline_grid_size(
|
|
182
|
+
(256, 256, 40), (0.7, 0.7, 3.0), 2.0)
|
|
183
|
+
assert resample is True
|
|
184
|
+
assert size == (269, 269, 180)
|
|
185
|
+
assert int(np.prod(size)) <= 20_000_000
|
|
186
|
+
assert spacing[0] == spacing[1] == spacing[2]
|
|
187
|
+
assert spacing[2] < 1.0
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def test_centerline_grid_coarsens_to_the_voxel_budget():
|
|
191
|
+
size, spacing, _target, resample = centerline_grid_size(
|
|
192
|
+
(1000, 1000, 1000), (0.5, 0.5, 0.5), 3.0)
|
|
193
|
+
assert resample is True
|
|
194
|
+
assert int(np.prod(size)) <= 20_000_000
|
|
195
|
+
assert spacing[0] > 1.0
|
|
196
|
+
assert size[0] < 1000
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_centerline_grid_keeps_the_input_when_radius_is_non_positive():
|
|
200
|
+
for radius in (0.0, -1.0, float("nan")):
|
|
201
|
+
size, spacing, target, resample = centerline_grid_size(
|
|
202
|
+
(40, 30, 20), (0.8, 0.8, 2.0), radius)
|
|
203
|
+
assert resample is False
|
|
204
|
+
assert target is None
|
|
205
|
+
assert size == (40, 30, 20)
|
|
206
|
+
assert spacing == (0.8, 0.8, 2.0)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|