FilterNet: Changing the location of nondominant filter also changes the location of dominant filter in class "TwoSubfieldLinearCell" of "cellmodel.py" in bmtk
Overview
In function default_cell_loader() of "bmtk/simulator/filternet/default_setters/cell_loaders.py", variable linear_filter_son (line 121) and linear_filter_soff (line 124) take the same variable spatial_filter as input. This causes the two former variables to store the same spatial_filter object.
Then, linear_filter_son and linear_filter_soff are passed into the __init__() function of "bmtk/simulator/filternet/lgnmodel/cellmodel.py" as argument dominant_filterand nondominant_filter. However, in line 65-69 of this file:
self.dominant_filter.spatial_filter.translate = self.dominant_subfield_location
hor_offset = np.cos(self.onoff_axis_angle*np.pi/180.)*self.subfield_separation + self.dominant_subfield_location[0]
vert_offset = np.sin(self.onoff_axis_angle*np.pi/180.)*self.subfield_separation + self.dominant_subfield_location[1]
rel_translation = (hor_offset, vert_offset)
self.nondominant_filter.spatial_filter.translate = rel_translation
the intention is to set a different location for the nondominant_filter. But since self.dominant_filter.spatial_filter and self.nondominant_filter.spatial_filter is actually the same object, changing self.nondominant_filter.spatial_filter.translate will also change self.dominant_filter.spatial_filter.translate.
Therefore, when running the LGN filternet, the dominant and nondominant spatial filter will be placed at exactly the same location. This can be verified by checking the lnunit_cursor_list in class SeparableMultiLNUnitCursor of "bmtk/simulator/filternet/lgnmodel/cursor.py".
Steps to reproduce
- Download and extract the attachment (1 MB) in this issue. The files are selected from the source code of the mouse V1 model in Billeh et al., Neuron, 2020 (mostly unmodified).
- Set a breakpoint at line 69 of "bmtk/simulator/filternet/lgnmodel/cellmodel.py". This file runs when establishing the network.
- Run and debug "run_filternet.py", wait until it stops at the above breakpoint.
- Print
self.dominant_filter.spatial_filter.translate, the result should be:(8.07545259102928, 10.021859654121696) - Run line 69 of "bmtk/simulator/filternet/lgnmodel/cellmodel.py".
- Print
self.dominant_filter.spatial_filter.translateandself.nondominant_filter.spatial_filter.translate, the outputs will both be(3.6379139789913113, 6.169332828582764). THIS IS THE BUG - Print
self.dominant_filter.spatial_filter.translate is self.nondominant_filter.spatial_filter.translate, the output will beTrue. - Remove the breakpoint in line 69 of "bmtk/simulator/filternet/lgnmodel/cellmodel.py", set a new breakpoint in line 210 of "bmtk/simulator/filternet/lgnmodel/cursor.py". This file runs when actually simulating the network.
- Continue running the code until it stops at the new breakpoint (make take several minutes).
- Print
self.lnunit_cursor_list[0].spatial_filter.translate, self.lnunit_cursor_list[1].spatial_filter.translate. The output is((3.6379139789913113, 6.169332828582764), (3.6379139789913113, 6.169332828582764)). - Print
np.abs(self.lnunit_cursor_list[0].spatial_filter.get_kernel(np.arange(120), np.arange(240)).full()).argmax(), np.abs(self.lnunit_cursor_list[1].spatial_filter.get_kernel(np.arange(120), np.arange(240)).full()).argmax(). The output is(1444, 1444), which shows that the two spatial kernels peak at the same location.
Suggested fix
Use copy.deepcopy to isolate the two spatial_filter in function default_cell_loader() of "bmtk/simulator/filternet/default_setters/cell_loaders.py".
Attached code
Hi CloudyDory,
Thank you for the very detailed issue report. I reproduced the bug and we are working towards fixing it.
Best, Shinya
@shixnya Thank you for the fix! Can you also take a look at issue #337 ? The bmtk code saves the dominant_filter amplitude for the nondominant_filter, which also looks strange.