petpal 0.6.0__tar.gz → 0.6.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.
Files changed (160) hide show
  1. {petpal-0.6.0 → petpal-0.6.1}/PKG-INFO +1 -1
  2. {petpal-0.6.0 → petpal-0.6.1}/petpal/__init__.py +1 -0
  3. {petpal-0.6.0 → petpal-0.6.1}/petpal/cli/cli_preproc.py +10 -11
  4. petpal-0.6.1/petpal/io/__init__.py +9 -0
  5. petpal-0.6.1/petpal/io/image.py +39 -0
  6. petpal-0.6.1/petpal/io/table.py +94 -0
  7. {petpal-0.6.0 → petpal-0.6.1}/petpal/pipelines/preproc_steps.py +1 -1
  8. {petpal-0.6.0 → petpal-0.6.1}/petpal/preproc/motion_corr.py +245 -79
  9. {petpal-0.6.0 → petpal-0.6.1}/petpal/utils/scan_timing.py +28 -26
  10. {petpal-0.6.0 → petpal-0.6.1}/petpal/utils/useful_functions.py +36 -8
  11. {petpal-0.6.0 → petpal-0.6.1}/pyproject.toml +1 -1
  12. {petpal-0.6.0 → petpal-0.6.1}/tests/test_scan_timing_decay.py +8 -1
  13. petpal-0.6.1/tests/test_table_save.py +41 -0
  14. {petpal-0.6.0 → petpal-0.6.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  15. {petpal-0.6.0 → petpal-0.6.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  16. {petpal-0.6.0 → petpal-0.6.1}/.github/workflows/publish-to-pypi.yml +0 -0
  17. {petpal-0.6.0 → petpal-0.6.1}/.github/workflows/python-package.yml +0 -0
  18. {petpal-0.6.0 → petpal-0.6.1}/.gitignore +0 -0
  19. {petpal-0.6.0 → petpal-0.6.1}/.readthedocs.yaml +0 -0
  20. {petpal-0.6.0 → petpal-0.6.1}/LICENSE +0 -0
  21. {petpal-0.6.0 → petpal-0.6.1}/README.md +0 -0
  22. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/1tcm/gaussian_noise/tac_1tcm_set-00.txt +0 -0
  23. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/1tcm/gaussian_noise/tac_1tcm_set-01.txt +0 -0
  24. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/1tcm/gaussian_noise/tac_1tcm_set-02.txt +0 -0
  25. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/1tcm/gaussian_noise/tacs.pdf +0 -0
  26. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/1tcm/gaussian_noise/tacs.png +0 -0
  27. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/1tcm/noise_free/tac_1tcm_set-00.txt +0 -0
  28. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/1tcm/noise_free/tac_1tcm_set-01.txt +0 -0
  29. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/1tcm/noise_free/tac_1tcm_set-02.txt +0 -0
  30. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/1tcm/noise_free/tacs.pdf +0 -0
  31. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/1tcm/noise_free/tacs.png +0 -0
  32. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/1tcm/params_1tcm_set-00.json +0 -0
  33. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/1tcm/params_1tcm_set-01.json +0 -0
  34. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/1tcm/params_1tcm_set-02.json +0 -0
  35. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/fdg_plasma_clamp_evenly_resampled.txt +0 -0
  36. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/fdg_plasma_clamp_evenly_resampled_woMax.txt +0 -0
  37. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/fdg_plasma_clamp_tacs.pdf +0 -0
  38. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/fdg_plasma_clamp_tacs.png +0 -0
  39. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/gen_tcms_data.ipynb +0 -0
  40. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/readme.md +0 -0
  41. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm/gaussian_noise/tac_2tcm_set-00.txt +0 -0
  42. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm/gaussian_noise/tac_2tcm_set-01.txt +0 -0
  43. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm/gaussian_noise/tac_2tcm_set-02.txt +0 -0
  44. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm/gaussian_noise/tacs.pdf +0 -0
  45. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm/gaussian_noise/tacs.png +0 -0
  46. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm/noise_free/tac_2tcm_set-00.txt +0 -0
  47. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm/noise_free/tac_2tcm_set-01.txt +0 -0
  48. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm/noise_free/tac_2tcm_set-02.txt +0 -0
  49. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm/noise_free/tacs.pdf +0 -0
  50. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm/noise_free/tacs.png +0 -0
  51. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm/params_serial_2tcm_set-00.json +0 -0
  52. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm/params_serial_2tcm_set-01.json +0 -0
  53. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm/params_serial_2tcm_set-02.json +0 -0
  54. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm_k4zero/gaussian_noise/tac_2tcm_k4zero_set-00.txt +0 -0
  55. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm_k4zero/gaussian_noise/tac_2tcm_k4zero_set-01.txt +0 -0
  56. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm_k4zero/gaussian_noise/tacs.pdf +0 -0
  57. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm_k4zero/gaussian_noise/tacs.png +0 -0
  58. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm_k4zero/noise_free/tac_2tcm_k4zero_set-00.txt +0 -0
  59. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm_k4zero/noise_free/tac_2tcm_k4zero_set-01.txt +0 -0
  60. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm_k4zero/noise_free/tacs.pdf +0 -0
  61. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm_k4zero/noise_free/tacs.png +0 -0
  62. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm_k4zero/params_serial_2tcm_k4zero_set-00.json +0 -0
  63. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/serial_2tcm_k4zero/params_serial_2tcm_k4zero_set-01.json +0 -0
  64. {petpal-0.6.0 → petpal-0.6.1}/data/tcm_tacs/turku_pet_center_fdg_plasma_clamp.txt +0 -0
  65. {petpal-0.6.0 → petpal-0.6.1}/docs/Makefile +0 -0
  66. {petpal-0.6.0 → petpal-0.6.1}/docs/PETPAL_Logo.png +0 -0
  67. {petpal-0.6.0 → petpal-0.6.1}/docs/_templates/index.rst +0 -0
  68. {petpal-0.6.0 → petpal-0.6.1}/docs/_templates/python/attribute.rst +0 -0
  69. {petpal-0.6.0 → petpal-0.6.1}/docs/_templates/python/class.rst +0 -0
  70. {petpal-0.6.0 → petpal-0.6.1}/docs/_templates/python/data.rst +0 -0
  71. {petpal-0.6.0 → petpal-0.6.1}/docs/_templates/python/exception.rst +0 -0
  72. {petpal-0.6.0 → petpal-0.6.1}/docs/_templates/python/function.rst +0 -0
  73. {petpal-0.6.0 → petpal-0.6.1}/docs/_templates/python/method.rst +0 -0
  74. {petpal-0.6.0 → petpal-0.6.1}/docs/_templates/python/module.rst +0 -0
  75. {petpal-0.6.0 → petpal-0.6.1}/docs/_templates/python/package.rst +0 -0
  76. {petpal-0.6.0 → petpal-0.6.1}/docs/_templates/python/property.rst +0 -0
  77. {petpal-0.6.0 → petpal-0.6.1}/docs/conf.py +0 -0
  78. {petpal-0.6.0 → petpal-0.6.1}/docs/index.rst +0 -0
  79. {petpal-0.6.0 → petpal-0.6.1}/docs/make.bat +0 -0
  80. {petpal-0.6.0 → petpal-0.6.1}/docs/requirements.txt +0 -0
  81. {petpal-0.6.0 → petpal-0.6.1}/docs/tutorials/index.rst +0 -0
  82. {petpal-0.6.0 → petpal-0.6.1}/docs/tutorials/pib_example.rst +0 -0
  83. {petpal-0.6.0 → petpal-0.6.1}/petpal/cli/__init__.py +0 -0
  84. {petpal-0.6.0 → petpal-0.6.1}/petpal/cli/cli_graphical_analysis.py +0 -0
  85. {petpal-0.6.0 → petpal-0.6.1}/petpal/cli/cli_graphical_plots.py +0 -0
  86. {petpal-0.6.0 → petpal-0.6.1}/petpal/cli/cli_idif.py +0 -0
  87. {petpal-0.6.0 → petpal-0.6.1}/petpal/cli/cli_parametric_images.py +0 -0
  88. {petpal-0.6.0 → petpal-0.6.1}/petpal/cli/cli_pib_processing.py +0 -0
  89. {petpal-0.6.0 → petpal-0.6.1}/petpal/cli/cli_plot_tacs.py +0 -0
  90. {petpal-0.6.0 → petpal-0.6.1}/petpal/cli/cli_pvc.py +0 -0
  91. {petpal-0.6.0 → petpal-0.6.1}/petpal/cli/cli_reference_tissue_models.py +0 -0
  92. {petpal-0.6.0 → petpal-0.6.1}/petpal/cli/cli_stats.py +0 -0
  93. {petpal-0.6.0 → petpal-0.6.1}/petpal/cli/cli_tac_fitting.py +0 -0
  94. {petpal-0.6.0 → petpal-0.6.1}/petpal/cli/cli_tac_interpolation.py +0 -0
  95. {petpal-0.6.0 → petpal-0.6.1}/petpal/cli/cli_vat_processing.py +0 -0
  96. {petpal-0.6.0 → petpal-0.6.1}/petpal/input_function/__init__.py +0 -0
  97. {petpal-0.6.0 → petpal-0.6.1}/petpal/input_function/blood_input.py +0 -0
  98. {petpal-0.6.0 → petpal-0.6.1}/petpal/input_function/idif_necktangle.py +0 -0
  99. {petpal-0.6.0 → petpal-0.6.1}/petpal/input_function/pca_guided_idif.py +0 -0
  100. {petpal-0.6.0 → petpal-0.6.1}/petpal/kinetic_modeling/__init__.py +0 -0
  101. {petpal-0.6.0 → petpal-0.6.1}/petpal/kinetic_modeling/fit_tac_with_rtms.py +0 -0
  102. {petpal-0.6.0 → petpal-0.6.1}/petpal/kinetic_modeling/graphical_analysis.py +0 -0
  103. {petpal-0.6.0 → petpal-0.6.1}/petpal/kinetic_modeling/parametric_images.py +0 -0
  104. {petpal-0.6.0 → petpal-0.6.1}/petpal/kinetic_modeling/reference_tissue_models.py +0 -0
  105. {petpal-0.6.0 → petpal-0.6.1}/petpal/kinetic_modeling/rtm_analysis.py +0 -0
  106. {petpal-0.6.0 → petpal-0.6.1}/petpal/kinetic_modeling/tac_fitting.py +0 -0
  107. {petpal-0.6.0 → petpal-0.6.1}/petpal/kinetic_modeling/tac_interpolation.py +0 -0
  108. {petpal-0.6.0 → petpal-0.6.1}/petpal/kinetic_modeling/tac_uncertainty.py +0 -0
  109. {petpal-0.6.0 → petpal-0.6.1}/petpal/kinetic_modeling/tcms_as_convolutions.py +0 -0
  110. {petpal-0.6.0 → petpal-0.6.1}/petpal/meta/__init__.py +0 -0
  111. {petpal-0.6.0 → petpal-0.6.1}/petpal/meta/label_maps.py +0 -0
  112. {petpal-0.6.0 → petpal-0.6.1}/petpal/pipelines/__init__.py +0 -0
  113. {petpal-0.6.0 → petpal-0.6.1}/petpal/pipelines/kinetic_modeling_steps.py +0 -0
  114. {petpal-0.6.0 → petpal-0.6.1}/petpal/pipelines/pca_guided_idif_steps.py +0 -0
  115. {petpal-0.6.0 → petpal-0.6.1}/petpal/pipelines/pipelines.py +0 -0
  116. {petpal-0.6.0 → petpal-0.6.1}/petpal/pipelines/steps_base.py +0 -0
  117. {petpal-0.6.0 → petpal-0.6.1}/petpal/pipelines/steps_containers.py +0 -0
  118. {petpal-0.6.0 → petpal-0.6.1}/petpal/preproc/__init__.py +0 -0
  119. {petpal-0.6.0 → petpal-0.6.1}/petpal/preproc/decay_correction.py +0 -0
  120. {petpal-0.6.0 → petpal-0.6.1}/petpal/preproc/image_operations_4d.py +0 -0
  121. {petpal-0.6.0 → petpal-0.6.1}/petpal/preproc/motion_target.py +0 -0
  122. {petpal-0.6.0 → petpal-0.6.1}/petpal/preproc/partial_volume_corrections.py +0 -0
  123. {petpal-0.6.0 → petpal-0.6.1}/petpal/preproc/regional_tac_extraction.py +0 -0
  124. {petpal-0.6.0 → petpal-0.6.1}/petpal/preproc/register.py +0 -0
  125. {petpal-0.6.0 → petpal-0.6.1}/petpal/preproc/segmentation_tools.py +0 -0
  126. {petpal-0.6.0 → petpal-0.6.1}/petpal/preproc/standard_uptake_value.py +0 -0
  127. {petpal-0.6.0 → petpal-0.6.1}/petpal/preproc/symmetric_geometric_transfer_matrix.py +0 -0
  128. {petpal-0.6.0 → petpal-0.6.1}/petpal/utils/__init__.py +0 -0
  129. {petpal-0.6.0 → petpal-0.6.1}/petpal/utils/bids_utils.py +0 -0
  130. {petpal-0.6.0 → petpal-0.6.1}/petpal/utils/constants.py +0 -0
  131. {petpal-0.6.0 → petpal-0.6.1}/petpal/utils/data_driven_image_analyses.py +0 -0
  132. {petpal-0.6.0 → petpal-0.6.1}/petpal/utils/decorators.py +0 -0
  133. {petpal-0.6.0 → petpal-0.6.1}/petpal/utils/image_io.py +0 -0
  134. {petpal-0.6.0 → petpal-0.6.1}/petpal/utils/math_lib.py +0 -0
  135. {petpal-0.6.0 → petpal-0.6.1}/petpal/utils/metadata.py +0 -0
  136. {petpal-0.6.0 → petpal-0.6.1}/petpal/utils/stats.py +0 -0
  137. {petpal-0.6.0 → petpal-0.6.1}/petpal/utils/testing_utils.py +0 -0
  138. {petpal-0.6.0 → petpal-0.6.1}/petpal/utils/time_activity_curve.py +0 -0
  139. {petpal-0.6.0 → petpal-0.6.1}/petpal/visualizations/__init__.py +0 -0
  140. {petpal-0.6.0 → petpal-0.6.1}/petpal/visualizations/graphical_plots.py +0 -0
  141. {petpal-0.6.0 → petpal-0.6.1}/petpal/visualizations/image_visualization.py +0 -0
  142. {petpal-0.6.0 → petpal-0.6.1}/petpal/visualizations/qc_plots.py +0 -0
  143. {petpal-0.6.0 → petpal-0.6.1}/petpal/visualizations/tac_plots.py +0 -0
  144. {petpal-0.6.0 → petpal-0.6.1}/shared/dseg.tsv +0 -0
  145. {petpal-0.6.0 → petpal-0.6.1}/shared/freesurfer_lmap.json +0 -0
  146. {petpal-0.6.0 → petpal-0.6.1}/shared/freesurfer_lmap_lr.json +0 -0
  147. {petpal-0.6.0 → petpal-0.6.1}/shared/perl_cyno_lmap.json +0 -0
  148. {petpal-0.6.0 → petpal-0.6.1}/shared/perl_cyno_lmap_lr.json +0 -0
  149. {petpal-0.6.0 → petpal-0.6.1}/test_notebooks/explicit_tac_fitting/01_fitting_TCMs.ipynb +0 -0
  150. {petpal-0.6.0 → petpal-0.6.1}/test_notebooks/testing_RTMs/01_testing_RTMs.ipynb +0 -0
  151. {petpal-0.6.0 → petpal-0.6.1}/test_notebooks/testing_graphical_analyses/01_testing_on_tcms_database.ipynb +0 -0
  152. {petpal-0.6.0 → petpal-0.6.1}/test_notebooks/testing_graphical_analyses/02_testing_parametric_images.ipynb +0 -0
  153. {petpal-0.6.0 → petpal-0.6.1}/test_notebooks/testing_graphical_analyses/03_plotting_graphical_anlayses_testbed.ipynb +0 -0
  154. {petpal-0.6.0 → petpal-0.6.1}/tests/test_graphical_analysis.py +0 -0
  155. {petpal-0.6.0 → petpal-0.6.1}/tests/test_importpetpal.py +0 -0
  156. {petpal-0.6.0 → petpal-0.6.1}/tests/test_register.py +0 -0
  157. {petpal-0.6.0 → petpal-0.6.1}/tests/test_sgtm.py +0 -0
  158. {petpal-0.6.0 → petpal-0.6.1}/tests/test_time_activity_curve.py +0 -0
  159. {petpal-0.6.0 → petpal-0.6.1}/tests/test_weighted_sum.py +0 -0
  160. {petpal-0.6.0 → petpal-0.6.1}/tests/test_write_tacs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: petpal
3
- Version: 0.6.0
3
+ Version: 0.6.1
4
4
  Summary: PET-PAL (Positron Emission Tomography Processing and Analysis Library)
5
5
  Project-URL: Repository, https://github.com/PETPAL-WUSM/PETPAL.git
6
6
  Author-email: Noah Goldman <noahg@wustl.edu>, Bradley Judge <bjudge@wustl.edu>, Furqan Dar <dar@wustl.edu>, Kenan Oestreich <kenan.oestreich@wustl.edu>
@@ -5,6 +5,7 @@ from . import utils
5
5
  from . import visualizations
6
6
  from . import pipelines
7
7
  from . import meta
8
+ from . import io
8
9
 
9
10
  def main():
10
11
  print("PET-PAL (Positron Emission Tomography Processing and Analysis Library)")
@@ -321,12 +321,11 @@ def _generate_args() -> argparse.ArgumentParser:
321
321
  help='Windowed motion correction for 4D PET'
322
322
  ' using ANTS')
323
323
  _add_common_args(parser_window_moco)
324
- parser_window_moco.add_argument('-t',
325
- '--motion-target',
326
- default='weighted_series_sum',
327
- type=str,
328
- help="Motion target option. Can be an image path , "
329
- "'weighted_series_sum' or 'mean_image'")
324
+ parser_window_moco.add_argument('--motion-target', default=None, nargs='+',
325
+ help="Motion target option. Can be an image path, "
326
+ "'weighted_series_sum' or a tuple "
327
+ "(i.e. '--motion-target 0 600' for first ten minutes).",
328
+ required=True)
330
329
  parser_window_moco.add_argument('-w', '--window-size', default=60.0, type=float,
331
330
  help="Window size in seconds.",)
332
331
  xfm_types = ['QuickRigid', 'Rigid', 'DenseRigid', 'Affine', 'AffineFast']
@@ -460,11 +459,11 @@ def main():
460
459
  start_time=args.start_time,
461
460
  end_time=args.end_time)
462
461
  case 'windowed_motion_corr':
463
- motion_corr.windowed_motion_corr_to_target(input_image_path=args.input_img,
464
- out_image_path=args.out_img,
465
- motion_target_option=motion_target,
466
- w_size=args.window_size,
467
- type_of_transform=args.transform_type)
462
+ motion_corrector = motion_corr.MotionCorrect()
463
+ motion_corrector(input_image_path=args.input_img,
464
+ output_image_path=args.out_img,
465
+ motion_target_option=motion_target,
466
+ window_duration=args.window_size)
468
467
  case 'rescale_image':
469
468
  input_img = ants.image_read(filename=args.input_img)
470
469
  out_img = image_operations_4d.rescale_image(input_image=input_img,
@@ -0,0 +1,9 @@
1
+ from . import table
2
+ from . import image
3
+
4
+ def main():
5
+ print("PETPAL - Load and Save Module")
6
+
7
+
8
+ if __name__ == "__main__":
9
+ main()
@@ -0,0 +1,39 @@
1
+ """
2
+ Module for loading and saving images
3
+ """
4
+ from typing import Optional
5
+ from collections.abc import Callable
6
+ import ants
7
+
8
+
9
+ class ImageLoader:
10
+ """Class for reading image files, with extensions such as Nifti or MGZ.
11
+
12
+ See also: :py:docs:`~ants.image_read`.
13
+
14
+ Example:
15
+
16
+ .. code-block:: python
17
+
18
+ from petpal.io.image import ImageLoader
19
+
20
+ image_loader = ImageLoader()
21
+ my_img = image_loader.load('/path/to/img.nii.gz')
22
+
23
+ :ivar _loader: Function that loads an image file as an ants.ANTsImage object.
24
+ """
25
+ def __init__(self, loader: Optional[Callable[[str], ants.ANTsImage]] = None):
26
+ self._loader = loader or ants.image_read
27
+
28
+ def load(self, filename: str) -> ants.ANTsImage:
29
+ """Public read API that delegates to the configured reader.
30
+
31
+ Args:
32
+ filename (str): Path to file that will be loaded as ANTsImage.
33
+
34
+ Returns:
35
+ img (ants.ANTsImage): Image object loaded into Python."""
36
+ return self._loader(filename)
37
+
38
+ def __call__(self, filename: str) -> ants.ANTsImage:
39
+ return self.load(filename=filename)
@@ -0,0 +1,94 @@
1
+ """
2
+ Module for reading and writing tables as TSV and CSV files.
3
+ """
4
+ import os
5
+ import tempfile
6
+ from typing import Optional
7
+ from collections.abc import Callable
8
+ from pathlib import Path
9
+ import dataclasses
10
+ import pandas as pd
11
+
12
+
13
+ def get_tabular_separator(ext: str) -> str:
14
+ """Get the separator corresponding to a given tabular data filetype.
15
+
16
+ '.csv' will return ',' while '.tsv' and '.txt' will return '\t'. Any other input will raise a
17
+ ValueError.
18
+
19
+ Args:
20
+ ext (str): Extension to get matching separator for.
21
+
22
+ Returns:
23
+ sep (str): Separator matched from extension.
24
+
25
+ Raises:
26
+ ValueError: If extension is not .csv or .tsv.
27
+ """
28
+ matching_separators = {'.csv': ',', '.tsv': '\t', '.txt': '\t'}
29
+ try:
30
+ return matching_separators[ext]
31
+ except ValueError as exc:
32
+ error_msg = f"Only accepted extensions are {matching_separators.keys()}. Got {ext}."
33
+ raise ValueError(error_msg) from exc
34
+
35
+
36
+ @dataclasses.dataclass
37
+ class TableSaver:
38
+ """
39
+ Class for saving Pandas Database objects as CSV or TSV files based on a provided path.
40
+
41
+ - Default behavior writes atomically (write temp file + os.replace) to avoid partial files.
42
+ - Accepts an injectable writer callable for testing or alternative persistence backends.
43
+
44
+ Example:
45
+
46
+ .. code-block:: python
47
+
48
+ import pandas as pd
49
+ from petpal.io.table import TableSaver
50
+
51
+ table_saver = TableSaver()
52
+ my_data = pd.DataFrame(data={'time': [0, 1, 2], 'value': [1, 4, 9]})
53
+
54
+ # when file extension is .csv, uses commas to separate values
55
+ table_saver.save(my_data, 'table.csv')
56
+
57
+ # when file extension is .tsv or .txt, uses tabs to separate values
58
+ table_saver.save(my_data, 'table.txt')
59
+
60
+ :ivar _saver: Injectable tabular data saving function that saves a dataframe to a file.
61
+ """
62
+ def __init__(self, saver: Optional[Callable[[pd.DataFrame, str], None]] = None):
63
+ self._saver = saver or self._atomic_save
64
+
65
+ def _atomic_save(self, df: pd.DataFrame, path: str):
66
+ """Saves the data from a Pandas DataFrame object as a tabular file, such as CSV or TSV.
67
+
68
+ Args:
69
+ df (pd.DataFrame): Pandas DataFrame with data to be saved.
70
+ path (str): Path to file where data is saved.
71
+ """
72
+ dirpath = os.path.dirname(os.path.abspath(path)) or "."
73
+ suffix = Path(path).suffix
74
+ sep = get_tabular_separator(ext=suffix)
75
+ fd, tmp_path = tempfile.mkstemp(prefix="tmp_petpal_", dir=dirpath, suffix=suffix)
76
+ os.close(fd)
77
+ try:
78
+ df.to_csv(tmp_path, sep=sep)
79
+ os.replace(tmp_path, path)
80
+ finally:
81
+ if os.path.exists(tmp_path):
82
+ try:
83
+ os.remove(tmp_path)
84
+ except OSError:
85
+ pass
86
+
87
+ def save(self, df: pd.DataFrame, path: str) -> None:
88
+ """API that applies the table saving function assigned to `self._saver`.
89
+
90
+ Args:
91
+ df (pd.DataFrame): Pandas DataFrame with data to be saved.
92
+ path (str): Path to file where data is saved.
93
+ """
94
+ self._saver(df, path)
@@ -642,7 +642,7 @@ class ImageToImageStep(FunctionBasedStep):
642
642
  """
643
643
  defaults = dict(name=name, function=windowed_motion_corr_to_target,
644
644
  input_image_path='', output_image_path='',
645
- motion_target_option='weighted_series_sum', w_size=60.0,
645
+ motion_target_option='weighted_series_sum', window_duration=60.0,
646
646
  verbose=verbose)
647
647
  override_dict = defaults | overrides
648
648
  try:
@@ -4,17 +4,23 @@ Provides methods to motion correct 4D PET data. Includes method
4
4
  4D input data to optimize contrast when computing motion correction or
5
5
  registration.
6
6
  """
7
+ from typing import Optional
7
8
  import ants
8
9
  import numpy as np
10
+ import pandas as pd
11
+ from scipy.spatial.transform import Rotation
9
12
 
10
13
  from petpal.utils.useful_functions import gen_nd_image_based_on_image_list
11
-
12
-
13
14
  from .motion_target import determine_motion_target
14
15
  from ..utils import image_io
15
- from ..utils.scan_timing import ScanTimingInfo, get_window_index_pairs_for_image
16
- from ..utils.useful_functions import weighted_series_sum_over_window_indecies
17
- from ..utils.image_io import get_half_life_from_nifti
16
+ from ..utils.scan_timing import (ScanTimingInfo,
17
+ get_window_index_pairs_from_durations,
18
+ get_window_index_pairs_for_image)
19
+ from ..utils.useful_functions import (weighted_series_sum_over_window_indices,
20
+ coerce_outpath_extension)
21
+ from ..utils.image_io import get_half_life_from_nifti, safe_copy_meta
22
+ from ..io.table import TableSaver
23
+ from ..io.image import ImageLoader
18
24
 
19
25
 
20
26
  def motion_corr(input_image_path: str,
@@ -425,11 +431,230 @@ def motion_corr_frames_above_mean_value_to_t1(input_image_path: str,
425
431
  type_of_transform=type_of_transform,
426
432
  transform_metric=transform_metric)
427
433
 
434
+ class MotionCorrect:
435
+ """Run windowed motion correction on an image and save the result"""
436
+ def __init__(self,
437
+ image_loader: Optional[ImageLoader] = None,
438
+ table_saver: Optional[TableSaver] = None):
439
+ self.image_loader = image_loader or ImageLoader()
440
+ self.table_saver = table_saver or TableSaver()
441
+ self.input_img = None
442
+ self.target_img = None
443
+ self.scan_timing = None
444
+ self.half_life = None
445
+ self.reg_kwargs = self.default_reg_kwargs
446
+
447
+ @property
448
+ def default_reg_kwargs(self) -> dict:
449
+ """Default registration arguments passed on to :py:func:`~ants.registration`."""
450
+ reg_kwargs_default = {'aff_metric' : 'mattes',
451
+ 'write_composite_transform': True,
452
+ 'interpolator' : 'linear',
453
+ 'type_of_transform' : 'DenseRigid'}
454
+ return reg_kwargs_default
455
+
456
+ def set_reg_kwargs(self, **reg_kwargs):
457
+ """Modify the registration arguments passed on to :py:func:`~ants.registration`."""
458
+ self.reg_kwargs.update(**reg_kwargs)
459
+
460
+ def get_input_scan_properties(self, input_image_path: str):
461
+ """Load input image and get half life and scan timing."""
462
+ self.input_img = self.image_loader.load(filename=input_image_path)
463
+ self.half_life = get_half_life_from_nifti(image_path=input_image_path)
464
+ self.scan_timing = ScanTimingInfo.from_nifti(image_path=input_image_path)
465
+
466
+ def get_target_img(self, input_image_path: str, motion_target_option: str | tuple):
467
+ """Get the motion target and load it as an image."""
468
+ motion_target_path = determine_motion_target(motion_target_option=motion_target_option,
469
+ input_image_path=input_image_path)
470
+ self.target_img = self.image_loader.load(filename=motion_target_path)
471
+
472
+ def window_index_pairs(self, window_duration: float=300):
473
+ """The pair of indices corresponding to each window in the image."""
474
+ return get_window_index_pairs_from_durations(frame_durations=self.scan_timing.duration,
475
+ window_duration=window_duration)
476
+
477
+ def window_target_img(self, start_index: int, end_index: int):
478
+ """Calculates the sum over frames in the target image within the provided time window."""
479
+ return weighted_series_sum_over_window_indices(input_image_4d=self.input_img,
480
+ output_image_path=None,
481
+ window_start_id=start_index,
482
+ window_end_id=end_index,
483
+ half_life=self.half_life,
484
+ image_frame_info=self.scan_timing)
485
+
486
+ @staticmethod
487
+ def ants_xfm_to_rigid_pars(ants_xfm: ants.ANTsTransform):
488
+ """Convert an ants transform object to six parameters (3 translation, 3 rotation) and the
489
+ center reference point."""
490
+ xfm_in = np.reshape(ants_xfm.parameters,(4,3))
491
+ rot_matrix = xfm_in[:3,:]
492
+ translate_matrix = xfm_in[3,:]
493
+
494
+ scipy_rotation = Rotation.from_matrix(rot_matrix)
495
+ rot_pars = -scipy_rotation.as_euler('xyz',degrees=True)
496
+
497
+ xfm_out = list(rot_pars)+list(translate_matrix)+list(ants_xfm.fixed_parameters)
498
+ return xfm_out
499
+
500
+ def run_motion_correct(self, window_duration: float=300):
501
+ """Run motion correction on the input image to the target image."""
502
+ moco_img_stack = []
503
+ window_xfm_stack = []
504
+ input_img_list = ants.ndimage_to_list(self.input_img)
505
+ for _, (st_id, end_id) in enumerate(zip(*self.window_index_pairs(window_duration=window_duration))):
506
+ window_target_img = self.window_target_img(start_index=st_id, end_index=end_id)
507
+ window_registration = ants.registration(fixed=self.target_img,
508
+ moving=window_target_img,
509
+ **self.reg_kwargs)
510
+ window_xfm = ants.read_transform(window_registration['fwdtransforms'])
511
+ window_xfm_stack.append(self.ants_xfm_to_rigid_pars(window_xfm))
512
+ for frm_id in range(st_id, end_id):
513
+ moco_img_stack.append(ants.apply_transforms(fixed=self.target_img,
514
+ moving=input_img_list[frm_id],
515
+ transformlist=window_registration['fwdtransforms']))
516
+ moco_img = gen_timeseries_from_image_list(moco_img_stack)
517
+ return moco_img, np.asarray(window_xfm_stack)
518
+
519
+ def save_xfm_parameters(self, window_xfms: np.ndarray, filename: str):
520
+ """Save window transform parameters as a table.
521
+
522
+ Args:
523
+ window_xfms (np.ndarray): Rigid transform parameters ordered as rotation, translation,
524
+ centerpoint, then X, Y, Z axis, totalling 9 parameters for each window.
525
+ filename (str): Path to where table will be saved, including extension.
526
+
527
+ Raises:
528
+ ValueError: If transform type does not containt 'Rigid'. Saving transform parameters is
529
+ currently only available for rigid transforms."""
530
+ if 'Rigid' not in self.reg_kwargs['type_of_transform']:
531
+ raise ValueError("Saving transform parameters is only available for rigid "
532
+ "registrations. Current transform type: "
533
+ f"{self.reg_kwargs['type_of_transform']}")
534
+ xfm_columns = ['rot_x',
535
+ 'rot_y',
536
+ 'rot_z',
537
+ 'tra_x',
538
+ 'tra_y',
539
+ 'tra_z',
540
+ 'cen_x',
541
+ 'cen_y',
542
+ 'cen_z']
543
+ xfms_df = pd.DataFrame(data=window_xfms,
544
+ columns=xfm_columns)
545
+ xfms_df.index.name = 'window'
546
+ csv_filename = coerce_outpath_extension(path=filename, ext='.csv')
547
+ self.table_saver.save(xfms_df,csv_filename)
548
+
549
+ def __call__(self, input_image_path: str,
550
+ output_image_path: str,
551
+ motion_target_option: str | tuple,
552
+ window_duration: float = 300,
553
+ copy_metadata: bool = True,
554
+ save_xfm: bool = True,
555
+ **reg_kwargs):
556
+ """Motion correct a dynamic PET image.
557
+
558
+ Divides image into segments of duration in seconds `window_duration` and register each frame
559
+ to a target image, using the same transformation on for every frame in each window.
560
+
561
+ Args:
562
+ input_image_path (str): Path to dynamic PET image.
563
+ output_image_path (str): Path to which motion corrected image is saved.
564
+ motion_target_option (str | tuple): Path to motion target image, or specify time window
565
+ such as (0,600) or preset option such as 'mean_image'. See
566
+ :py:func:`~petpal.preproc.motion_target.determine_motion_target`.
567
+ window_duration (float): Duration of each window in seconds. Default 300.
568
+ copy_metadata (bool): Copies metadata info from input image to output image. Default
569
+ True.
570
+ save_xfm (bool): Saves motion correction transform parameters for translation,
571
+ rotation, and rotation center point. Only compatible with rigid transforms. Default
572
+ True.
573
+ """
574
+ self.get_input_scan_properties(input_image_path=input_image_path)
575
+ self.get_target_img(input_image_path=input_image_path,
576
+ motion_target_option=motion_target_option)
577
+
578
+ self.set_reg_kwargs(**reg_kwargs)
579
+
580
+ moco_img, window_xfms = self.run_motion_correct(window_duration=window_duration)
581
+
582
+ if save_xfm:
583
+ self.save_xfm_parameters(window_xfms=window_xfms, filename=output_image_path)
584
+ ants.image_write(image=moco_img, filename=output_image_path)
585
+ if copy_metadata:
586
+ safe_copy_meta(input_image_path=input_image_path, out_image_path=output_image_path)
587
+
588
+ return moco_img
589
+
590
+ def gen_timeseries_from_image_list(image_list: list[ants.core.ANTsImage]) -> ants.core.ANTsImage:
591
+ r"""
592
+ Takes a list of ANTs ndimages, and generates a 4D ndimage. Undoes :func:`ants.ndimage_to_list`
593
+ so that we take a list of 3D images and generates a 4D image.
594
+
595
+ Args:
596
+ image_list (list[ants.core.ANTsImage]): A list of ndimages.
597
+
598
+ Returns:
599
+ ants.core.ANTsImage: 4D ndimage.
600
+ """
601
+ tmp_image = gen_nd_image_based_on_image_list(image_list)
602
+ return ants.list_to_ndimage(tmp_image, image_list)
603
+
604
+
605
+ def _get_list_of_frames_above_total_mean(image_4d_path: str,
606
+ scale_factor: float = 1.0):
607
+ """
608
+ Get the frame indices where the frame mean is higher than the total mean of a 4D image.
609
+
610
+ This function calculates the mean voxel value of each frame in a 4D image and returns the
611
+ indices of the frames whose mean voxel value is greater than or equal to the mean voxel
612
+ value of the entire image, optionally scaled by a provided factor.
613
+
614
+ Args:
615
+ image_4d_path (str): Path to the input 4D PET image file.
616
+ scale_factor (float, optional): Scale factor applied to the mean voxel value of the entire
617
+ image for comparison. Must be greater than 0. Default is 1.0.
618
+
619
+ Returns:
620
+ list: A list of frame indices where the frame mean voxel value is greater than or equal to
621
+ the scaled total mean voxel value.
622
+
623
+ Example:
624
+
625
+ .. code-block:: python
626
+
627
+ from petpal.preproc.motion_corr import _get_list_of_frames_above_total_mean
628
+
629
+ frame_ids = _get_list_of_frames_above_total_mean(image_4d_path='/path/to/image.nii.gz',
630
+ scale_factor=1.2)
631
+
632
+ print(frame_ids) # Output: [0, 3, 5, ...]
633
+
634
+ Notes:
635
+ - The :func:`ants.image_read` from ANTsPy is used to read the 4D image into memory.
636
+ - The mean voxel value of the entire image is scaled by `scale_factor` for comparison with
637
+ individual frame means.
638
+ - The function uses the :func:`ants.ndimage_to_list` method from ANTsPy to convert the 4D
639
+ image into a list of 3D frames.
640
+
641
+ """
642
+ assert scale_factor > 0
643
+ image = ants.image_read(image_4d_path)
644
+ total_mean = scale_factor * image.mean()
645
+
646
+ frames_list = []
647
+ for frame_id, a_frame in enumerate(image.ndimage_to_list()):
648
+ if a_frame.mean() >= total_mean:
649
+ frames_list.append(frame_id)
650
+
651
+ return frames_list
652
+
428
653
 
429
654
  def windowed_motion_corr_to_target(input_image_path: str,
430
655
  out_image_path: str | None,
431
656
  motion_target_option: str | tuple,
432
- w_size: float,
657
+ window_duration: float,
433
658
  type_of_transform: str = 'QuickRigid',
434
659
  interpolator: str = 'linear',
435
660
  copy_metadata: bool = True,
@@ -445,6 +670,10 @@ def windowed_motion_corr_to_target(input_image_path: str,
445
670
  The motion-target will determine the space of the output image. If we provide a T1 image
446
671
  as the `motion_target_option`, the output image will be in T1-space.
447
672
 
673
+ Note:
674
+ This function is deprecated. Use :py:func:`~petpal.preproc.motion_corr.MotionCorrect`
675
+ instead.
676
+
448
677
  Args:
449
678
  input_image_path (str): Path to the input 4D PET image file.
450
679
  out_image_path (str | None): Path to save the resulting motion-corrected image. If
@@ -452,7 +681,7 @@ def windowed_motion_corr_to_target(input_image_path: str,
452
681
  motion_target_option (str | tuple): Option to determine the motion target. This can
453
682
  be a path to a specific image file, a tuple of frame indices to generate a target, or
454
683
  specific options recognized by :func:`determine_motion_target`.
455
- w_size (float): Window size in seconds for dividing the image into time sections.
684
+ window_duration (float): Window size in seconds for dividing the image into time sections.
456
685
  type_of_transform (str): Type of transformation to use in registration (default: 'QuickRigid').
457
686
  interpolator (str): Interpolation method for the transformation (default: 'linear').
458
687
  **kwargs: Additional arguments passed to :func:`ants.registration`.
@@ -462,7 +691,7 @@ def windowed_motion_corr_to_target(input_image_path: str,
462
691
 
463
692
  Workflow:
464
693
  1. Reads the input 4D image and splits it into individual frames.
465
- 2. Computes index windows based on the specified window size (`w_size`).
694
+ 2. Computes index windows based on the specified window size (`window_duration`).
466
695
  3. Extracts necessary frame timing information and the tracer's half-life.
467
696
  4. For each window:
468
697
  - Calculates a weighted sum image for the window.
@@ -477,7 +706,7 @@ def windowed_motion_corr_to_target(input_image_path: str,
477
706
  """
478
707
  input_image = ants.image_read(filename=input_image_path)
479
708
  input_image_list = ants.ndimage_to_list(input_image)
480
- window_idx_pairs = get_window_index_pairs_for_image(image_path=input_image_path, w_size=w_size)
709
+ window_idx_pairs = get_window_index_pairs_for_image(image_path=input_image_path, window_duration=window_duration)
481
710
  half_life = get_half_life_from_nifti(image_path=input_image_path)
482
711
  frame_timing_info = ScanTimingInfo.from_nifti(image_path=input_image_path)
483
712
 
@@ -490,13 +719,13 @@ def windowed_motion_corr_to_target(input_image_path: str,
490
719
  reg_kwargs = {**reg_kwargs_default, **kwargs}
491
720
 
492
721
  out_image = []
493
- for win_id, (st_id, end_id) in enumerate(zip(*window_idx_pairs)):
494
- window_tgt_image = weighted_series_sum_over_window_indecies(input_image_4d=input_image,
495
- output_image_path=None,
496
- window_start_id=st_id,
497
- window_end_id=end_id,
498
- half_life=half_life,
499
- image_frame_info=frame_timing_info)
722
+ for _, (st_id, end_id) in enumerate(zip(*window_idx_pairs)):
723
+ window_tgt_image = weighted_series_sum_over_window_indices(input_image_4d=input_image,
724
+ output_image_path=None,
725
+ window_start_id=st_id,
726
+ window_end_id=end_id,
727
+ half_life=half_life,
728
+ image_frame_info=frame_timing_info)
500
729
  window_registration = ants.registration(fixed=target_image,
501
730
  moving=window_tgt_image,
502
731
  type_of_transform=type_of_transform,
@@ -516,66 +745,3 @@ def windowed_motion_corr_to_target(input_image_path: str,
516
745
  image_io.safe_copy_meta(input_image_path=input_image_path,
517
746
  out_image_path=out_image_path)
518
747
  return out_image
519
-
520
- def gen_timeseries_from_image_list(image_list: list[ants.core.ANTsImage]) -> ants.core.ANTsImage:
521
- r"""
522
- Takes a list of ANTs ndimages, and generates a 4D ndimage. Undoes :func:`ants.ndimage_to_list`
523
- so that we take a list of 3D images and generates a 4D image.
524
-
525
- Args:
526
- image_list (list[ants.core.ANTsImage]): A list of ndimages.
527
-
528
- Returns:
529
- ants.core.ANTsImage: 4D ndimage.
530
- """
531
- tmp_image = gen_nd_image_based_on_image_list(image_list)
532
- return ants.list_to_ndimage(tmp_image, image_list)
533
-
534
-
535
- def _get_list_of_frames_above_total_mean(image_4d_path: str,
536
- scale_factor: float = 1.0):
537
- """
538
- Get the frame indices where the frame mean is higher than the total mean of a 4D image.
539
-
540
- This function calculates the mean voxel value of each frame in a 4D image and returns the
541
- indices of the frames whose mean voxel value is greater than or equal to the mean voxel
542
- value of the entire image, optionally scaled by a provided factor.
543
-
544
- Args:
545
- image_4d_path (str): Path to the input 4D PET image file.
546
- scale_factor (float, optional): Scale factor applied to the mean voxel value of the entire
547
- image for comparison. Must be greater than 0. Default is 1.0.
548
-
549
- Returns:
550
- list: A list of frame indices where the frame mean voxel value is greater than or equal to
551
- the scaled total mean voxel value.
552
-
553
- Example:
554
-
555
- .. code-block:: python
556
-
557
- from petpal.preproc.motion_corr import _get_list_of_frames_above_total_mean
558
-
559
- frame_ids = _get_list_of_frames_above_total_mean(image_4d_path='/path/to/image.nii.gz',
560
- scale_factor=1.2)
561
-
562
- print(frame_ids) # Output: [0, 3, 5, ...]
563
-
564
- Notes:
565
- - The :func:`ants.image_read` from ANTsPy is used to read the 4D image into memory.
566
- - The mean voxel value of the entire image is scaled by `scale_factor` for comparison with
567
- individual frame means.
568
- - The function uses the :func:`ants.ndimage_to_list` method from ANTsPy to convert the 4D
569
- image into a list of 3D frames.
570
-
571
- """
572
- assert scale_factor > 0
573
- image = ants.image_read(image_4d_path)
574
- total_mean = scale_factor * image.mean()
575
-
576
- frames_list = []
577
- for frame_id, a_frame in enumerate(image.ndimage_to_list()):
578
- if a_frame.mean() >= total_mean:
579
- frames_list.append(frame_id)
580
-
581
- return frames_list