Skip to content

Allow nufft for traj where nufft/fft splitting fails#972

Merged
ckolbPTB merged 4 commits intomainfrom
default_full_nufft
Mar 10, 2026
Merged

Allow nufft for traj where nufft/fft splitting fails#972
ckolbPTB merged 4 commits intomainfrom
default_full_nufft

Conversation

@ckolbPTB
Copy link
Copy Markdown
Collaborator

@ckolbPTB ckolbPTB commented Mar 2, 2026

For some trajectory (e.g. EPI) the FourierOp cannot be created as a combination of FFTOp and NUFFTOp. So far this led to an error and users had to create their own FourierOp. This PR defaults the FourierOp to NufftOp for all dimensions. Not optimal but at least we get an image.

@ckolbPTB ckolbPTB requested a review from schuenke March 2, 2026 22:26
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 2, 2026

Coverage

Coverage Report
FileStmtsMissCoverMissing
src/mrpro
   _version.py6267%7–8
src/mrpro/algorithms/csm
   inati.py25196%43
src/mrpro/algorithms/dcf
   dcf_voronoi.py55493%15, 55–56, 89
src/mrpro/algorithms/optimizers
   adam.py30680%108, 125–129
   cg.py52198%139
   pdhg.py81396%180–181, 187
   pgd.py53492%108, 153–156
src/mrpro/algorithms/reconstruction
   DirectReconstruction.py28293%62, 78
   Reconstruction.py47491%54–56, 122
   RegularizedIterativeSENSEReconstruction.py51296%123, 152
   TotalVariationRegularizedReconstruction.py52492%104, 110, 127, 130
src/mrpro/data
   AcqInfo.py165796%49, 56, 134–135, 137, 243, 367
   Dataclass.py3102692%59, 320, 336, 402, 460–462, 475, 570, 590–591, 593, 608–609, 611, 658–659, 664–665, 850–851, 876, 883, 888–889, 891
   DcfData.py33197%62
   EncodingLimits.py97397%37, 127, 130
   IData.py1781293%138, 155–156, 217–225, 258, 304, 322
   IHeader.py174895%49, 98–101, 284, 288, 292, 296
   KData.py2302490%122–123, 138, 145, 156–167, 178, 186, 197, 236, 258–260, 307–308, 380, 546, 548, 620
   KHeader.py1761393%115–121, 148, 196, 203–204, 231–238
   KNoise.py22195%44
   KTrajectory.py95397%163, 165, 185
   QData.py32197%43
   Rotation.py7374294%104, 202, 339, 437, 481, 499, 586, 588, 597, 634, 636, 699, 776, 781, 784, 799, 816, 821, 897, 1085, 1090, 1093, 1117, 1121, 1145, 1265, 1267, 1275–1276, 1340, 1422, 1626, 1633–1635, 1694, 1790, 1942, 1977, 1981, 2156, 2177
   SpatialDimension.py1501987%34, 103, 146, 158, 278–280, 293–295, 329, 347, 360, 373, 386, 399, 408–409, 437
src/mrpro/data/traj_calculators
   KTrajectoryCalculator.py26292%84, 95
   KTrajectoryCartesian.py31487%129–132, 136
   KTrajectoryIsmrmrd.py19195%57
   KTrajectorySpiral2D.py571377%63–66, 69, 71, 73, 75, 77, 105, 107, 134–136
src/mrpro/operators
   AveragingOp.py38295%73, 115
   CartesianSamplingOp.py112496%152, 191, 266, 387
   ConjugateGradientOp.py89792%62, 64, 100, 106, 228, 230, 233
   ConstraintsOp.py85495%78, 80, 250, 255
   EndomorphOperator.py28293%71, 77
   FiniteDifferenceOp.py29293%40, 126
   FourierOp.py105991%85–87, 192–193, 212, 257, 321, 326
   Functional.py70297%116, 118
   GridSamplingOp.py1651591%72–73, 82–83, 90–91, 94, 96, 98, 282, 290–291, 306, 312–313
   LinearOperator.py202697%217, 255, 296, 305, 313, 330
   LinearOperatorMatrix.py1741989%99, 136, 169, 178, 183, 192–195, 208–211, 219–220, 225–226, 238, 347, 377, 404
   MultiIdentityOp.py16288%58, 63
   NonUniformFastFourierOp.py1981095%72, 99, 221, 223, 261, 263, 340, 397, 471, 476
   Operator.py88397%82, 115, 125
   PatchOp.py49394%93, 129, 144
   ProximableFunctionalSeparableSum.py44393%118, 213, 224
   SliceProjectionOp.py1781094%45, 62, 64, 70, 154, 180, 216, 253, 290, 330
   WaveletOp.py119397%184, 228, 254
   ZeroPadOp.py18194%30
src/mrpro/operators/functionals
   SSIM.py73790%60–80, 82, 86, 114, 147
src/mrpro/operators/models
   EPG.py2551993%31–32, 283, 288, 304–306, 326–327, 332, 356, 361, 386, 391, 546, 600, 739, 756, 783
src/mrpro/phantoms
   EllipsePhantom.py43295%66, 131
   brainweb.py2974087%276, 290–294, 349–359, 398, 454–457, 479–480, 485–486, 488–489, 493, 501, 508–509, 550, 621, 624–625, 644, 653–656, 667, 669, 700–701, 715, 723
   fastmri.py1061091%50–51, 59, 65, 162, 169–171, 174–175, 189
   m4raw.py74495%58–59, 74, 76
   mdcnn.py71790%58, 62–63, 70, 82, 88, 135
src/mrpro/utils
   RandomGenerator.py1561590%23–24, 36, 38, 188, 212, 428, 446, 528, 799, 829–832, 895, 898
   ema.py42490%49, 74, 78, 82
   filters.py61198%46
   indexing.py177199%321
   pad_or_crop.py39685%40, 43, 46, 49, 66, 73
   reshape.py1531093%176, 370, 482–484, 505, 508–509, 515, 530
   slice_profiles.py49688%21, 37, 119–122, 155
   split_idx.py10280%43, 47
   summarize.py57689%40–41, 70–73, 81
   unit_conversion.py721579%34, 44, 51, 53, 62, 69, 71, 78, 80, 89, 100, 121, 123, 144, 146
TOTAL791047594% 

Tests Skipped Failures Errors Time
3050 0 💤 0 ❌ 0 🔥 2m 16s ⏱️

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 2, 2026

📚 Documentation

📁 Download as zip
🔍 View online

@fzimmermann89
Copy link
Copy Markdown
Member

I am a bit confused why epi does not work:
Is the full shot in k0 like a spiral or is each line+blip in k0?If the latter, can you cut by indexing the blip and get an FFT?

Or how does an epi trajectory look like? @schuenke

@schuenke
Copy link
Copy Markdown
Collaborator

schuenke commented Mar 3, 2026

For our implementation, each line is a separate acquisition and we either use a symmetric trajectory (every 2nd line is acquired in reverse order) or a flyback trajectory (each line is acquired from -k to +k like a typical cartesian acquisition. However, we often use ramp sampling, so that only the center points are on a cartesian grid and the first and last few samples are closer together (adc dwell is constant, but due to non-constant gradients we have samller steps in kx direction).

Note: Currently, we also use the pp.calculate_kspace() function to calculate the "nominell" trajectory we store in the mrd header file. It's been a while, but as far as I remember the calculated trajectories for the symmetric case where not perfectly matching and the default repeat detection did not recognize all kx points as identical for the different ky positions.

This is a typical trajectory for an 8x8 symmetric EPI with ramp sampling and readout oversampling:
image

In this case, the trajectories in non-reverse and reverse direction match quite well.

However, if we use the same bandwidth and nx=ny=64, they get shifted as we can see when we zoom in:
image

The reason is that the adc start time has to be aligned to the RF (!) raster time and not the ADC raster time and rf_raster_time >> adc_raster time:
image

@fzimmermann89
Copy link
Copy Markdown
Member

fzimmermann89 commented Mar 3, 2026

I am confused why this does not work with the current code (might be #842 related?)

Do you have an mrd file for debugging?

@ckolbPTB ckolbPTB merged commit 73e9bc2 into main Mar 10, 2026
24 checks passed
@ckolbPTB ckolbPTB deleted the default_full_nufft branch March 10, 2026 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants