pypulseq
pypulseq copied to clipboard
Implemented rotate3D. Fix for rotate.
Implemented rotate3D. Changed rotate to have the correct rotation direction when rotating around the y-axis. The direction for rotating around the x- and z-axis is not changed.
Coverage Report
| Tests | Skipped | Failures | Errors | Time |
|---|---|---|---|---|
| 1324 | 18 :zzz: | 6 :x: | 0 :fire: | 3m 6s :stopwatch: |
@BenWilhelmUniklinikFreiburg All source code needs to be in src/pypulseq. So you have to move the rotate3D.py file in order for the code to work and the tests to run.
@schuenke sorry, not sure how that happened. The files should be in the correct place now.
@BenWilhelmUniklinikFreiburg I had a look at the tests and realized that you were only checking for val_a - val_b > tol without using abs() first, which means that the condition was never true for val_a beeing smaller than val_b. When fixing it, I realized that the tests failed.
For easier debugging, I introduced pytest.mark.parametrize and splitted the one test function with 3 assert statements into 3 test functions with 1 assert statement, each. Now it is obvious, that only the case where you provide the doubled rotation matrix fails.
Maybe you can check yourself what causes the problem. At least in one case the grad.last attribute was the problem.
Ah, and it's NOT failing for all cases. All multiples of pi/2 work fine 😄
Just a note: the Approx class in test_sequence.py should be able to handle checking the equality of arbitrary objects, as an extension of pytest.approx. So it can be used for checking the equality of gradient objects with tolerances. No need to write a custom compare_gradients function for each test script. Previously the Approx class was in a separate utils file in the tests folder, where I think it should be.
E.g. checking the equality of blocks works like this: https://github.com/imr-framework/pypulseq/blob/1c623fe0ff7ad4908ccb6c634ae9c9ae33e0630d/tests/test_sequence.py#L319
It seems to me that tests fails for pypulseq.make_extended_trapezoid('y', [0, 5, 1, 3], convert_to_arbitrary=True, times=[1, 3, 4, 7]) when angles are 0.1, 1.0, 60.0, 400.1, -0.1 and -1.0 and rotation axis is x. It seems that rotation via rotate3D by the composite rotation_matrix @ rotation_matrix generate waveforms along y and z with the same area as the original y waveform. Maybe there is some rounding issues of the scaling factors given by the composite matrix which do not arise when applying two separate rotations - I am trying to investigate and will open a PR to @BenWilhelmUniklinikFreiburg's fork if I manage to fix this.
Ok, scaling the area together with the other parameters in scale_grad fixes the inconsistency:
scaled_grad = copy(grad)
if scaled_grad.type == 'trap':
scaled_grad.amplitude = scaled_grad.amplitude * scale
scaled_grad.flat_area = scaled_grad.flat_area * scale
else:
scaled_grad.waveform = scaled_grad.waveform * scale
scaled_grad.first = scaled_grad.first * scale
scaled_grad.last = scaled_grad.last * scale
scaled_grad.area = scaled_grad.area * scale
I think this is correct, isn't it? After all, it seems that area is a parameter for arbitrary grad events - in other functions such as add_gradients, area is updated as well.
I opened a PR to @BenWilhelmUniklinikFreiburg fork with my changes - if merged, it should update this PR as well.
It also refactors tests so that it uses existing Approx, now moved to conftest.py to be re-used in different tests, as suggested by @FrankZijlstra.
As Approx was quite slow for test_rotation3D_vs_rotation, I refactored to speed it up. I later noticed that maybe test_rotation3D_vs_rotation generates unecessary long arbitrary waveforms (60000 points) - perhaps that is the reason for slow test runtime? If you like the older Approx version better, we can try reduce waveforms length and see if this is enough to achieve reasonable test runtime.