Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding a new getitem method #2749

Merged
merged 15 commits into from
Apr 22, 2023
Merged

adding a new getitem method #2749

merged 15 commits into from
Apr 22, 2023

Conversation

shilpiprd
Copy link
Contributor

I figured that get_item method is most closely related to gradients.py file which is inside dipy/core/gradients.py folder because i saw the bvech bvals method.

Example usage

#since we can call a getItem method by simply using square brackets,
gtab = MyGradientTable(gradients)
gtab_selected = gtab[2:10] # Select b-values from 2 to 10.

Please let me know if I need to make any changes to this method.

@arokem
Copy link
Contributor

arokem commented Mar 19, 2023

Yeah - I like it! Could you please add a couple of tests of this new functionality?

@codecov
Copy link

codecov bot commented Mar 19, 2023

Codecov Report

Merging #2749 (3fae50e) into master (7ee828b) will increase coverage by 0.00%.
The diff coverage is 82.35%.

❗ Current head 3fae50e differs from pull request most recent head ac107fa. Consider uploading reports for the commit ac107fa to get more accurate results

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #2749   +/-   ##
=======================================
  Coverage   83.92%   83.92%           
=======================================
  Files         132      132           
  Lines       18464    18493   +29     
  Branches     3014     3050   +36     
=======================================
+ Hits        15495    15521   +26     
- Misses       2226     2228    +2     
- Partials      743      744    +1     
Impacted Files Coverage Δ
dipy/core/sphere.py 94.04% <ø> (ø)
dipy/data/fetcher.py 36.83% <ø> (ø)
dipy/io/dpy.py 100.00% <ø> (ø)
dipy/nn/histo_resdnn.py 85.55% <ø> (ø)
dipy/reconst/cross_validation.py 95.74% <ø> (ø)
dipy/reconst/dki.py 96.39% <ø> (ø)
dipy/reconst/dki_micro.py 92.30% <ø> (ø)
dipy/reconst/forecast.py 90.50% <0.00%> (ø)
dipy/reconst/fwdti.py 94.28% <ø> (ø)
dipy/reconst/ivim.py 95.17% <ø> (ø)
... and 16 more

... and 13 files with indirect coverage changes

@shilpiprd
Copy link
Contributor Author

Yeah - I like it! Could you please add a couple of tests of this new functionality?

I've added the tests, please review them.

@arokem
Copy link
Contributor

arokem commented Mar 20, 2023

Thanks!

Could you add the tests on this PR? That is, merge or cherry-pick the work you have in #2750 into this branch and then close #2750. It would be best to have the functionality and the tests in the same branch.

@shilpiprd
Copy link
Contributor Author

Sure

Copy link
Member

@skoudoro skoudoro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @shilpiprd,

I agree with @arokem concerning the tests. Can you also address the comments below? Thanks!

idx = [idx]
elif isinstance(idx, slice):
idx = range(*idx.indices(len(self.bvals)))
mask = self.bvals[idx] > 200
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 200? is it possible to get this value in a other way?

Copy link
Contributor Author

@shilpiprd shilpiprd Mar 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I'll add a 'threshold' parameter to the definition against which values can be compared to create the mask.

small_delta=0.01,
b0_threshold=50,
atol=1e-2, btens=None)
#gave default value for small_delta, big_delta and b0_threshold
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why default value? I recommend to get the value already initialized like self.big_delta , self.small_delta , etc

Copy link
Contributor Author

@shilpiprd shilpiprd Mar 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I'll change this as well.

@shilpiprd
Copy link
Contributor Author

I updated the getitem method, and realised that the test file needed to be changed as well.I changed the test file in this branch. Should I close the #2750 now?
Please review and let me know if any other changes needs to be made.

@arokem
Copy link
Contributor

arokem commented Mar 21, 2023

Yeah. I'll close the other one.

Copy link
Contributor

@arokem arokem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've thought about this a bit more. What do you think about indexing with a lower and upper bound on b-values?

dipy/core/gradients.py Outdated Show resolved Hide resolved
@@ -210,7 +210,25 @@ def bvecs(self):
denom = self.bvals + (self.bvals == 0)
denom = denom.reshape((-1, 1))
return self.gradients / denom


def __getitem__(self, idx, threshold = 200):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would set the threshold to be 0 per default.

One more thought is to implement a lower and upper bound, so that if you index:

gtab[:, 100:1000]

you would get a new gradient table with all the b-values between 100 and 1,000. I can see some use-cases for this kind of selection.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that we can probably remove the threshold argument and associated code, as it is no longer needed with the new indexing scheme.
Since now we could have: def getitem(self, bmin=0, bmax=np.inf):
And to create mask we can simply use: mask = (self.bvals >= bmin) & (self.bvals <= bmax). Using this to create mask we can remove a lot of unnecessary code as well.
and # Select b-values between 100 and 1000,
gtab = gtab[bmin=100, bmax=1000]
What do you think of this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea!

however, a deprecation cycle will be needed. a lot people are using this default behavior so we will have to keep both behavior for 1 year (2-3 releases) at least.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So are you sugggesting something like this?
def getitem(self, bmin=0, bmax=np.inf, threshold=0):
# Use the new behavior if bmin and bmax are provided
if bmin != 0 or bmax != np.inf:
mask = (self.bvals >= bmin) & (self.bvals <= bmax)
else:
# Use the old behavior if bmin and bmax are not provided
mask = self.bvals > threshold

Copy link
Member

@skoudoro skoudoro Mar 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost, something like:

from dipy.utils.deprecator import deprecated_params
...
@deprecated_params('threshold', None, since='1.17', until='1.19')
def getitem(self, bmin=0, bmax=np.inf, threshold=0):
      if threshold and not bmin:
           bmin = threshold
      # priority to bmin in alll the other case
      mask = (self.bvals >= bmin) & (self.bvals <= bmax)
      ...

Does it make sense for both of you @arokem? @shilpiprd ? (not tested, need to be checked)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, so should i create a commit for this? I mean the method looks correct.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, update the code and test it to make sure it works as expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I modified the gradients file and tests_gradients.py as well. Please review it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could somebody please manually trigger the checks on my last commit?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realised there were slight problems with the previous code. I've corrected them. Could somebody please review them. I think it should pass all the checks now.

@shilpiprd
Copy link
Contributor Author

The test file is passing all checks. It is failing one check but I can't seem to figure out where the error is because it says PASSED everywhere.
Is there a need to look into this ? or is it mergable?

Copy link
Member

@skoudoro skoudoro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @shilpiprd,

See below my comment and sorry for the late review.



@deprecated_params('threshold', None, since='1.17', until='1.19')
def getitem(self, bmin=0, bmax=np.inf, threshold=0):
Copy link
Member

@skoudoro skoudoro Mar 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, we got lost here.

The goal was to have the slicing capabilities. However, we loose it from your initial implementation.
we can not do anymore gtab[100:200].

we should get back to that. we just need a warning if the slicing is really different from the b0_treshold.

Sorry for that. Can you revert your code, use __get_item__, and in case of a slice, you check if the lower bound is similar to b0_thresold. If yes, nothing to do, if no, just update self.b0_thresold to this value and warn the user

Does it make sense? we do not need(bmin, bmax, thresold)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, you're right. We did get deviated from the original method. I changed it, Did you mean something like this? If you'd agree with this, i'll change the test cases as well.

Comment on lines 216 to 218
if bmin != self.b0_threshold:
self.b0_threshold = bmin
warn("Updating b0_threshold to {} for slicing.".format(bmin), UserWarning, stacklevel=2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I mean something like this

@@ -210,7 +211,26 @@ def bvecs(self):
denom = self.bvals + (self.bvals == 0)
denom = denom.reshape((-1, 1))
return self.gradients / denom


def getitem(self, bmin=0, bmax=np.inf):
Copy link
Member

@skoudoro skoudoro Mar 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here it should be __get_item__(self, idx):

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'll change this

self.b0_threshold = bmin
warn("Updating b0_threshold to {} for slicing.".format(bmin), UserWarning, stacklevel=2)
# priority to bmin in all other cases
mask = (self.bvals >= bmin) & (self.bvals <= bmax)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like you did before, check if slice or just int

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry I didn't understand. Can u elaborate on what needs to be checked?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were you referring to bmin value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry. I understood what you meant.

@shilpiprd
Copy link
Contributor Author

I've added the idx parameter and modified the test file as well. Can someone please review it and allow for workflow checks to run?

@skoudoro skoudoro linked an issue Mar 31, 2023 that may be closed by this pull request
@shilpiprd
Copy link
Contributor Author

I noticed that the failing checks are not from the method that I"ve implemented.

Copy link
Member

@skoudoro skoudoro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @shilpiprd,

Can you address my comments below? Thank you

@@ -210,7 +211,35 @@ def bvecs(self):
denom = self.bvals + (self.bvals == 0)
denom = denom.reshape((-1, 1))
return self.gradients / denom


def __get_item__(self, idx):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__getitem__ instead

gtab = GradientTable(gradients)

# Test with a single index
gtab_slice1 = gtab.__get_item__(1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the goal was to do gtab[1] for example

assert np.array_equal(gtab_slice1.bvecs, np.array([[1., 0., 0.]]))

# Test with a range of indices
gtab_slice2 = gtab.__get_item__(slice(2, 5))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same, the goal is to do gtab[2:5] and not gtab.__get_item__.. Please can you update

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems there was a typo in the test file. I changed it, it should pass all the tests now.

@shilpiprd
Copy link
Contributor Author

I don't think it's my method that's failing. It says 1 check failing, but inside it says PASSED for all.

@skoudoro skoudoro merged commit 97eedf5 into dipy:master Apr 22, 2023
24 of 25 checks passed
@skoudoro
Copy link
Member

Thank you @shilpiprd, great job for your first contribution! it works like a charm. merging.

@shilpiprd shilpiprd deleted the new_method branch May 25, 2023 02:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a method to slice gtab using bvals (Eg : gtab[bvals>200])
3 participants