-
Notifications
You must be signed in to change notification settings - Fork 35
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
Add dynamic run index <<>> option #195
Conversation
Hi, thanks for your suggestion. I considered this in the past but, without making things much more complicated, I figured you may end up with folders such as: bids/sub-001/func/
|-- sub-001_task-rest_bold.json
|-- sub-001_task-rest.nii.gz
|-- sub-001_task-rest_run-2_bold.json
`-- sub-001_task-rest_run-2_bold.nii.gz I expected that may be confusing for bids apps, when they encounter a |
I see you wrote code to retrospectively rename the run-1 images, I'll have a look and see if it doesn't complicate things too much |
bidscoin/bids.py
Outdated
@@ -6,6 +6,7 @@ | |||
|
|||
@author: Marcel Zwiers | |||
""" | |||
import os |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please replace the obsolete os.path usage with the (much better) pathlib usage (Path is already imported, i.e. this is an unnecesary import)
bidscoin/bids.py
Outdated
bidsvalue = run['datasource'].dynamicvalue(bidsvalue, cleanup=True, runtime=runtime) | ||
if bidsvalue: | ||
if cleanup: | ||
bidsvalue = cleanup_value(bidsvalue) | ||
if not bidsvalue: | ||
continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe there are a few too many nested if statements now, i.e. the first if bidsvalue:
can be discarded and then unindent and this and change it to if bidsvalue: bidsname = ..
(i.e. reverse the order of the two original if statements)
bidscoin/bids.py
Outdated
old_bidsname = remove_run_keyval(bidsname) | ||
new_bidsname = insert_bidskeyval(bidsname, 'run', '1', False) | ||
scanpath = outfolder.relative_to(bidsses) | ||
for ext in ('.nii.gz', '.nii', '.json', '.tsv', '.tsv.gz', '.bval', '.bvec'): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to not hardcode BIDS specifications but instead to use something generic like outfolder.glob(bidsname + '.*')
and then determine the extension from the result
bidscoin/plugins/nibabel2bids.py
Outdated
@@ -14,6 +14,7 @@ | |||
from typing import Union | |||
from pathlib import Path | |||
from bidscoin import bids | |||
from bidscoin.bids import add_run1_keyval |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discarding this import statement and using bids.add_run1_keyval()
below is more clean / readable. Idem for other plugins
bidscoin/bids.py
Outdated
new_bidsname = insert_bidskeyval(bidsname, 'run', '1', False) | ||
scanpath = outfolder.relative_to(bidsses) | ||
for ext in ('.nii.gz', '.nii', '.json', '.tsv', '.tsv.gz', '.bval', '.bvec'): | ||
if os.path.exists((outfolder / old_bidsname).with_suffix(ext)): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use pathlib's .is_file()
method
bidscoin/bids.py
Outdated
scanpath = outfolder.relative_to(bidsses) | ||
for ext in ('.nii.gz', '.nii', '.json', '.tsv', '.tsv.gz', '.bval', '.bvec'): | ||
if os.path.exists((outfolder / old_bidsname).with_suffix(ext)): | ||
os.rename((outfolder / old_bidsname).with_suffix(ext), (outfolder / new_bidsname).with_suffix(ext)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use pathlib's .rename() method
bidscoin/bids.py
Outdated
@@ -1869,25 +1873,64 @@ def insert_bidskeyval(bidsfile: Union[str, Path], bidskey: str, newvalue: str, v | |||
return newbidsfile | |||
|
|||
|
|||
def remove_run_keyval(bidsname: str) -> str: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function is not needed and can be achieved by calling insert_keyval(bidsname, 'run', '', False)
If you have some subjects with one run and others with multiple runs, you do get inconsistent naming of the first run when using
|
Your solution looks good to me, I might even make the new |
Perfect, thanks a lot! I like the new |
- Move core dynamic run value code from the plugins to the bids module - Move `add_run1_keyval()` code to `increment_runindex` (as they should always be executed in conjunction) Bugfix dcm2niix (add_run1_keyval: bidsname -> newbidsname)
I refactored (3c9b896) your PR a bit, i.e. I moved code that is central to bidscoin out of the plugins. But now I messed up the tests a bit... I'm not really familiar with unittest mocking, and I'm struggling to get it all right. It would be really nice if you can fix your tests and help me out :-) |
Ok, I rewrote your mock tests because I think that mocking system libraries is fragile and a bad idea |
Hello, sorry for replying so late. Mocking is good for test isolation, when a test fails we know that this concrete tested function failed and not other external factor e.g function that it used. Furthermore, one doesn't need to actually create the resources. I can see that you want to use code that you fully understand so it is easier to maintain :). |
You don't need to rewrite your tests, but if you like you can review the new tests to see if it misses anything (you'll see I need less and easier code to generate some temporary files than you did to mock I think mocking can be suitable for testing code in cases where you don't have control over certain functions (such as functions that read out sensors in an industrial process, or functions that communicate with web servers). Otherwise, I think you should stay away from mocking if you can -- especially from mocking common system libraries. The reason is that mocking is very fragile: you are mocking the functions in the code you like to test, but in fact, mocking can have side effects outside the scope of the code you like to test. In other words, if your mocking works today, it may be broken tomorrow if a dependency of the code you want to test gets updated and also starts using the function that you mocked. For instance, you are mocking |
Yeah, you are right that my tests were a bit tightly coupled to the implementation and the new tests look much cleaner and cover everything needed :). |
Thanks again for your contribution, I like it so much that I made it the new default :-) |
I am happy I could contribute a bit :). |
Feature request: dynamic run index naming
Hello,
I would like to suggest a small extension to the way how dynamic run indexes are handled. Right now, with option run
<<1>>
, each filename will getrun
keyval, even when it doesn’t need it when no more runs are present. It would be amazing if there was an option to automatically increment run index while not to include it in the filename if it is not needed, as that pollutes a bit the filenames.I suggest a solution which preserves the original behavior for dynamic
<<1>>
run index, but for<<>>
empty run index files are named withoutrun
keyval. Only when file with the same name is found, bidscoin addsrun-2
index to its filename and retrospectively addsrun-1
to the corresponding other file with which it has matched. Next found run getsrun-3
,run-4
etc.What do you think about the solution?
I might have missed some errors this could introduce, although I tested the results.
As an alternative approach, I've been thinking about making a script that would run after bidscoin, renaming the files. But that would also mean all references to the files would need to be renamed.