brainles-preprocessing 0.6.12__tar.gz → 0.6.14__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 (124) hide show
  1. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/PKG-INFO +4 -3
  2. brainles_preprocessing-0.6.14/brainles_preprocessing/utils/zenodo.py +352 -0
  3. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/pyproject.toml +3 -3
  4. brainles_preprocessing-0.6.12/brainles_preprocessing/utils/zenodo.py +0 -238
  5. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/LICENSE +0 -0
  6. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/README.md +0 -0
  7. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/__init__.py +0 -0
  8. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/brain_extraction/__init__.py +0 -0
  9. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/brain_extraction/brain_extractor.py +0 -0
  10. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/brain_extraction/hd_bet.py +0 -0
  11. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/brain_extraction/synthstrip.py +0 -0
  12. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/brats/__init__.py +0 -0
  13. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/brats/t1_centric.py +0 -0
  14. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/brats/t1c_centric.py +0 -0
  15. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/cli.py +0 -0
  16. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/constants.py +0 -0
  17. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/defacing/__init__.py +0 -0
  18. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/defacing/defacer.py +0 -0
  19. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/defacing/quickshear/__init__.py +0 -0
  20. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/defacing/quickshear/nipy_quickshear.py +0 -0
  21. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/defacing/quickshear/quickshear.py +0 -0
  22. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/modality.py +0 -0
  23. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/n4_bias_correction/__init__.py +0 -0
  24. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/n4_bias_correction/n4_bias_corrector.py +0 -0
  25. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/n4_bias_correction/sitk/__init__.py +0 -0
  26. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/n4_bias_correction/sitk/sitk_n4_bias_corrector.py +0 -0
  27. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/normalization/__init__.py +0 -0
  28. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/normalization/normalizer_base.py +0 -0
  29. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/normalization/percentile_normalizer.py +0 -0
  30. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/normalization/windowing_normalizer.py +0 -0
  31. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/preprocessor/__init__.py +0 -0
  32. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/preprocessor/atlas_centric_preprocessor.py +0 -0
  33. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/preprocessor/native_space_preprocessor.py +0 -0
  34. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/preprocessor/preprocessor.py +0 -0
  35. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/ANTs/ANTs.py +0 -0
  36. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/ANTs/TODO_ANTs_parameters.py +0 -0
  37. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/ANTs/__init__.py +0 -0
  38. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/__init__.py +0 -0
  39. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/elastix/__init__.py +0 -0
  40. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/elastix/elastix.py +0 -0
  41. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/greedy/__init__.py +0 -0
  42. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/greedy/greedy.py +0 -0
  43. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/__init__.py +0 -0
  44. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg.py +0 -0
  45. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/bin/groupwise_niftyreg_params.sh +0 -0
  46. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/bin/groupwise_niftyreg_run.sh +0 -0
  47. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/bin/reg_aladin +0 -0
  48. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/bin/reg_average +0 -0
  49. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/bin/reg_f3d +0 -0
  50. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/bin/reg_jacobian +0 -0
  51. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/bin/reg_measure +0 -0
  52. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/bin/reg_resample +0 -0
  53. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/bin/reg_tools +0 -0
  54. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/bin/reg_transform +0 -0
  55. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/AffineDeformationFieldKernel.h +0 -0
  56. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/AladinContent.h +0 -0
  57. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/BlockMatchingKernel.h +0 -0
  58. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/CPUAffineDeformationFieldKernel.h +0 -0
  59. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/CPUBlockMatchingKernel.h +0 -0
  60. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/CPUConvolutionKernel.h +0 -0
  61. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/CPUKernelFactory.h +0 -0
  62. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/CPUOptimiseKernel.h +0 -0
  63. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/CPUResampleImageKernel.h +0 -0
  64. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/ConvolutionKernel.h +0 -0
  65. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/Kernel.h +0 -0
  66. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/KernelFactory.h +0 -0
  67. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/OptimiseKernel.h +0 -0
  68. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/Platform.h +0 -0
  69. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/ResampleImageKernel.h +0 -0
  70. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_ReadWriteImage.h +0 -0
  71. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_ReadWriteMatrix.h +0 -0
  72. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_aladin.cpp +0 -0
  73. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_aladin.h +0 -0
  74. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_aladin_sym.cpp +0 -0
  75. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_aladin_sym.h +0 -0
  76. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_base.h +0 -0
  77. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_blockMatching.h +0 -0
  78. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_dti.h +0 -0
  79. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_f3d.h +0 -0
  80. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_f3d2.h +0 -0
  81. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_f3d_sym.h +0 -0
  82. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_femTrans.h +0 -0
  83. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_globalTrans.h +0 -0
  84. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_kld.h +0 -0
  85. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_lncc.h +0 -0
  86. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_localTrans.h +0 -0
  87. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_localTrans_jac.h +0 -0
  88. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_localTrans_regul.h +0 -0
  89. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_macros.h +0 -0
  90. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_maths.h +0 -0
  91. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_maths_eigen.h +0 -0
  92. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_measure.h +0 -0
  93. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_mind.h +0 -0
  94. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_nmi.h +0 -0
  95. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_optimiser.cpp +0 -0
  96. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_optimiser.h +0 -0
  97. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_resampling.h +0 -0
  98. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_splineBasis.h +0 -0
  99. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_ssd.h +0 -0
  100. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_stringFormat.h +0 -0
  101. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/_reg_tools.h +0 -0
  102. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/nifti1.h +0 -0
  103. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/nifti1_io.h +0 -0
  104. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/png.h +0 -0
  105. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/pngconf.h +0 -0
  106. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/pngdebug.h +0 -0
  107. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/pnginfo.h +0 -0
  108. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/pnglibconf.h +0 -0
  109. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/pngpriv.h +0 -0
  110. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/pngstruct.h +0 -0
  111. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/readpng.h +0 -0
  112. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/reg_png.h +0 -0
  113. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/zlib.h +0 -0
  114. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/znzlib.h +0 -0
  115. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/niftyreg_1.5.68/include/zutil.h +0 -0
  116. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/reg_aladin +0 -0
  117. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/reg_resample +0 -0
  118. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/rigid_reg.sh +0 -0
  119. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/niftyreg/niftyreg_scripts/transform.sh +0 -0
  120. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/registration/registrator.py +0 -0
  121. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/transform.py +0 -0
  122. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/utils/__init__.py +0 -0
  123. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/utils/citation_reminder.py +0 -0
  124. {brainles_preprocessing-0.6.12 → brainles_preprocessing-0.6.14}/brainles_preprocessing/utils/generic.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brainles_preprocessing
3
- Version: 0.6.12
3
+ Version: 0.6.14
4
4
  Summary: Tool for preprocessing tasks in biomedical imaging, with a focus on (but not limited to) multi-modal brain MRI
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Python :: 3.13
20
20
  Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Programming Language :: Python :: 3.15
21
22
  Provides-Extra: all
22
23
  Provides-Extra: itk-elastix
23
24
  Provides-Extra: picsl-greedy
@@ -27,9 +28,9 @@ Requires-Dist: auxiliary (>=0.3.1)
27
28
  Requires-Dist: brainles_hd_bet (>=0.0.10,<1.0.0)
28
29
  Requires-Dist: itk-elastix (>=0.20.0,<0.21.0) ; extra == "all" or extra == "itk-elastix"
29
30
  Requires-Dist: loguru (>=0.7.3,<0.8.0)
30
- Requires-Dist: nibabel (>=3.2.1)
31
+ Requires-Dist: nibabel (>=5.0.0)
31
32
  Requires-Dist: nipreps-synthstrip (>=0.0.1,<0.0.2) ; extra == "all" or extra == "synthstrip"
32
- Requires-Dist: numpy (>=1.23.0,<2.0.0)
33
+ Requires-Dist: numpy (>=1.23.0)
33
34
  Requires-Dist: picsl_greedy (>=0.0.6,<0.0.7) ; extra == "all" or extra == "picsl-greedy"
34
35
  Requires-Dist: rich (>=13.6.0,<14.0.0)
35
36
  Requires-Dist: scipy (>=1.14,<2.0)
@@ -0,0 +1,352 @@
1
+ from __future__ import annotations
2
+
3
+ import shutil
4
+ import tempfile
5
+ import zipfile
6
+ from pathlib import Path
7
+ from typing import ClassVar, Dict, List, Tuple
8
+
9
+ import requests
10
+ from loguru import logger
11
+ from rich.progress import Progress, SpinnerColumn, TextColumn
12
+
13
+ # (connect, read) timeout in seconds for every request to Zenodo.
14
+ REQUEST_TIMEOUT = (10, 60)
15
+
16
+ ATLASES_FOLDER = Path(__file__).parent.parent / "registration" / "atlases"
17
+ ATLASES_RECORD_ID = "15236131"
18
+
19
+ SYNTHSTRIP_FOLDER = Path(__file__).parent.parent / "brain_extraction" / "weights"
20
+ SYNTHSTRIP_RECORD_ID = "16535633"
21
+
22
+
23
+ def fetch_atlases() -> Path:
24
+ """
25
+ Ensure that the required atlases are available locally, downloading them if necessary.
26
+
27
+ Returns:
28
+ Path: The path to the folder containing the atlases.
29
+ """
30
+ record = ZenodoRecord(
31
+ record_id=ATLASES_RECORD_ID,
32
+ target_dir=ATLASES_FOLDER,
33
+ label="atlases",
34
+ )
35
+ return record.fetch()
36
+
37
+
38
+ def fetch_synthstrip() -> Path:
39
+ """
40
+ Ensure that the SynthStrip weights are available locally, downloading them if necessary.
41
+
42
+ Returns:
43
+ Path: The path to the folder containing the SynthStrip weights.
44
+ """
45
+ record = ZenodoRecord(
46
+ record_id=SYNTHSTRIP_RECORD_ID,
47
+ target_dir=SYNTHSTRIP_FOLDER,
48
+ label="SynthStrip",
49
+ )
50
+ return record.fetch()
51
+
52
+
53
+ class ZenodoException(Exception):
54
+ """Raised when Zenodo cannot be reached or the request fails."""
55
+
56
+ pass
57
+
58
+
59
+ class ZenodoRecord:
60
+ BASE_URL = "https://zenodo.org/api/records"
61
+
62
+ # Process-level cache: maps (record_id, target_dir) -> resolved Path
63
+ _cache: ClassVar[Dict[Tuple[str, str], Path]] = {}
64
+
65
+ def __init__(
66
+ self,
67
+ record_id: str,
68
+ target_dir: Path,
69
+ label: str = "asset",
70
+ ):
71
+ self.record_id = record_id
72
+ self.target_dir = target_dir
73
+ self.label = label
74
+
75
+ @classmethod
76
+ def clear_cache(cls) -> None:
77
+ """Clear the process-level fetch cache. Primarily intended for testing."""
78
+ cls._cache.clear()
79
+
80
+ def _cache_key(self) -> Tuple[str, str]:
81
+ return (self.record_id, str(self.target_dir))
82
+
83
+ def fetch(self) -> Path:
84
+ """Fetch the latest version of the record from Zenodo or from local storage.
85
+
86
+ Results are cached for the lifetime of the process so that repeated calls
87
+ (e.g. when processing many subjects in a loop) do not trigger redundant
88
+ Zenodo API requests.
89
+ """
90
+ key = self._cache_key()
91
+
92
+ cached = ZenodoRecord._cache.get(key)
93
+ if cached is not None:
94
+ logger.debug(f"Using cached {self.label} path: {cached}")
95
+ return cached
96
+
97
+ result = self._fetch_uncached()
98
+ ZenodoRecord._cache[key] = result
99
+ return result
100
+
101
+ def _fetch_uncached(self) -> Path:
102
+ """Perform the actual Zenodo check / download without consulting the cache."""
103
+ zenodo_response = self._get_metadata_and_files()
104
+
105
+ pattern = self._glob_pattern()
106
+ matching_folders = list(self.target_dir.glob(pattern))
107
+ latest_local = self._get_latest_version_folder_name(matching_folders)
108
+
109
+ if not latest_local:
110
+ if not zenodo_response:
111
+ msg = f"{self.label.title()} not found locally and Zenodo could not be reached."
112
+ logger.error(msg)
113
+ raise ZenodoException(msg)
114
+
115
+ logger.info(f"{self.label.title()} not found locally.")
116
+ metadata, files = zenodo_response
117
+ return self._download(metadata, files)
118
+
119
+ logger.info(f"Found local {self.label}: {latest_local}")
120
+
121
+ if not zenodo_response:
122
+ logger.warning(f"Zenodo unreachable. Using latest downloaded {self.label}.")
123
+ return self.target_dir / latest_local
124
+
125
+ metadata, files = zenodo_response
126
+ remote_version = metadata["version"]
127
+ local_version = latest_local.split("_v")[1]
128
+
129
+ if remote_version == local_version:
130
+ logger.info(f"Latest {self.label} ({remote_version}) already present.")
131
+ return self.target_dir / latest_local
132
+
133
+ logger.info(
134
+ f"New version of {self.label} available on Zenodo ({remote_version}). Replacing local copy..."
135
+ )
136
+ # The old version is only removed once the new one has been fully
137
+ # downloaded and staged, so a failed download never destroys a
138
+ # known-good local copy.
139
+ return self._download(
140
+ metadata,
141
+ files,
142
+ stale_folder=self.target_dir / latest_local,
143
+ )
144
+
145
+ def _glob_pattern(self) -> str:
146
+ return f"{self.record_id}_v*.*.*"
147
+
148
+ def _build_folder_path(
149
+ self,
150
+ version: str,
151
+ ) -> Path:
152
+ return self.target_dir / f"{self.record_id}_v{version}"
153
+
154
+ def _get_latest_version_folder_name(
155
+ self,
156
+ folders: List[Path],
157
+ ) -> str | None:
158
+ if not folders:
159
+ return None
160
+ latest = sorted(
161
+ folders,
162
+ reverse=True,
163
+ key=lambda x: tuple(map(int, str(x.name).split("_v")[1].split("."))),
164
+ )[0]
165
+ if not list(latest.glob("*")):
166
+ return None
167
+ return latest.name
168
+
169
+ def _get_metadata_and_files(self) -> Tuple[Dict, List[Dict]] | None:
170
+ """Retrieve the record metadata and its file list from Zenodo.
171
+
172
+ Zenodo's API used to expose a single ``links["archive"]`` URL pointing to
173
+ a zip of all files. That link has been removed, so the record's ``files``
174
+ list is used instead: each entry provides a ``key`` and a ``links["self"]``
175
+ download URL.
176
+
177
+ Returns:
178
+ A ``(metadata, files)`` tuple, or ``None`` if Zenodo is unreachable or
179
+ returns an unexpected response.
180
+ """
181
+ try:
182
+ response = requests.get(
183
+ f"{self.BASE_URL}/{self.record_id}", timeout=REQUEST_TIMEOUT
184
+ )
185
+ if response.status_code != 200:
186
+ error_msg = (
187
+ f"Cannot find record '{self.record_id}' on Zenodo "
188
+ f"({response.status_code=})."
189
+ )
190
+ logger.error(error_msg)
191
+ raise ZenodoException(error_msg)
192
+
193
+ data = response.json()
194
+ metadata = data["metadata"]
195
+ if not isinstance(metadata, dict) or not isinstance(
196
+ metadata.get("version"), str
197
+ ):
198
+ raise ValueError("record metadata is missing a string 'version'")
199
+
200
+ files = data.get("files")
201
+ if files:
202
+ for entry in files:
203
+ if (
204
+ not isinstance(entry, dict)
205
+ or not isinstance(entry.get("key"), str)
206
+ or not isinstance(entry.get("links", {}).get("self"), str)
207
+ ):
208
+ raise ValueError(
209
+ f"invalid file descriptor in record: {entry!r}"
210
+ )
211
+ return metadata, files
212
+
213
+ # Fallback for the deprecated archive link (kept for compatibility).
214
+ archive_url = data.get("links", {}).get("archive")
215
+ if isinstance(archive_url, str):
216
+ return metadata, [
217
+ {"key": f"{self.record_id}.zip", "links": {"self": archive_url}}
218
+ ]
219
+
220
+ logger.warning(
221
+ f"Zenodo record '{self.record_id}' does not expose any downloadable files."
222
+ )
223
+ return None
224
+
225
+ except requests.exceptions.RequestException as e:
226
+ logger.warning(f"Failed to fetch metadata from Zenodo: {e}")
227
+ return None
228
+ except (KeyError, ValueError) as e:
229
+ logger.warning(
230
+ f"Unexpected Zenodo response for record '{self.record_id}': {e}"
231
+ )
232
+ return None
233
+
234
+ def _download(
235
+ self,
236
+ metadata: Dict,
237
+ files: List[Dict],
238
+ stale_folder: Path | None = None,
239
+ ) -> Path:
240
+ folder = self._build_folder_path(metadata["version"])
241
+ self.target_dir.mkdir(parents=True, exist_ok=True)
242
+
243
+ # Stage everything in a temporary directory first, so a failed download
244
+ # never leaves a partial install or destroys an existing good version.
245
+ temp_folder = Path(
246
+ tempfile.mkdtemp(dir=self.target_dir, prefix=f".{folder.name}.tmp.")
247
+ )
248
+
249
+ logger.info(f"Downloading {self.label} from Zenodo. This may take a while...")
250
+
251
+ try:
252
+ for file in files:
253
+ key = file["key"]
254
+ file_url = file["links"]["self"]
255
+ destination = self._safe_destination(temp_folder, key)
256
+ self._download_file(file_url, destination)
257
+
258
+ if destination.suffix == ".zip":
259
+ self._extract_zip(destination, temp_folder)
260
+
261
+ if folder.exists():
262
+ shutil.rmtree(folder, ignore_errors=True)
263
+
264
+ # Install the fully staged version before removing the previous one,
265
+ # so an interrupted/failed swap can never leave us with neither.
266
+ temp_folder.rename(folder)
267
+
268
+ if (
269
+ stale_folder is not None
270
+ and stale_folder != folder
271
+ and stale_folder.exists()
272
+ ):
273
+ shutil.rmtree(
274
+ stale_folder,
275
+ onerror=lambda func, path, excinfo: logger.warning(
276
+ f"Failed to delete {path}: {excinfo}"
277
+ ),
278
+ )
279
+ except BaseException:
280
+ shutil.rmtree(temp_folder, ignore_errors=True)
281
+ raise
282
+
283
+ logger.info(f"{self.label.title()} downloaded to {folder}")
284
+ return folder
285
+
286
+ @staticmethod
287
+ def _safe_destination(base: Path, key: str) -> Path:
288
+ """Join ``key`` onto ``base`` and reject paths that escape it.
289
+
290
+ ``key`` comes from the (remote) record metadata, so it must not be able
291
+ to write outside the staging directory via ``..`` or an absolute path.
292
+ """
293
+ base_resolved = base.resolve()
294
+ destination = (base_resolved / key).resolve()
295
+ if destination != base_resolved and base_resolved not in destination.parents:
296
+ raise ZenodoException(f"Unsafe file key in record: {key!r}")
297
+ return destination
298
+
299
+ def _download_file(self, url: str, destination: Path) -> None:
300
+ chunk_size = 1024 * 1024 # 1 MiB
301
+ destination.parent.mkdir(parents=True, exist_ok=True)
302
+
303
+ try:
304
+ response = requests.get(url, stream=True, timeout=REQUEST_TIMEOUT)
305
+ if response.status_code != 200:
306
+ msg = (
307
+ f"Failed to download {self.label} file '{destination.name}'. "
308
+ f"Status code: {response.status_code}"
309
+ )
310
+ logger.error(msg)
311
+ raise ZenodoException(msg)
312
+
313
+ with Progress(
314
+ SpinnerColumn(),
315
+ TextColumn(f"[cyan]Downloading {destination.name}..."),
316
+ TextColumn("[cyan]{task.completed:.2f} MB"),
317
+ transient=True,
318
+ ) as progress:
319
+ task = progress.add_task("", total=None)
320
+ with open(destination, "wb") as f:
321
+ for chunk in response.iter_content(chunk_size=chunk_size):
322
+ if not chunk:
323
+ continue
324
+ f.write(chunk)
325
+ progress.update(task, advance=len(chunk) / (1024**2))
326
+ except requests.exceptions.RequestException as e:
327
+ msg = f"Failed to download {self.label} file '{destination.name}': {e}"
328
+ logger.error(msg)
329
+ raise ZenodoException(msg) from e
330
+
331
+ def _extract_zip(
332
+ self,
333
+ zip_path: Path,
334
+ folder: Path,
335
+ ) -> None:
336
+ with zipfile.ZipFile(zip_path) as z:
337
+ names = z.namelist()
338
+ with Progress(transient=True) as progress:
339
+ task = progress.add_task(
340
+ f"[cyan]Extracting {zip_path.name}...", total=len(names)
341
+ )
342
+ for i, name in enumerate(names):
343
+ self._safe_destination(folder, name)
344
+ z.extract(name, folder)
345
+ progress.update(task, completed=i + 1)
346
+ zip_path.unlink() # Remove the archive after extraction
347
+
348
+ # Extract any archives nested inside this one (e.g. a record whose
349
+ # archive contains the SynthStrip weights zip).
350
+ for nested in folder.rglob("*.zip"):
351
+ if nested.is_file():
352
+ self._extract_zip(nested, folder)
@@ -14,7 +14,7 @@ exclude = [
14
14
  ]
15
15
 
16
16
  name = "brainles_preprocessing"
17
- version = "0.6.12" # will be overwritten by poetry-dynamic-versioning but needs to be set
17
+ version = "0.6.14" # will be overwritten by poetry-dynamic-versioning but needs to be set
18
18
  description = "Tool for preprocessing tasks in biomedical imaging, with a focus on (but not limited to) multi-modal brain MRI"
19
19
  license = "Apache-2.0"
20
20
 
@@ -50,8 +50,8 @@ classifiers = [
50
50
  python = "^3.10"
51
51
  # core
52
52
  ttictoc = "^0.5.6"
53
- nibabel = ">=3.2.1"
54
- numpy = "^1.23.0"
53
+ nibabel = ">=5.0.0"
54
+ numpy = ">=1.23.0"
55
55
  typer = "^0.15.0"
56
56
  simpleitk = ">=2.2.1"
57
57
 
@@ -1,238 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import shutil
4
- import zipfile
5
- from io import BytesIO
6
- from pathlib import Path
7
- from typing import ClassVar, Dict, List, Tuple
8
-
9
- import requests
10
- from loguru import logger
11
- from rich.progress import Progress, SpinnerColumn, TextColumn
12
-
13
- ATLASES_FOLDER = Path(__file__).parent.parent / "registration" / "atlases"
14
- ATLASES_RECORD_ID = "15236131"
15
-
16
- SYNTHSTRIP_FOLDER = Path(__file__).parent.parent / "brain_extraction" / "weights"
17
- SYNTHSTRIP_RECORD_ID = "16535633"
18
-
19
-
20
- def fetch_atlases() -> Path:
21
- """
22
- Ensure that the required atlases are available locally, downloading them if necessary.
23
-
24
- Returns:
25
- Path: The path to the folder containing the atlases.
26
- """
27
- record = ZenodoRecord(
28
- record_id=ATLASES_RECORD_ID,
29
- target_dir=ATLASES_FOLDER,
30
- label="atlases",
31
- )
32
- return record.fetch()
33
-
34
-
35
- def fetch_synthstrip() -> Path:
36
- """
37
- Ensure that the SynthStrip weights are available locally, downloading them if necessary.
38
-
39
- Returns:
40
- Path: The path to the folder containing the SynthStrip weights.
41
- """
42
- record = ZenodoRecord(
43
- record_id=SYNTHSTRIP_RECORD_ID,
44
- target_dir=SYNTHSTRIP_FOLDER,
45
- label="SynthStrip",
46
- )
47
- return record.fetch()
48
-
49
-
50
- class ZenodoException(Exception):
51
- """Raised when Zenodo cannot be reached or the request fails."""
52
-
53
- pass
54
-
55
-
56
- class ZenodoRecord:
57
- BASE_URL = "https://zenodo.org/api/records"
58
-
59
- # Process-level cache: maps (record_id, target_dir) -> resolved Path
60
- _cache: ClassVar[Dict[Tuple[str, str], Path]] = {}
61
-
62
- def __init__(
63
- self,
64
- record_id: str,
65
- target_dir: Path,
66
- label: str = "asset",
67
- ):
68
- self.record_id = record_id
69
- self.target_dir = target_dir
70
- self.label = label
71
-
72
- @classmethod
73
- def clear_cache(cls) -> None:
74
- """Clear the process-level fetch cache. Primarily intended for testing."""
75
- cls._cache.clear()
76
-
77
- def _cache_key(self) -> Tuple[str, str]:
78
- return (self.record_id, str(self.target_dir))
79
-
80
- def fetch(self) -> Path:
81
- """Fetch the latest version of the record from Zenodo or from local storage.
82
-
83
- Results are cached for the lifetime of the process so that repeated calls
84
- (e.g. when processing many subjects in a loop) do not trigger redundant
85
- Zenodo API requests.
86
- """
87
- key = self._cache_key()
88
-
89
- cached = ZenodoRecord._cache.get(key)
90
- if cached is not None:
91
- logger.debug(f"Using cached {self.label} path: {cached}")
92
- return cached
93
-
94
- result = self._fetch_uncached()
95
- ZenodoRecord._cache[key] = result
96
- return result
97
-
98
- def _fetch_uncached(self) -> Path:
99
- """Perform the actual Zenodo check / download without consulting the cache."""
100
- zenodo_response = self._get_metadata_and_archive_url()
101
-
102
- pattern = self._glob_pattern()
103
- matching_folders = list(self.target_dir.glob(pattern))
104
- latest_local = self._get_latest_version_folder_name(matching_folders)
105
-
106
- if not latest_local:
107
- if not zenodo_response:
108
- msg = f"{self.label.title()} not found locally and Zenodo could not be reached."
109
- logger.error(msg)
110
- raise ZenodoException(msg)
111
-
112
- logger.info(f"{self.label.title()} not found locally.")
113
- metadata, archive_url = zenodo_response
114
- return self._download(metadata, archive_url)
115
-
116
- logger.info(f"Found local {self.label}: {latest_local}")
117
-
118
- if not zenodo_response:
119
- logger.warning(f"Zenodo unreachable. Using latest downloaded {self.label}.")
120
- return self.target_dir / latest_local
121
-
122
- metadata, archive_url = zenodo_response
123
- remote_version = metadata["version"]
124
- local_version = latest_local.split("_v")[1]
125
-
126
- if remote_version == local_version:
127
- logger.info(f"Latest {self.label} ({remote_version}) already present.")
128
- return self.target_dir / latest_local
129
-
130
- logger.info(
131
- f"New version of {self.label} available on Zenodo ({remote_version}). Replacing local copy..."
132
- )
133
- shutil.rmtree(
134
- self.target_dir / latest_local,
135
- onerror=lambda func, path, excinfo: logger.warning(
136
- f"Failed to delete {path}: {excinfo}"
137
- ),
138
- )
139
- return self._download(metadata, archive_url)
140
-
141
- def _glob_pattern(self) -> str:
142
- return f"{self.record_id}_v*.*.*"
143
-
144
- def _build_folder_path(
145
- self,
146
- version: str,
147
- ) -> Path:
148
- return self.target_dir / f"{self.record_id}_v{version}"
149
-
150
- def _get_latest_version_folder_name(
151
- self,
152
- folders: List[Path],
153
- ) -> str | None:
154
- if not folders:
155
- return None
156
- latest = sorted(
157
- folders,
158
- reverse=True,
159
- key=lambda x: tuple(map(int, str(x.name).split("_v")[1].split("."))),
160
- )[0]
161
- if not list(latest.glob("*")):
162
- return None
163
- return latest.name
164
-
165
- def _get_metadata_and_archive_url(self) -> Tuple[Dict, str] | None:
166
- try:
167
- response = requests.get(f"{self.BASE_URL}/{self.record_id}")
168
- if response.status_code != 200:
169
- error_msg = (
170
- f"Cannot find record '{self.record_id}' on Zenodo "
171
- f"({response.status_code=})."
172
- )
173
- logger.error(error_msg)
174
- raise ZenodoException(error_msg)
175
-
176
- data = response.json()
177
- return data["metadata"], data["links"]["archive"]
178
-
179
- except requests.exceptions.RequestException as e:
180
- logger.warning(f"Failed to fetch metadata from Zenodo: {e}")
181
- return None
182
-
183
- def _download(
184
- self,
185
- metadata: Dict,
186
- archive_url: str,
187
- ) -> Path:
188
- folder = self._build_folder_path(metadata["version"])
189
- folder.mkdir(parents=True, exist_ok=True)
190
-
191
- logger.info(f"Downloading {self.label} from Zenodo. This may take a while...")
192
-
193
- response = requests.get(archive_url, stream=True)
194
- if response.status_code != 200:
195
- msg = (
196
- f"Failed to download {self.label}. Status code: {response.status_code}"
197
- )
198
- logger.error(msg)
199
- raise ZenodoException(msg)
200
-
201
- self._extract_archive(response, folder)
202
- logger.info(f"{self.label.title()} extracted to {folder}")
203
- return folder
204
-
205
- def _extract_archive(
206
- self,
207
- response: requests.Response,
208
- folder: Path,
209
- ):
210
- chunk_size = 1024 # 1KB
211
- buffer = BytesIO()
212
-
213
- with Progress(
214
- SpinnerColumn(),
215
- TextColumn(f"[cyan]Downloading {self.label}..."),
216
- TextColumn("[cyan]{task.completed:.2f} MB"),
217
- transient=True,
218
- ) as progress:
219
- task = progress.add_task("", total=None)
220
- for chunk in response.iter_content(chunk_size=chunk_size):
221
- buffer.write(chunk)
222
- progress.update(task, advance=len(chunk) / (chunk_size**2))
223
-
224
- with zipfile.ZipFile(buffer) as z:
225
- z.extractall(folder)
226
-
227
- for file in folder.iterdir():
228
- if file.is_file() and file.suffix == ".zip":
229
- with zipfile.ZipFile(file) as inner_zip:
230
- files = inner_zip.namelist()
231
- with Progress(transient=True) as progress:
232
- task = progress.add_task(
233
- f"[cyan]Extracting inner zip...", total=len(files)
234
- )
235
- for i, f in enumerate(files):
236
- inner_zip.extract(f, folder)
237
- progress.update(task, completed=i + 1)
238
- file.unlink() # Remove inner zip after extraction