warpkit 1.2.2__tar.gz → 1.3.0__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.
Files changed (91) hide show
  1. {warpkit-1.2.2 → warpkit-1.3.0}/CLAUDE.md +4 -0
  2. {warpkit-1.2.2 → warpkit-1.3.0}/PKG-INFO +2 -2
  3. {warpkit-1.2.2 → warpkit-1.3.0}/README.md +1 -1
  4. {warpkit-1.2.2 → warpkit-1.3.0}/tests/test_utilities.py +8 -5
  5. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/distortion.py +5 -3
  6. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/utilities.py +28 -2
  7. {warpkit-1.2.2 → warpkit-1.3.0}/.github/workflows/build.yml +0 -0
  8. {warpkit-1.2.2 → warpkit-1.3.0}/.gitignore +0 -0
  9. {warpkit-1.2.2 → warpkit-1.3.0}/.pre-commit-config.yaml +0 -0
  10. {warpkit-1.2.2 → warpkit-1.3.0}/.python-version +0 -0
  11. {warpkit-1.2.2 → warpkit-1.3.0}/.vscode/c_cpp_properties.json +0 -0
  12. {warpkit-1.2.2 → warpkit-1.3.0}/CMakeLists.txt +0 -0
  13. {warpkit-1.2.2 → warpkit-1.3.0}/Dockerfile +0 -0
  14. {warpkit-1.2.2 → warpkit-1.3.0}/LICENSE +0 -0
  15. {warpkit-1.2.2 → warpkit-1.3.0}/codecov +0 -0
  16. {warpkit-1.2.2 → warpkit-1.3.0}/codecov.SHA256SUM +0 -0
  17. {warpkit-1.2.2 → warpkit-1.3.0}/codecov.SHA256SUM.sig +0 -0
  18. {warpkit-1.2.2 → warpkit-1.3.0}/codecov.yml +0 -0
  19. {warpkit-1.2.2 → warpkit-1.3.0}/include/itk/itkModifiedInvertDisplacementFieldImageFilter.h +0 -0
  20. {warpkit-1.2.2 → warpkit-1.3.0}/include/itk/itkModifiedInvertDisplacementFieldImageFilter.hxx +0 -0
  21. {warpkit-1.2.2 → warpkit-1.3.0}/include/romeo/LICENSE +0 -0
  22. {warpkit-1.2.2 → warpkit-1.3.0}/include/romeo/README.md +0 -0
  23. {warpkit-1.2.2 → warpkit-1.3.0}/include/romeo/algorithm.h +0 -0
  24. {warpkit-1.2.2 → warpkit-1.3.0}/include/romeo/priority_queue.h +0 -0
  25. {warpkit-1.2.2 → warpkit-1.3.0}/include/romeo/romeo.h +0 -0
  26. {warpkit-1.2.2 → warpkit-1.3.0}/include/romeo/seed.h +0 -0
  27. {warpkit-1.2.2 → warpkit-1.3.0}/include/romeo/unwrap.h +0 -0
  28. {warpkit-1.2.2 → warpkit-1.3.0}/include/romeo/utility.h +0 -0
  29. {warpkit-1.2.2 → warpkit-1.3.0}/include/romeo/volume_view.h +0 -0
  30. {warpkit-1.2.2 → warpkit-1.3.0}/include/romeo/voxel_quality.h +0 -0
  31. {warpkit-1.2.2 → warpkit-1.3.0}/include/romeo/weights.h +0 -0
  32. {warpkit-1.2.2 → warpkit-1.3.0}/include/utilities.h +0 -0
  33. {warpkit-1.2.2 → warpkit-1.3.0}/include/warps.h +0 -0
  34. {warpkit-1.2.2 → warpkit-1.3.0}/notes/ROMEO_port_plan.md +0 -0
  35. {warpkit-1.2.2 → warpkit-1.3.0}/notes/fmap.png +0 -0
  36. {warpkit-1.2.2 → warpkit-1.3.0}/notes/phase.png +0 -0
  37. {warpkit-1.2.2 → warpkit-1.3.0}/packaging/pyinstaller/build_bundle.py +0 -0
  38. {warpkit-1.2.2 → warpkit-1.3.0}/packaging/pyinstaller/bundle_README.md +0 -0
  39. {warpkit-1.2.2 → warpkit-1.3.0}/packaging/pyinstaller/hooks/hook-warpkit.py +0 -0
  40. {warpkit-1.2.2 → warpkit-1.3.0}/packaging/pyinstaller/launchers/wk-apply-warp.py +0 -0
  41. {warpkit-1.2.2 → warpkit-1.3.0}/packaging/pyinstaller/launchers/wk-compute-fieldmap.py +0 -0
  42. {warpkit-1.2.2 → warpkit-1.3.0}/packaging/pyinstaller/launchers/wk-compute-jacobian.py +0 -0
  43. {warpkit-1.2.2 → warpkit-1.3.0}/packaging/pyinstaller/launchers/wk-convert-fieldmap.py +0 -0
  44. {warpkit-1.2.2 → warpkit-1.3.0}/packaging/pyinstaller/launchers/wk-convert-warp.py +0 -0
  45. {warpkit-1.2.2 → warpkit-1.3.0}/packaging/pyinstaller/launchers/wk-medic.py +0 -0
  46. {warpkit-1.2.2 → warpkit-1.3.0}/packaging/pyinstaller/launchers/wk-unwrap-phase.py +0 -0
  47. {warpkit-1.2.2 → warpkit-1.3.0}/packaging/pyinstaller/warpkit.spec +0 -0
  48. {warpkit-1.2.2 → warpkit-1.3.0}/pyproject.toml +0 -0
  49. {warpkit-1.2.2 → warpkit-1.3.0}/scripts/check-gitmoji.sh +0 -0
  50. {warpkit-1.2.2 → warpkit-1.3.0}/scripts/regen-stub.sh +0 -0
  51. {warpkit-1.2.2 → warpkit-1.3.0}/src/warpkit.cpp +0 -0
  52. {warpkit-1.2.2 → warpkit-1.3.0}/tests/conftest.py +0 -0
  53. {warpkit-1.2.2 → warpkit-1.3.0}/tests/data/romeo/Mag.nii +0 -0
  54. {warpkit-1.2.2 → warpkit-1.3.0}/tests/data/romeo/Phase.nii +0 -0
  55. {warpkit-1.2.2 → warpkit-1.3.0}/tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-1_part-mag_bold.json +0 -0
  56. {warpkit-1.2.2 → warpkit-1.3.0}/tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-1_part-mag_bold.nii.gz +0 -0
  57. {warpkit-1.2.2 → warpkit-1.3.0}/tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-1_part-phase_bold.json +0 -0
  58. {warpkit-1.2.2 → warpkit-1.3.0}/tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-1_part-phase_bold.nii.gz +0 -0
  59. {warpkit-1.2.2 → warpkit-1.3.0}/tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-2_part-mag_bold.json +0 -0
  60. {warpkit-1.2.2 → warpkit-1.3.0}/tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-2_part-mag_bold.nii.gz +0 -0
  61. {warpkit-1.2.2 → warpkit-1.3.0}/tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-2_part-phase_bold.json +0 -0
  62. {warpkit-1.2.2 → warpkit-1.3.0}/tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-2_part-phase_bold.nii.gz +0 -0
  63. {warpkit-1.2.2 → warpkit-1.3.0}/tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-3_part-mag_bold.json +0 -0
  64. {warpkit-1.2.2 → warpkit-1.3.0}/tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-3_part-mag_bold.nii.gz +0 -0
  65. {warpkit-1.2.2 → warpkit-1.3.0}/tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-3_part-phase_bold.json +0 -0
  66. {warpkit-1.2.2 → warpkit-1.3.0}/tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-3_part-phase_bold.nii.gz +0 -0
  67. {warpkit-1.2.2 → warpkit-1.3.0}/tests/test_concurrency.py +0 -0
  68. {warpkit-1.2.2 → warpkit-1.3.0}/tests/test_distortion.py +0 -0
  69. {warpkit-1.2.2 → warpkit-1.3.0}/tests/test_model.py +0 -0
  70. {warpkit-1.2.2 → warpkit-1.3.0}/tests/test_romeo.py +0 -0
  71. {warpkit-1.2.2 → warpkit-1.3.0}/tests/test_scripts.py +0 -0
  72. {warpkit-1.2.2 → warpkit-1.3.0}/tests/test_unwrap.py +0 -0
  73. {warpkit-1.2.2 → warpkit-1.3.0}/tests/test_version.py +0 -0
  74. {warpkit-1.2.2 → warpkit-1.3.0}/uv.lock +0 -0
  75. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/__init__.py +0 -0
  76. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/api.py +0 -0
  77. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/concurrency.py +0 -0
  78. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/model.py +0 -0
  79. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/py.typed +0 -0
  80. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/scripts/__init__.py +0 -0
  81. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/scripts/_metadata.py +0 -0
  82. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/scripts/_warp_io.py +0 -0
  83. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/scripts/apply_warp.py +0 -0
  84. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/scripts/compute_fieldmap.py +0 -0
  85. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/scripts/compute_jacobian.py +0 -0
  86. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/scripts/convert_fieldmap.py +0 -0
  87. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/scripts/convert_warp.py +0 -0
  88. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/scripts/medic.py +0 -0
  89. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/scripts/unwrap_phase.py +0 -0
  90. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/unwrap.py +0 -0
  91. {warpkit-1.2.2 → warpkit-1.3.0}/warpkit/warpkit_cpp.pyi +0 -0
@@ -129,6 +129,10 @@ commit; merge / revert / fixup / squash / amend commits are exempt.
129
129
  PR titles follow the same convention. Body: short summary, bullet the changes,
130
130
  and call out anything CI-relevant (wheel matrix, pybind11 ABI, ITK).
131
131
 
132
+ **No AI attribution.** Do not add `Co-Authored-By: Claude ...` trailers to
133
+ commit messages, and do not add "Generated with Claude Code" (or similar)
134
+ lines to PR bodies. This overrides any default tooling behavior.
135
+
132
136
  ## CI specifics
133
137
 
134
138
  GitHub Actions builds wheels for Python 3.11–3.14 (both standard and free-threaded) on `ubuntu-latest`,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: warpkit
3
- Version: 1.2.2
3
+ Version: 1.3.0
4
4
  Summary: A python library for neuroimaging transformations
5
5
  Keywords: neuroimaging
6
6
  Author-Email: Andrew Van <vanandrew77@gmail.com>
@@ -26,7 +26,7 @@ Description-Content-Type: text/markdown
26
26
 
27
27
  A Python library for neuroimaging transforms, focused on the Multi-Echo DIstortion Correction (MEDIC) algorithm. The paper is published in *Imaging Neuroscience* at <https://doi.org/10.1162/IMAG.a.1262>.
28
28
 
29
- The phase-unwrapping core is a self-contained C++17 port of [ROMEO](https://github.com/korbinian90/ROMEO) — there is no Julia runtime to install, and binary wheels ship with the ITK pieces statically linked. If you used an older release of warpkit that required `julia` on `PATH`, that step is gone.
29
+ The phase-unwrapping core is a self-contained C++17 port of [ROMEO](https://github.com/korbinian90/ROMEO).
30
30
 
31
31
  ## Installation
32
32
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  A Python library for neuroimaging transforms, focused on the Multi-Echo DIstortion Correction (MEDIC) algorithm. The paper is published in *Imaging Neuroscience* at <https://doi.org/10.1162/IMAG.a.1262>.
8
8
 
9
- The phase-unwrapping core is a self-contained C++17 port of [ROMEO](https://github.com/korbinian90/ROMEO) — there is no Julia runtime to install, and binary wheels ship with the ITK pieces statically linked. If you used an older release of warpkit that required `julia` on `PATH`, that step is gone.
9
+ The phase-unwrapping core is a self-contained C++17 port of [ROMEO](https://github.com/korbinian90/ROMEO).
10
10
 
11
11
  ## Installation
12
12
 
@@ -235,19 +235,22 @@ def _zero_3vec_field(shape=(8, 8, 8)) -> nib.Nifti1Image:
235
235
  return nib.Nifti1Image(np.zeros((*shape, 3), dtype=np.float32), affine)
236
236
 
237
237
 
238
- def test_invert_displacement_field_zero():
238
+ @pytest.mark.parametrize("ignore_rotation", [False, True])
239
+ def test_invert_displacement_field_zero(ignore_rotation):
239
240
  """Inverting a zero field gives back zero (within numerical noise) and
240
- preserves the 3-channel last axis."""
241
+ preserves the 3-channel last axis. Covers both the physical-space and the
242
+ ignore_rotation (voxel-grid frame) inversion paths."""
241
243
  field = _zero_3vec_field()
242
- inverted = invert_displacement_field(field)
244
+ inverted = invert_displacement_field(field, ignore_rotation=ignore_rotation)
243
245
  assert inverted.shape == field.shape
244
246
  assert_allclose(inverted.get_fdata(), 0.0, atol=1e-5)
245
247
 
246
248
 
247
- def test_invert_displacement_maps_zero():
249
+ @pytest.mark.parametrize("ignore_rotation", [False, True])
250
+ def test_invert_displacement_maps_zero(ignore_rotation):
248
251
  affine = np.diag([2.0, 2.0, 2.0, 1.0])
249
252
  dmap = nib.Nifti1Image(np.zeros((6, 6, 6, 2), dtype=np.float32), affine)
250
- inverted = invert_displacement_maps(dmap, axis="y")
253
+ inverted = invert_displacement_maps(dmap, axis="y", ignore_rotation=ignore_rotation)
251
254
  assert inverted.shape == dmap.shape
252
255
  assert_allclose(inverted.get_fdata(), 0.0, atol=1e-5)
253
256
 
@@ -126,12 +126,14 @@ def medic(
126
126
 
127
127
  # invert displacement maps (these are in undistorted space)
128
128
  displacement_maps = invert_displacement_maps(
129
- inv_displacement_maps, phase_encoding_direction
129
+ inv_displacement_maps, phase_encoding_direction, ignore_rotation=True
130
130
  )
131
131
 
132
- # convert correction maps back to undistorted space field map
132
+ # convert correction maps back to undistorted space field map. No
133
+ # flip_sign here: the correlation check below sets the sign relative to
134
+ # the native field map, which makes an up-front flip redundant.
133
135
  field_maps = displacement_maps_to_field_maps(
134
- displacement_maps, total_readout_time, phase_encoding_direction, flip_sign=True
136
+ displacement_maps, total_readout_time, phase_encoding_direction
135
137
  )
136
138
 
137
139
  # check if we need to flip sign of field maps (this is done by comparing sign of correlation between
@@ -493,7 +493,10 @@ def get_ras_orient_transform(
493
493
 
494
494
 
495
495
  def invert_displacement_maps(
496
- displacement_maps: nib.Nifti1Image, axis: str = "y", verbose: bool = False
496
+ displacement_maps: nib.Nifti1Image,
497
+ axis: str = "y",
498
+ verbose: bool = False,
499
+ ignore_rotation: bool = False,
497
500
  ) -> nib.Nifti1Image:
498
501
  """Invert displacement maps
499
502
 
@@ -505,6 +508,13 @@ def invert_displacement_maps(
505
508
  Axis displacement maps are along, by default "y"
506
509
  verbose : bool, optional
507
510
  Print debugging information, by default False
511
+ ignore_rotation : bool, optional
512
+ Invert in data (voxel) space rather than physical space by passing an
513
+ identity rotation to the inverter instead of the affine's rotation, by
514
+ default False. The displacement map is composed along a voxel axis, so
515
+ for oblique acquisitions the affine's rotation makes ITK invert in a
516
+ physical frame that does not align with that axis; setting this keeps
517
+ the inversion in the grid frame the map is actually defined in.
508
518
 
509
519
  Returns
510
520
  -------
@@ -527,6 +537,11 @@ def invert_displacement_maps(
527
537
  # split affine into components
528
538
  translations, rotations, zooms, _ = decompose44(displacement_maps_ras.affine)
529
539
 
540
+ # invert in data space: drop the affine's rotation so ITK inverts in the
541
+ # voxel grid frame the map is composed along, not the physical orientation
542
+ if ignore_rotation:
543
+ rotations = np.eye(3)
544
+
530
545
  # invert maps
531
546
  new_data = np.zeros(data.shape, dtype=np.float32)
532
547
  logging.info("Inverting displacement maps...")
@@ -554,7 +569,9 @@ def invert_displacement_maps(
554
569
 
555
570
 
556
571
  def invert_displacement_field(
557
- displacement_field: nib.Nifti1Image, verbose: bool = False
572
+ displacement_field: nib.Nifti1Image,
573
+ verbose: bool = False,
574
+ ignore_rotation: bool = False,
558
575
  ) -> nib.Nifti1Image:
559
576
  """Invert displacement field
560
577
 
@@ -564,6 +581,10 @@ def invert_displacement_field(
564
581
  Displacement field data in mm
565
582
  verbose : bool, optional
566
583
  Print debugging information, by default False
584
+ ignore_rotation : bool, optional
585
+ Invert in data (voxel) space rather than physical space by passing an
586
+ identity rotation to the inverter instead of the affine's rotation, by
587
+ default False. See :func:`invert_displacement_maps` for details.
567
588
 
568
589
  Returns
569
590
  -------
@@ -583,6 +604,11 @@ def invert_displacement_field(
583
604
  # split affine into components
584
605
  translations, rotations, zooms, _ = decompose44(displacement_field_ras.affine)
585
606
 
607
+ # invert in data space: drop the affine's rotation so ITK inverts in the
608
+ # voxel grid frame rather than the physical orientation
609
+ if ignore_rotation:
610
+ rotations = np.eye(3)
611
+
586
612
  # Pad spatial dims only — leaving the 3-channel axis at size 3 — so we
587
613
  # can avoid edge effects of the inverse without inflating the channel
588
614
  # count. (Earlier versions padded all 4 axes which produced a 5-channel
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