[Documentation]: Clarification on attribute constant `value`
What would you like changed or added to the documentation and why?
I hope to have some clarification on the usage of value in NWBAttributeSpec. In the schema, it says that:
Optional key specifying a fixed, constant value for the attribute. Default value is None, i.e., the attribute has a variable value to be determined by the user (or API) in accordance with the current data.
So if I create an extension to have an object that has an attribute defined as, for example:
NWBAttributeSpec(name='my_pi_const', doc='my constant pi value', dtype='float', value=3.14) ...
then when I load the extension in, is the object.my_pi_const going to be None and not 3.14?
If I were to write it to a file, then object.my_pi_const is set to 3.14, overwriting the default None and/or any values set by the user when creating the object. Is that correct?
It seems confusing to me that this supposedly-constant field in the extension schema is determined by user/API when creating, but then can get overwritten at the write stage.
I hope to get some clarification on when to use value and whether there's a way to set a constant value attribute. Is default_value a better alternative?
Thanks!
Do you have any interest in helping write or edit the documentation?
No.
Code of Conduct
- [X] I agree to follow this project's Code of Conduct
- [X] Have you checked the Contributing document?
- [X] Have you ensured this change was not already requested?
then when I load the extension in, is the
object.my_pi_constgoing to beNoneand not3.14?
It means that if value is not specified then the default value is None, which means that the value for the attribute must be defined by the user. When you set value=3.14 it indicates that the value for the Attribute is fixed to that value and cannot be changed by the user. If you want to provide a default value for the attribute but still allow the user to modify the value of the attribute then you would use the key default_value instead.
If I were to write it to a file, then
object.my_pi_constis set to3.14, overwriting the defaultNoneand/or any values set by the user when creating the object. Is that correct?
I think this may be misunderstanding. The doc you are referencing, describes the behavior of the key value of the attribute specification. By default, the key value of the specification is set to None (if it is not provided)to indicate that the value of the Attribute when created in a file can be set by the user. However, when you set the key value in the specification of the Attribute to a fixed value, then that value will always be used as the value for the attribute when writing it to disk. In your case:
NWBAttributeSpec(name='my_pi_const', doc='my constant pi value', dtype='float', value=3.14)
means that the value of the attribute object.my_pi_const is always 3.14. In contrast:
NWBAttributeSpec(name='my_pi_const', doc='my constant pi value', dtype='float')
is the same as:
NWBAttributeSpec(name='my_pi_const', doc='my constant pi value', dtype='float', value=None)
which means that there is no pre-defined value for the attribute so that the value forobject.my_pi_const is specified by the user.
I hope to get some clarification on when to use
valueand whether there's a way to set a constant value attribute. Isdefault_valuea better alternative?
Use the value key to set to a constant value and use the default_value key if you want to provide a default value but still allow a user to overwrite the value.
Thanks for your clarification. When I was trying to use value=3.14 it only sets my_pi_const to 3.14. Here's an example
- Script to create extension
from pynwb.spec import NWBNamespaceBuilder, NWBGroupSpec, NWBAttributeSpec
name = 'ndx-testpi'
ns_path = name + ".namespace.yaml"
ext_source = name + ".extensions.yaml"
ns_builder = NWBNamespaceBuilder(name + ' extensions', name, version='0.1.0')
ns_builder.include_type('NWBDataInterface', namespace='core')
my_pi = NWBGroupSpec(
neurodata_type_def='TestConstAttr',
neurodata_type_inc='NWBDataInterface',
doc='test const pi',
quantity='*',
attributes = [
NWBAttributeSpec(name='const_pi', doc='const pi', dtype='float', value=3.14),
NWBAttributeSpec(name='changeable_pi', doc='pi', dtype='float', default_value=3.14)
]
)
ns_builder.add_spec(ext_source, my_pi)
ns_builder.export(ns_path)
- Use extension in another file
from datetime import datetime
from dateutil import tz
from pynwb import NWBFile, load_namespaces, get_class,NWBHDF5IO
from hdmf.utils import get_docval
ns_path = 'ndx-testpi.namespace.yaml'
load_namespaces(ns_path)
TestConstAttr = get_class('TestConstAttr', 'ndx-testpi')
get_docval(TestConstAttr.__init__)
----------------------------------------------------------------------------------------------------
# output of `get_docval`
({'name': 'name', 'type': str, 'doc': 'the name of this container'},
{'name': 'const_pi',
'doc': 'const pi',
'type': (float, numpy.float32, numpy.float64),
'default': None}, # <------ no `value` for `const_pi`?
{'name': 'changeable_pi',
'doc': 'pi',
'type': (float, numpy.float32, numpy.float64),
'default': 3.14})
When creating the objects, const_pi is not automatically set to 3.14
nwbfile = NWBFile(session_description='abc', identifier='xyz',session_start_time= datetime(2018, 1, 2, 3, 4, 5, tzinfo=tz.gettz("US/Pacific")))
obj1 = TestConstAttr(name='obj1')
obj2 = TestConstAttr(name='obj2', const_pi=0.0, changeable_pi=-1.0)
nwbfile.add_acquisition(obj1)
nwbfile.add_acquisition(obj2)
print(nwbfile.acquisition)
with NWBHDF5IO('test.nwb', mode='w') as io:
io.write(nwbfile)
----------------------------------------------------------------------------------------------------
# output of `print`
{'obj1': obj1 abc.TestConstAttr at 0x140467055934192
Fields:
changeable_pi: 3.14 # <----- no `const_pi` for `obj1`
, 'obj2': obj2 abc.TestConstAttr at 0x140467055930928
Fields:
changeable_pi: -1.0
const_pi: 0.0 # <----- `const_pi` is changed here, but not after write->read
}
When reading the file back, const_pi is set correctly but not before.
with NWBHDF5IO('test.nwb', mode='r', load_namespaces=True) as io:
nwbfile_reload = io.read()
print(nwbfile_reload.acquisition)
----------------------------------------------------------------------------------------------------
# output of `print`
{'obj1': obj1 abc.TestConstAttr at 0x140468520980336
Fields:
changeable_pi: 3.14
const_pi: 3.14 # <---- `const_pi` set for `obj1` automatically
, 'obj2': obj2 abc.TestConstAttr at 0x140468520986288
Fields:
changeable_pi: -1.0
const_pi: 3.14 # <---- overwrite user-defined `const_pi` back to schema definition
}
Sorry, for the late reply. Thanks for the additional helpful details. The behavior for changeable_pi using the default_value appears to be correct. The behavior for const_pi appears to be correct on write (i.e., the object mappers during construction of the builders for I/O are setting the correct value for const_pi on write), however, the behavior of the auto-generated container class for attributes with a fixed value is not. I.e., ideally the value for const_pi should also be set as a fixed value also in the Container so that the correct value is visible also before write. This part is really an issue in HDMF. I created the following issue https://github.com/hdmf-dev/hdmf/issues/798 to make sure this is being addressed in HDMF.
I'm leaving this issue open for now here as well. The part that should be addressed in PyNWB is to clarify the documentation for value and default_value in the spec classed.