Skip to content

Clarify intended behavior of ImageSeries.num_samples for internal (non-external) data #2215

Description

@ehennestad

Clarification request: ImageSeries.num_samples is persisted for internal (non-external) data

I'd like to understand the intended behavior of num_samples on ImageSeries, because the inherited in-memory TimeSeries.num_samples property interacts with the schema-defined num_samples dataset (added in #2194) in a way that seems inconsistent with the field's documented meaning. I'm raising this as a question rather than a bug report — I may be misreading the intent.

Background

  • TimeSeries.num_samples is a computed property that returns len(data) (falling back to len(timestamps)).

  • ImageSeries overrides this property, and Add num_samples to ImageSeries #2194 also added num_samples as an actual schema dataset (nwb.image.yaml), documented as:

    Total number of frames across all external files. This is required when format='external' and timing is described using starting_time and rate, since data is empty and its first dimension cannot be used to determine the number of frames. When timestamps is provided, len(timestamps) already serves this purpose.

The doc frames num_samples as an external-file concept: a way to recover a frame count that cannot otherwise be derived. The _check_num_samples warning is likewise gated on external_file is not None and rate is not None and num_samples is None.

Observed behavior

For an internal ImageSeries (data stored in the file, no external file), the user never sets num_samples, but:

  1. The overridden property falls through to TimeSeries.num_samples and returns len(data).
  2. There is no custom IO mapper, so the default ObjectMapper persists whatever the property returns — so num_samples gets written to disk even though data.shape[0] already carries that information.
import numpy as np
from pynwb.image import ImageSeries

img = ImageSeries(name="img", data=np.zeros((10, 5, 5)), unit="n.a.", rate=1.0)
print(img._num_samples)  # None  -- user never set it
print(img.num_samples)   # 10    -- inherited TimeSeries property returns len(data)
# on write -> acquisition/img/num_samples == 10 is persisted to the file

So the persisted num_samples value on an internal ImageSeries doesn't correspond to the documented meaning of the field ("total number of frames across all external files") — there are no external files. The value is numerically correct, but it's a redundant write of a field the schema doc describes as external-only.

Questions

  1. Is persisting num_samples for internal (non-external) ImageSeries intended, or should the dataset only be written in the external-file case it was designed for?
  2. If it's not intended, would the preferred fix be to decouple the in-memory convenience property from persistence (e.g. only write the dataset when external_file is set / data is empty), so that ImageSeries.num_samples can still return the frame count in Python without writing a redundant/misapplied dataset?
  3. Should the schema doc be broadened to also describe the internal-data meaning, if persisting it there is in fact intended?

Environment

  • pynwb 4.0.0
  • hdmf 6.1.0
  • Python 3.11.7

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    category: questionquestions about code or code behaviorpriority: mediumnon-critical problem and/or affecting only a small set of NWB users

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions