-
Notifications
You must be signed in to change notification settings - Fork 0
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
New pixel time offsets and cycle times #58
Conversation
7a9aae4
to
4d23769
Compare
this is a move towards allowing to account for different ways of storing/calculating cycle time and pixel time offsets from LabView version to LabView version.
4d23769
to
0f47464
Compare
flake8 didn't like that empty line
assertions and assigning of pixel time offsets still needs to be adapted to newer 231 version. also, pandas reading the roi data as uint16 causes rounding errors, which mess with using z_start as key for the ROI plane
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.
Some small comments. I haven't looked at the code for the computation in the newer version yet but it's looking good so far!
src/silverlabnwb/nwb_file.py
Outdated
timings = LabViewTimingsPre2018(file_path) | ||
self.cycle_relative_times = timings.pixel_time_offsets | ||
self.cycle_time = timings.cycle_time | ||
|
||
def read_cycle_relative_times_v231(self, file_path, roi_path): | ||
timings = LabViewTimings231(file_path, | ||
roi_path=roi_path, | ||
n_cycles_per_trial=self.imaging_info.cycles_per_trial, | ||
n_trials=len(self.trial_times)) | ||
self.cycle_relative_times = timings.pixel_time_offsets | ||
self.cycle_time = timings.cycle_time |
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.
Since these are quite similar, it may be better to have a single method read_cycle_relative_times
which takes a path rather than a file name:
def read_cycle_relative_times(self, path):
file_path = ...
if version is old:
timings = LabViewTimingsPre2018(file_path)
else if version is new:
roi_path = ...
timings = LabViewTimings231(file_path, roi_path)
self.cycle_relative_times = ...
Or we could even have the Timings classes take a path in their constructor, but that makes them less general (and makes testing harder).
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.
We should also update the docstring to replace the specific filename with something more descriptive that applies to both versions.
src/silverlabnwb/timings.py
Outdated
class LabViewTimingsPre2018(LabViewTimings): | ||
|
||
def __init__(self, relative_times_path): | ||
self._read_relative_times_file(relative_times_path) |
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.
This (same for the other subclass) should call the base LabViewTimings
constructor using super()
, to ensure the object is properly initialised. In this case it doesn't really matter because the base class constructor only initialises some attributes which will be overwritten later!
In fact, since they both call _read_relative_times_file
as the first step, that should maybe go into the base class __init__
that both subclasses call - unless we think that other subclasses would not.
src/silverlabnwb/timings.py
Outdated
|
||
def _read_relative_times_file(self, file_path): | ||
raw_data = pd.read_csv(file_path, names=('RelativeTime', 'CycleTime'), | ||
sep='\t', dtype=np.float64) / 1e6 |
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.
Maybe a note here that we're converting to seconds for consistency? It's in the docstring of the NWBFile
's method but worth moving/adding here too, I think.
src/silverlabnwb/timings.py
Outdated
# in 3D, in which case the ==0 assertion may become imprecise and we would need a tolerance to compare with a | ||
# double value. |
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.
Good to have the future changes in mind! We could use math.isclose
or np.isclose
for checking against 0 in that case.
tests/test_timings.py
Outdated
# the synthetic file has | ||
# 2 trials, each of which has 3 cycles and 4 rois a 5 lines | ||
# and a few zero lines as may be expected "in the wild" | ||
# first cycle ofs trial 1 and trial 2 take 1300.4 and 1200.4 nanoseconds, respectively |
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.
# first cycle ofs trial 1 and trial 2 take 1300.4 and 1200.4 nanoseconds, respectively | |
# first cycle of trial 1 and trial 2 take 1300.4 and 1200.4 nanoseconds, respectively |
add data structure that stores pixel time offsets by ROI to timings class write pixel time offsets for first cycle in new labview
remove old datastructure completely, rename some indices
addresses initial review comments - hide versioning inside read_cycle_relative_times - call timings base class constructor - add comment - typo
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.
Looking good. What's left is recording the times for the other cycles in the NWBFile
?
It may make sense to move the computation out of add_rois
and into the LabViewTimings
subclasses. I was thinking they would have a get_roi_offsets(roi_number)
method or something similar that the NwbFile
would call. Then all the row calculations etc could happen inside the new class, allowing for less branching in the main file. That should also let us write tests more easily (e.g. get all the times for a ROI).
Or would the timings objects be missing something they need to compute the offsets themselves? I suppose they need the dwell time... we could pass that in, or could return the offset of each row in the ROI rather than the whole rectangle.
src/silverlabnwb/nwb_file.py
Outdated
@@ -591,24 +586,34 @@ def add_stimulus(self): | |||
self.nwb_file.add_stimulus(TimeSeries(**attrs)) | |||
self._write() | |||
|
|||
def read_cycle_relative_times_pre2018(self, file_path): | |||
def read_cycle_relative_times(self, folder_path): | |||
"""Read the 'Single cycle relative times.txt' file and store the values in memory. |
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.
We should probably update this to be more generic. Something like: (roughly)
"""Read the files containing relative times and store the values in memory.
Depending on the version of software used, the file is named either (...) or (...), and in newer versions this can also require reading the ROI.dat file.
Note that while the file has...
"""
src/silverlabnwb/nwb_file.py
Outdated
self.cycle_relative_times = timings.pixel_time_offsets | ||
self.cycle_time = timings.cycle_time |
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.
These can go outside the if
since they're shared in both branches.
(I now see that one of the fields is named differently (pixel_time_offsets_*by_roi*
), but maybe that's by accident?)
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.
yea, you're right - that is a mess-up by yours truly :)
pixel_time_offsets=pixel_time_offsets, | ||
dimensions=dimensions, |
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.
This confused me, but it's just changing the order of the arguments, right? Nothing else is going on?
pixel_time_offsets will be moved from segmentation to roi data this allows more flexibility to vary the dimensions and the shape of the pixel_time_offsets between ROIs
src/silverlabnwb/nwb_file.py
Outdated
all_attrs = dict(ts_attrs) | ||
all_attrs.update(data_attrs) | ||
roi_with_offsets = ROISeriesWithPixelTimeOffsets(name=ts_name, | ||
data=data, | ||
pixel_time_offsets=pixel_time_offsets, | ||
rate=1.0/self.cycle_time, | ||
**all_attrs) | ||
self.nwb_file.add_acquisition(roi_with_offsets) |
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.
This can possibly use self._add_time_series_data(kind=ROISeriesWithPixelTimeOffsets, ...)
?
'relative to the starting time/timestamp of a ' | ||
'ROISeriesWithPixelTimeOffsets.', |
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.
Is it relative to the starting time (= time of the cycle?) or to the start of the trial?
moved within-line pixel time offset computation inside timings class making the interface more unified among subclasses
consistent API across versions unifies handling of pixel_time_offsets adapt roi extension to shape of pre2018 versions pixel_time_offsets completely removed from ImageSegmentation in all versions to form part of ROI image data.
Before this, there was an issue when planes had more than one ROI. This fixes it for now but we should find a better solution.
The newest release (2.0.0) adds some attributes and allows slightly different types, resulting in changes to the signatures. pynwb is still using the older versions too, even in the most recent release (1.3.3).
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.
This looks basically done, but with some (generally small) comments below.
Some improvements can wait for another PR. I'll leave a comment about that.
'relative to the starting time/timestamp of a ' | ||
'ROISeriesWithPixelTimeOffsets.', |
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.
This should be changed to "starting time of a trial" (can run a quick check to confirm first).
name='pixel_time_offsets', | ||
shape=[(None, None), (None, None, None), (None, None, None, None)], | ||
neurodata_type_def='PixelTimeOffsets') | ||
silverlab_roi_image_specs = NWBGroupSpec(doc='documentation of this class goes here', |
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.
We should put something there :)
src/silverlabnwb/nwb_file.py
Outdated
@@ -583,15 +586,37 @@ def add_stimulus(self): | |||
self.nwb_file.add_stimulus(TimeSeries(**attrs)) | |||
self._write() | |||
|
|||
def read_cycle_relative_times(self, file_path): | |||
def read_cycle_relative_times(self, folder_path): | |||
"""Read the 'Single cycle relative times.txt' file and store the values in memory. |
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.
See the previous comment about this.
src/silverlabnwb/nwb_file.py
Outdated
self.raw_pixel_time_offsets = timings.pixel_time_offsets | ||
self.cycle_time = timings.cycle_time |
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.
These lines should go outside the if
as they're shared.
else: | ||
raise ValueError('Unsupported LabView version for timings {}.'.format(self.labview_version)) |
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.
This is fine for now, but if you want to avoid it, we can add a method to the (abstract) Timings
class which creates an object of the appropriate type based on the version we pass it (similar to how the abstract Header
class works, if I remember right). If we pass the wrong version (or the wrong arguments for that version), we can throw the error there.
This can wait until later, however.
tests/data/synthetic v231 ROI.dat
Outdated
1 3 133200 888 70 100 0 76 105 0 0 0 5 512 1 45.1 0 | ||
2 3 133200 888 80 200 1 86 205 1 0 0 5 512 1 45.2 0 | ||
3 3 133200 888 90 300 2 96 305 2 0 0 5 512 1 45.3 0 | ||
4 3 133200 888 100 400 3 106 405 3 0 0 5 512 1 45.4 0 |
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.
Same comment as in the other test file :)
@@ -0,0 +1,8 @@ | |||
400.0 12345.000 |
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.
Hm, does this work even without the header line? Interesting!
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.
Yes, none of the pre2018
data that I have seen have headers here. The column designation is inferred from the order and added in the code.
tests/test_timings.py
Outdated
|
||
|
||
def test_pixel_time_offsets_for_roi_v231(synthetic_timings_v231): | ||
roi_0_offsets = synthetic_timings_v231.pixel_time_offsets[0] |
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.
We should check the other ROI too; this is the recent error that we had to work around in 661aca6.
tests/test_timings.py
Outdated
assert roi_0_offsets.shape == expected_shape | ||
assert roi_0_offsets[0][0][0] == expected_first_cycle_first_row_offset | ||
assert roi_0_offsets[0][4][0] == expected_first_cycle_last_row_offset | ||
assert roi_0_offsets[5][0][0] == expected_last_cycle_first_row_offset |
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.
We're already checking the size earlier, but if we want to be clearer that we're checking the last cycle, I'd put roi_0_offsets[-1][0][0]
.
tests/test_timings.py
Outdated
|
||
|
||
def test_pixel_time_offsets_for_roi_pre2018(synthetic_timings_pre2018): | ||
roi_0_offsets = synthetic_timings_pre2018.pixel_time_offsets[0] |
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.
As in the other test, we should check the other ROIs too. Perhaps the first and last line of each?
Once the above changes are made, it would be good to compare with the existing analysis code if possible. There are other possible improvements but better left for another round, since this is already quite a lot! For example:
|
improve docstring move common code outside if-else clauses consistent passing of arguments
add docstring to abstract timings class make comment more precise clarify confusing variable name remove unnecessary comment (see #60)
required because of modified docstrings in silverlab specs
Also includes changes due to #58 (moving pixel time offsets). Otherwise the only changes are to the spec and the new LabVIEW version, plus the datatype of the BodyCam image series.
This PR will handle differences between LabView versions in storing/computing pixel time offsets and cycle time.