Skip to content

Commit

Permalink
Update for 0.1.0 release (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
rly authored Jan 26, 2022
1 parent 42deb4f commit fd77067
Show file tree
Hide file tree
Showing 14 changed files with 206 additions and 122 deletions.
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
# generated docs
docs/_build
docs/source/_format_auto_docs
docs/source/_static
!docs/source/_static/theme_overrides.css

# copied spec files
src/pynwb/ndx_pose/spec/*.yaml
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
BSD 3-Clause License

Copyright (c) 2021, Ryan Ly
Copyright (c) 2021-2022, Ryan Ly, Ben Dichter, Alexander Mathis
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ nwbfile = NWBFile(
session_start_time=datetime.datetime.now(datetime.timezone.utc)
)

camera1 = nwbfile.create_device(
name='camera1',
description='left camera',
manufacturer='my manufacturer'
)
camera2 = nwbfile.create_device(
name='camera2',
description='right camera',
manufacturer='my manufacturer'
)

data = np.random.rand(100, 3) # num_frames x (x, y, z)
timestamps = np.linspace(0, 10, num=100) # a timestamp for every frame
confidence = np.random.rand(100) # a confidence value for every frame
Expand Down Expand Up @@ -54,13 +65,13 @@ pe = PoseEstimation(
description='Estimated positions of front paws using DeepLabCut.',
original_videos=['camera1.mp4', 'camera2.mp4'],
labeled_videos=['camera1_labeled.mp4', 'camera2_labeled.mp4'],
dimensions=[[640, 480], [1024, 768]],
dimensions=np.array([[640, 480], [1024, 768]], dtype='uint8'),
scorer='DLC_resnet50_openfieldOct30shuffle1_1600',
source_software='DeepLabCut',
source_software_version='2.2b8',
nodes=['front_left_paw', 'front_right_paw'],
edges=[[0, 1]],
# devices=[self.nwbfile.devices['camera1'], self.nwbfile.devices['camera2']],
edges=np.array([[0, 1]], dtype='uint8'),
# devices=[camera1, camera2], # this is not yet supported
)

behavior_pm = nwbfile.create_processing_module(
Expand Down
22 changes: 15 additions & 7 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
# -- Project information -----------------------------------------------------

project = 'ndx-pose'
copyright = '2021, Ryan Ly, Ben Dichter, Alexander Mathis'
copyright = '2021-2022, Ryan Ly, Ben Dichter, Alexander Mathis'
author = 'Ryan Ly, Ben Dichter, Alexander Mathis'

# The short X.Y version
version = '0.1.0'

# The full version, including alpha/beta/rc tags
release = 'alpha'
release = '0.1.0'


# -- General configuration ---------------------------------------------------
Expand Down Expand Up @@ -68,9 +68,11 @@
html_static_path = ['_static']


# -- Extension configuration -------------------------------------------------
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
}

# -- Options for intersphinx extension ---------------------------------------
############################################################################
# CUSTOM CONFIGURATIONS ADDED BY THE NWB TOOL FOR GENERATING FORMAT DOCS
###########################################################################
Expand All @@ -79,7 +81,10 @@
import textwrap # noqa: E402

# -- Options for intersphinx ---------------------------------------------
intersphinx_mapping = {'core': ('https://nwb-schema.readthedocs.io/en/latest/', None)}
intersphinx_mapping.update({
'core': ('https://nwb-schema.readthedocs.io/en/latest/', None),
'hdmf-common': ('https://hdmf-common-schema.readthedocs.io/en/latest/', None),
})

# -- Generate sources from YAML---------------------------------------------------
# Always rebuild the source docs from YAML even if the folder with the source files already exists
Expand All @@ -102,8 +107,11 @@ def run_doc_autogen(_):

def setup(app):
app.connect('builder-inited', run_doc_autogen)
app.add_stylesheet("theme_overrides.css") # overrides for wide tables in RTD theme

# overrides for wide tables in RTD theme
try:
app.add_css_file("theme_overrides.css") # Used by newer Sphinx versions
except AttributeError:
app.add_stylesheet("theme_overrides.css") # Used by older version of Sphinx

# -- Customize sphinx settings
numfig = True
Expand Down
3 changes: 3 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# pinned dependencies to reproduce an entire development environment to run tests and check code style
flake8==4.0.1
pytest==6.2.5
6 changes: 4 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
pynwb>=1.3.0
hdmf_docutils
# pinned dependencies to reproduce a working development environment
hdmf_docutils==0.4.4
hdmf==3.1.1
pynwb==2.0.0
13 changes: 11 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@
'url': '',
'license': 'BSD 3-Clause',
'install_requires': [
'pynwb>=1.3.0'
'pynwb>=1.5.0,<3',
'hdmf>=2.5.6,<4',
'hdmf-docutils>=0.4.4,<1'
],
'packages': find_packages('src/pynwb'),
'packages': find_packages('src/pynwb', exclude=["tests", "tests.*"]),
'package_dir': {'': 'src/pynwb'},
'package_data': {'ndx_pose': [
'spec/ndx-pose.namespace.yaml',
Expand All @@ -42,6 +44,13 @@
'classifiers': [
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: BSD License"
],
'keywords': [
'NeurodataWithoutBorders',
'NWB',
'nwb-extension',
'ndx-extension'
],
'zip_safe': False
}
Expand Down
2 changes: 1 addition & 1 deletion spec/ndx-pose.extensions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ groups:
- num_body_parts
shape:
- null
doc: Array of body part names corresponding to the names of the SpatialSeries
doc: Array of body part names corresponding to the names of the PoseEstimationSeries
objects within this group.
quantity: '?'
- name: edges
Expand Down
2 changes: 0 additions & 2 deletions spec/ndx-pose.namespace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ namespaces:
- namespace: core
neurodata_types:
- SpatialSeries
- TimeSeries
- NWBDataInterface
- NWBContainer
- source: ndx-pose.extensions.yaml
version: 0.1.0
147 changes: 92 additions & 55 deletions src/pynwb/ndx_pose/pose.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,56 @@
from hdmf.utils import docval, popargs, get_docval, call_docval_func
from hdmf.utils import docval, popargs, get_docval, call_docval_func, AllowPositional

from pynwb import register_class, TimeSeries
# from pynwb.behavior import SpatialSeries
from pynwb.behavior import SpatialSeries
from pynwb.core import MultiContainerInterface
from pynwb.device import Device


@register_class('PoseEstimationSeries', 'ndx-pose')
class PoseEstimationSeries(TimeSeries):
"""
class PoseEstimationSeries(SpatialSeries):
"""Estimated position (x, y) or (x, y, z) of a body part over time.
""" # TODO

__nwbfields__ = ('reference_frame', 'confidence', 'confidence_definition')
__nwbfields__ = ('confidence', 'confidence_definition')

# custom mapper maps 'confidence' dataset > 'definition' attribute to 'confidence_definition' field here
# custom mapper in ndx_pose.io.pose maps:
# 'confidence' dataset -> 'definition' attribute to 'confidence_definition' field

# TODO fill in doc
@docval({'name': 'name', 'type': str, 'doc': ('')}, # required
{'name': 'data', 'type': ('array_data', 'data', TimeSeries), 'shape': ((None, 2), (None, 3)), # required
'doc': ('')},
{'name': 'unit', 'type': str, 'doc': ('')}, # required
{'name': 'reference_frame', 'type': str, # required
'doc': 'Description defining what the zero-position (0, 0) or (0, 0, 0) is.'},
{'name': 'confidence', 'type': ('array_data', 'data'), 'shape': (None, ), 'doc': ('')}, # required
{'name': 'confidence_definition', 'type': str, 'doc': (''), 'default': None},
*get_docval(TimeSeries.__init__, 'conversion', 'resolution', 'timestamps', 'starting_time', 'rate',
'comments', 'description', 'control', 'control_description'))
@docval(
{'name': 'name', 'type': str,
'doc': ('Name of this PoseEstimationSeries, usually the name of a body part.')}, # required
{'name': 'data', 'type': ('array_data', 'data', TimeSeries), 'shape': ((None, 2), (None, 3)), # required
'doc': ('Estimated position (x, y) or (x, y, z).')},
{'name': 'reference_frame', 'type': str, # required
'doc': 'Description defining what the zero-position (0, 0) or (0, 0, 0) is.'},
{'name': 'confidence', 'type': ('array_data', 'data'), 'shape': (None, ), # required
'doc': ('Confidence or likelihood of the estimated positions, scaled to be between 0 and 1.')},
{'name': 'unit', 'type': str,
'doc': ("Base unit of measurement for working with the data. The default value "
"is 'pixels'. Actual stored values are not necessarily stored in these units. "
"To access the data in these units, multiply 'data' by 'conversion'."),
'default': 'pixels'},
{'name': 'confidence_definition', 'type': str,
'doc': ("Description of how the confidence was computed, e.g., "
"'Softmax output of the deep neural network'."),
'default': None},
*get_docval(TimeSeries.__init__, 'conversion', 'resolution', 'timestamps', 'starting_time', 'rate',
'comments', 'description', 'control', 'control_description'),
allow_positional=AllowPositional.ERROR
)
def __init__(self, **kwargs):
"""Construct a new PoseEstimationSeries representing pose estimates for a particular body part."""
reference_frame, confidence, confidence_definition = popargs('reference_frame', 'confidence',
'confidence_definition', kwargs)
confidence, confidence_definition = popargs('confidence', 'confidence_definition', kwargs)
call_docval_func(super().__init__, kwargs)
self.reference_frame = reference_frame
self.confidence = confidence
self.confidence_definition = confidence_definition

# TODO SpatialSeries does not allow the 'unit' argument to be different from 'meters'. This needs to be updated
# for the inheritance to work correctly here. In the meantime, just inherit from TimeSeries


@register_class('PoseEstimation', 'ndx-pose')
class PoseEstimation(MultiContainerInterface):
"""Estimated position data for multiple body parts, computed from the same video with the same tool/algorithm.
The timestamps of each child PoseEstimationSeries type should be the same.
"""
""" # TODO

__clsconf__ = [
{
Expand All @@ -52,47 +60,70 @@ class PoseEstimation(MultiContainerInterface):
'type': PoseEstimationSeries,
'attr': 'pose_estimation_series'
},
{
'add': 'add_device',
'get': 'get_devices',
'type': Device,
'attr': 'devices'
# TODO prevent these from being children / add better support for links
# may require update to HDMF to add a key 'child': False
}
# {
# 'add': 'add_device',
# 'get': 'get_devices',
# 'type': Device,
# 'attr': 'devices'
# # TODO prevent these from being children / add better support for links
# # may require update to HDMF to add a key 'child': False
# }
]

__nwbfields__ = ('description', 'original_videos', 'labeled_videos', 'dimensions', 'scorer', 'source_software',
'source_software_version', 'nodes', 'edges')

# custom mapper maps 'source_software' dataset > 'version' attribute to 'source_software_version' field here
# custom mapper in ndx_pose.io.pose maps:
# 'source_software' dataset -> 'version' attribute to 'source_software_version' field

# TODO fill in doc
@docval({'name': 'pose_estimation_series', 'type': ('array_data', 'data'), 'doc': (''), 'default': None},
{'name': 'name', 'type': str, 'doc': (''), 'default': 'PoseEstimation'},
{'name': 'description', 'type': str, 'doc': (''), 'default': None},
{'name': 'original_videos', 'type': ('array_data', 'data'), 'shape': (None, ),
'doc': (''), 'default': None},
{'name': 'labeled_videos', 'type': ('array_data', 'data'), 'shape': (None, ),
'doc': (''), 'default': None},
{'name': 'dimensions', 'type': ('array_data', 'data'), 'shape': ((None, 2)),
'doc': (''), 'default': None},
{'name': 'scorer', 'type': str, 'doc': (''), 'default': None},
{'name': 'source_software', 'type': str, 'doc': (''), 'default': None},
{'name': 'source_software_version', 'type': str, 'doc': (''), 'default': None},
{'name': 'nodes', 'type': ('array_data', 'data'), 'doc': (''), 'default': None},
{'name': 'edges', 'type': ('array_data', 'data'), 'doc': (''), 'default': None},
{'name': 'devices', 'type': ('array_data', 'data'), 'doc': (''), 'default': None},
)
@docval( # all fields optional
{'name': 'pose_estimation_series', 'type': ('array_data', 'data'),
'doc': ('Estimated position data for each body part.'),
'default': None},
{'name': 'name', 'type': str,
'doc': ('Description of the pose estimation procedure and output.'),
'default': 'PoseEstimation'},
{'name': 'description', 'type': str,
'doc': ('Description of the pose estimation procedure and output.'),
'default': None},
{'name': 'original_videos', 'type': ('array_data', 'data'), 'shape': (None, ),
'doc': ('Paths to the original video files. The number of files should equal the number of camera devices.'),
'default': None},
{'name': 'labeled_videos', 'type': ('array_data', 'data'), 'shape': (None, ),
'doc': ('Paths to the labeled video files. The number of files should equal the number of camera devices.'),
'default': None},
{'name': 'dimensions', 'type': ('array_data', 'data'), 'shape': ((None, 2)),
'doc': ('Dimensions of each labeled video file.'),
'default': None},
{'name': 'scorer', 'type': str,
'doc': ('Name of the scorer / algorithm used.'),
'default': None},
{'name': 'source_software', 'type': str,
'doc': ('Name of the software tool used. Specifying the version attribute is strongly encouraged.'),
'default': None},
{'name': 'source_software_version', 'type': str,
'doc': ('Version string of the software tool used.'),
'default': None},
{'name': 'nodes', 'type': ('array_data', 'data'),
'doc': ('Array of body part names corresponding to the names of the PoseEstimationSeries objects within '
'this container.'),
'default': None},
{'name': 'edges', 'type': ('array_data', 'data'),
'doc': ("Array of pairs of indices corresponding to edges between nodes. Index values correspond to row "
"indices of the 'nodes' field. Index values use 0-indexing."),
'default': None},
# {'name': 'devices', 'type': ('array_data', 'data'),
# 'doc': ('Cameras used to record the videos.'),
# 'default': None},
allow_positional=AllowPositional.ERROR
)
def __init__(self, **kwargs):
"""
""" # TODO
pose_estimation_series, description = popargs('pose_estimation_series', 'description', kwargs)
original_videos, labeled_videos, = popargs('original_videos', 'labeled_videos', kwargs)
dimensions, scorer = popargs('dimensions', 'scorer', kwargs)
source_software, source_software_version = popargs('source_software', 'source_software_version', kwargs)
nodes, edges = popargs('nodes', 'edges', kwargs)
devices = popargs('devices', kwargs)
# devices = popargs('devices', kwargs)
call_docval_func(super().__init__, kwargs)
self.pose_estimation_series = pose_estimation_series
self.description = description
Expand All @@ -104,9 +135,15 @@ def __init__(self, **kwargs):
self.source_software_version = source_software_version
self.nodes = nodes
self.edges = edges
self.devices = devices
# self.devices = devices

# TODO include calibration images for 3D estimates?

# TODO validate lengths of original_videos, labeled_videos, dimensions, and devices
# if original_videos is not None and (devices is None or len(original_videos) != len(devices)):
# raise ValueError("The number of original videos should equal the number of camera devices.")
# if labeled_videos is not None and (devices is None or len(labeled_videos) != len(devices)):
# raise ValueError("The number of labeled videos should equal the number of camera devices.")
# if dimensions is not None and (devices is None or len(dimensions) != len(devices)):
# raise ValueError("The number of dimensions should equal the number of camera devices.")

# TODO validate nodes and edges correspondence, convert edges to uint
Loading

0 comments on commit fd77067

Please sign in to comment.