davidssmith / DCEMRI.jl

DCE MRI analysis in Julia
Other
37 stars 16 forks source link

Speed improvement by updating convolution code #43

Closed notZaki closed 6 years ago

notZaki commented 6 years ago

(or: how I learned to stop worrying and love the for loop)

This PR provides a speed boost, e.g. the QIBA4-with-noise validation (661x9000 points) went from 23 seconds to 14 seconds.

I thought I was being clever in #29 by using things like t[2:n] - t[1:n-1] in order to avoid for-looping - because that is how matlab raised me. But it turns out that this isn't efficient - especially because a loop is used at the last step anyways. It is much more efficient to bring everything into the loop.

According to the BenchmarkTools package, the previous implementation took 4 KiB of memory, and ~1.7 μs per run, while the newer implementation takes 480 bytes and ~1 μs per run.

notZaki commented 6 years ago

Hmm. Travis gets a CCC below the cutoff for ve - causing it to fail - but I get a larger CCC that passes the cutoff on my machine.

davidssmith commented 6 years ago

Probably a bad random noise realization. See my plans in #42.

coveralls commented 6 years ago

Coverage Status

Coverage increased (+0.9%) to 47.59% when pulling 3a0fba843b6774dc5b3d66df7eeb29d1353a5cfb on notZaki:optimizeConv into bad59c697d89dafec0a373d868c4800043536577 on davidssmith:master.

notZaki commented 6 years ago

Seems Julia behaves differently between Linux and Windows - which is why appveyor was passing the tests while Travis was failing.

The noisy data is actually nearly identical in both cases. The issue was that the precontrast signal was being incorrectly estimated. The previous implementation in science.jl/r1eff() played it safe by taking the mean of the first two frames and using that as the precontrast signal.
In a few unlucky voxels, the estimated precontrast signal was too high, so the calculated concentration curve is 'shifted down'. This shift causes trouble in poorly-enhancing voxels, such as those with low ve, because most of the curve becomes negative and can't be accurately fitted. Example is attached.
example image

The strange part is that the fitting behaviour in Windows and Linux is different for the shown curve. Windows estimates ve=eps(), while Linux estimates ve=1. Both estimates are wrong, but the Windows estimate is closer because the ground truth was 0.01. The CCC's sensitivity to outliers is probably why Linux fails the test, while Windows passes.
I don't know why changing the convolution code caused the two to behave so differently.

Anyways, I added a way of estimating the bolus arrival using cumulative sums. This way, it'll use more frames to calculate the precontrast signal - if they can be estimated, otherwise it defaults to using the first two frames. It seems to work well on the QIBA data (and defaults to '2' on demo data), and it reduced the RMSE in most cases. Example of the same curve with the new implementation is shown below: corrected image

For some odd reason, the new code also degrades the CCC in some cases (while still improving the RMSE). I relaxed the test passing criteria slightly so that everything passes. That might sound bad, but the CCC only became worse in some tests while improving in other tests, and I think overall there is an improvement - especially when considering the RMSE improvement. Maybe the testing criteria could be switched to using the RMSE instead of the CCC, since the latter is sensitive to outliers?

notZaki commented 6 years ago

Oh, and this is the comparison of the tests between the old/new implementation. The 'better' cases are bolded.

It's interesting that the RMSE improves for QIBA4 when noise is added (for ve and vp).

QIBA 4 - Noiseless

Param. Value Old New
Kt RMSE 6.97 3.96
- errMax 23.49 15.5
- CCC 0.9998 0.9990
ve RMSE 18.02 21.18
- errMax 99.99 99.99
- CCC 0.8904 0.8504
vp RMSE 23.8 18.91
- errMax 92.53 69.62
- CCC 0.9999 0.9970

QIBA 4 - with Noise

Param. Value Old New
Kt RMSE 10.91 5.5
- errMax 100 100
- CCC 0.9733 0.9948
ve RMSE 18.28 16.61
- errMax 100 100
- CCC 0.697 0.728
vp RMSE 13.15 12.11
- errMax 100 100
- CCC 0.972 0.9926

QIBA 6 - Noiseless

Param. Value Old New
Kt RMSE 0.05 0.06
- errMax 0.12 0.12
- CCC 0.9999 0.9999
ve RMSE 0.08 0.08
- errMax 0.23 0.23
- CCC 0.9999 0.9999

QIBA 6 - with Noise

Param. Value Old New
Kt RMSE 19.97 6.25
- errMax 100 100
- CCC 0.865 0.861
ve RMSE 16.07 6.56
- errMax 100 100
- CCC 0.861 0.962
davidssmith commented 6 years ago

I'm still looking this over, but it looks good to me. I want to be doubly sure I understand it before I merge a change that changes the validation numbers, though.

In principle switching to RMSE as the @test criterion is ok, but I'm not sure being sensitive to outliers is a bad thing.

notZaki commented 6 years ago

Closing this in favor of PR #44