Skip to content

Random permutation of FCMA - #217

Merged
xoltar merged 10 commits into
brainiak:masterfrom
yidawang:fcma-random-permutation
Apr 28, 2017
Merged

xoltar merged 10 commits into
brainiak:masterfrom
yidawang:fcma-random-permutation

Conversation

@yidawang

Copy link
Copy Markdown
Member
  1. add random voxel permutation to FCMA;
  2. add more debugging and information logs;
  3. minor tweak for docstring format, etc.

Thanks in advance for reviewing.

@yidawang
yidawang requested a review from mihaic April 27, 2017 05:30

@xoltar xoltar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, Yida! Looks pretty good, just a few small changes needed.

raw_data2, _ = _separate_epochs(activity_data2, conditions)
else:
activity_data1 = list(mask_images(images, mask1, np.float32))
_randomize_subject_list(activity_data1, random)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

_randomize_single_subject modifies data in place (and then returns the same array). _randomize_subject_list returns a new list, containing old arrays that have been modified in place, which is why these lines work even though you're ignoring the return value. I would say either write them both to work in-place, or write them both to return new values. Mixing the two makes for confusion. If you decide you want to have _randomize_single_subject return a new array instead of working in-place, consider numpy.random.permutation instead of shuffle.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks for the suggestion! Somehow I ignored the return value here although I should put it as activity_data1 = _randomize_subject_list(activity_data1, random). I would prefer working in-place, let me modify _randomize_subject_list accordingly.

Comment thread brainiak/fcma/preprocessing.py Outdated

Parameters
----------
data: 2D array in shape [nVxels, nTRs]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nVoxels

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

Comment thread brainiak/fcma/preprocessing.py Outdated
Seed for random state used implicitly for shuffling.

Returns
data: 2D array in shape [nVxels, nTRs]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nVoxels

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

Comment thread brainiak/fcma/preprocessing.py Outdated
data = f.get_data().astype(data_type)
[d1, d2, d3, d4] = data.shape
if random == RandomType.REPRODUCIBLE:
data = _randomize_single_subject(data.reshape((d1 * d2 * d3, d4)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You could make this reshaping logic part of _randomize_single_subject (i.e. have it flatten, randomize, then reshape into whatever shape the original array was).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I didn't do this because I would like to make the input argument data of _randomize_single_subject an nVoxels x nTRs 2D array for clarity purpose.

Comment thread brainiak/fcma/preprocessing.py Outdated

logger.info(
logger.debug(
'file %s is loaded and processed, with data shape %s' %

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

logger does formatting itself, you can just do:
logger.debug('file %s is loaded and processed, with data shape $s', f.get_filename(), data.shape)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

Comment thread brainiak/fcma/voxelselector.py Outdated
"""
logger.info(
'Master starts to allocate tasks'
'Master at rank %d starts to allocate tasks' %

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same logging comments apply to this file.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

Comment thread brainiak/io.py Outdated
for f in files:
yield nib.load(str(f))
logger.info(
'file %s is read' %

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Here too. No need for str(), either.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

f is a path here, which is not supported to be the argument of numpy.load(), and presumably nibabel.load(), for numpy version before 1.12. @mihaic should be able to further justify this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Right. However, Paths work with string formatting without explicit casting to str, e.g.:
"test %s" % pathlib.Path("/")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

OK, just realized that Bryn was referring the str usage below this line :) Done.

Comment thread brainiak/fcma/preprocessing.py Outdated
Type to cast image to.
random: Optional[RandomType]
Randomize the data within subject or not.
Default NORANDOM

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You should not specify the default because you are duplicating the information in the function definition, which is visible both in the code and in the generated docs. (Of course, if you use None as a placeholder, you should explain what it stands for.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

Comment thread brainiak/io.py Outdated

import nibabel as nib
import numpy as np
import logging

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Use alphabetical order, please. I will introduce a checker real soon now®.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

Comment thread brainiak/io.py
logger.info(
'file %s is read',
f
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would say you should use the debug level for the messages you added in "io.py".

Also, do not break lines if they do not exceed the maximum line length.

Finally, the other message placement, before yield, makes more sense, but I would change the wording to "Starting to read [...]" and put a full stop at the end of the message.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done, but I would put some brief logger info to indicate in the beginning of some stages (reading file, apply masking, separating epochs, etc.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sure, but not in "io.py".

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Why not? I think within the io methods are the best places to go.
BTW, I am feeling that io.load_images_from_dir and io.load_images should be merged.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

anyway, I will leave io.py intact

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Because they are not useful. Why would a function log an info-level message saying it has been called every time it is called? Such messages could be emitted by the caller, if relevant.

We can discuss merging the load functions, but I would rather not, because "_dir" requires a suffix as well, so we would need to explain that.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

well, the suffix can be an optional argument. Let's discuss this in the next standup.

Comment thread examples/fcma/mvpa_voxel_selection.py Outdated
np.sum(mask)
)
images = io.load_images_from_dir(data_dir, extension)
images = io.load_images_from_dir(data_dir, suffix=extension)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It makes more sense to rename extension to suffix here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

Comment thread examples/fcma/voxel_selection.py Outdated
mask_file = sys.argv[3]
epoch_file = sys.argv[4]
images = io.load_images_from_dir(data_dir, extension)
images = io.load_images_from_dir(data_dir, suffix=extension)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

Comment thread brainiak/io.py
for f in files:
logger.debug(
'starts to read file %s', f
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please write sentences in log messages, at least in this file.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

Comment thread brainiak/fcma/preprocessing.py Outdated
data_list[i] = _randomize_single_subject(data_list[i], seed=i)
elif random == RandomType.UNREPRODUCIBLE:
for i in range(len(data_list)):
data_list[i] = _randomize_single_subject(data_list[i])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is better, but still mixed usage. If you're going to do the change in place, don't return a value. If you're going to return a value, it should be a new value. So if you want to use in-place, it should look like:

for subject in data_list:
    _randomize_single_subject(subject)

So neither _randomize_single_subject nor this function should return a value. Also, note since you're modifying in place, you don't need to assign to indexes, so you can use the simpler for loop.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

seed: Optional[int]
Seed for random state used implicitly for shuffling.

Returns

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good! Now just need to remove the Returns section from docs of both of these functions since they don't return anything anymore.

@xoltar
xoltar merged commit 3a4c566 into brainiak:master Apr 28, 2017
@yidawang
yidawang deleted the fcma-random-permutation branch April 29, 2017 03:37
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