codecov[bot]: # [Codecov](https://codecov.io/gh/NeurodataWithoutBorders/pynwb/pull/596?src=pr&el=h1) Report
> Merging [#596](https://codecov.io/gh/NeurodataWithoutBorders/pynwb/pull/596?src=pr&el=desc) into [dev](https://codecov.io/gh/NeurodataWithoutBorders/pynwb/commit/96b7f081534f26ca1a8a3f3080a6faae914fe5f3?src=pr&el=desc) will **decrease** coverage by `0.02%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/NeurodataWithoutBorders/pynwb/pull/596/graphs/tree.svg?width=650&token=2HFN1QHI9R&height=150&src=pr)](https://codecov.io/gh/NeurodataWithoutBorders/pynwb/pull/596?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##              dev    #596      +/-   ##
=========================================
- Coverage   76.83%   76.8%   -0.03%     
=========================================
  Files          59      59              
  Lines        6354    6356       +2     
  Branches     1257    1258       +1     
=========================================
  Hits         4882    4882              
- Misses       1138    1139       +1     
- Partials      334     335       +1
```


| [Impacted Files](https://codecov.io/gh/NeurodataWithoutBorders/pynwb/pull/596?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/pynwb/image.py](https://codecov.io/gh/NeurodataWithoutBorders/pynwb/pull/596/diff?src=pr&el=tree#diff-c3JjL3B5bndiL2ltYWdlLnB5) | `100% <100%> (ø)` | :arrow_up: |
| [src/pynwb/form/utils.py](https://codecov.io/gh/NeurodataWithoutBorders/pynwb/pull/596/diff?src=pr&el=tree#diff-c3JjL3B5bndiL2Zvcm0vdXRpbHMucHk=) | `88.07% <0%> (-0.71%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/NeurodataWithoutBorders/pynwb/pull/596?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/NeurodataWithoutBorders/pynwb/pull/596?src=pr&el=footer). Last update [96b7f08...3b90f28](https://codecov.io/gh/NeurodataWithoutBorders/pynwb/pull/596?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

bendichter: These commits are a mess due to merging with the YAML reorder, but the diff is right: https://github.com/NeurodataWithoutBorders/pynwb/compare/dev...bendichter:two_photon_warning
diff --git a/src/pynwb/data/nwb.image.yaml b/src/pynwb/data/nwb.image.yaml
index ba78d096..964b12bf 100644
--- a/src/pynwb/data/nwb.image.yaml
+++ b/src/pynwb/data/nwb.image.yaml
@@ -19,6 +19,7 @@ groups:
   - name: data
     dtype: int8
     doc: Either binary data containing image or empty.
+    quantity: '?'
     dims:
     - - x
       - y
diff --git a/src/pynwb/image.py b/src/pynwb/image.py
index f79248ad..1c1fcc0a 100644
--- a/src/pynwb/image.py
+++ b/src/pynwb/image.py
@@ -68,6 +68,8 @@ class ImageSeries(TimeSeries):
         bits_per_pixel, dimension, external_file, starting_frame, format = popargs(
             'bits_per_pixel', 'dimension', 'external_file', 'starting_frame', 'format', kwargs)
         call_docval_func(super(ImageSeries, self).__init__, kwargs)
+        if external_file is None and self.data is None:
+            raise ValueError('must supply either external_file or data to ' + self.name)
         self.bits_per_pixel = bits_per_pixel
         self.dimension = dimension
         self.external_file = external_file

TwoPhotonSeries data warning
TwoPhotonSeries should not throw warning`UserWarning: missing required attribute 'data' for 'object_name' warnings.warn("missing required attribute '%s' for '%s'" % (spec.name, builder.name))` when `format='external'`
diff --git a/tests/unit/pynwb_tests/test_ophys.py b/tests/unit/pynwb_tests/test_ophys.py
index 6c25c1c3..14081672 100644
--- a/tests/unit/pynwb_tests/test_ophys.py
+++ b/tests/unit/pynwb_tests/test_ophys.py
@@ -49,7 +49,7 @@ class TwoPhotonSeriesConstructor(unittest.TestCase):
         self.assertEqual(ip.unit, 'unit')
         self.assertEqual(ip.reference_frame, 'reference_frame')
 
-        tPS = TwoPhotonSeries('test_tPS', 'a hypothetical source', data=list(), unit='unit', field_of_view=list(),
+        tPS = TwoPhotonSeries('test_tPS', 'a hypothetical source', unit='unit', field_of_view=list(),
                               imaging_plane=ip, pmt_gain=1.0, scan_line_rate=2.0, external_file=['external_file'],
                               starting_frame=[1, 2, 3], format='tiff', timestamps=list())
         self.assertEqual(tPS.name, 'test_tPS')
@@ -64,6 +64,16 @@ class TwoPhotonSeriesConstructor(unittest.TestCase):
         self.assertEqual(tPS.format, 'tiff')
         self.assertEqual(tPS.dimension, [np.nan])
 
+    def test_args(self):
+        oc = OpticalChannel('test_name', 'test_source', 'description', 500.)
+        device = Device(name='device_name', source='device_source')
+        ip = ImagingPlane('test_imaging_plane', 'test source', oc, 'description', device, 600.,
+                          'imaging_rate', 'indicator', 'location', (50, 100, 3), 4.0, 'unit', 'reference_frame')
+        with self.assertRaises(ValueError):  # no data or external file
+            TwoPhotonSeries('test_tPS', 'a hypothetical source', unit='unit', field_of_view=list(),
+                            imaging_plane=ip, pmt_gain=1.0, scan_line_rate=2.0,
+                            starting_frame=[1, 2, 3], format='tiff', timestamps=list())
+
 
 class MotionCorrectionConstructor(unittest.TestCase):
     def test_init(self):

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
apt-get update apt-get install -y gcc
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2018.1.18
chardet==3.0.4
coverage==6.2
execnet==1.9.0
h5py==2.7.1
idna==2.6
importlib-metadata==4.8.3
iniconfig==1.1.1
numpy==1.14.2
packaging==21.3
pandas==0.19.2
pluggy==1.0.0
py==1.11.0
-e git+https://github.com/NeurodataWithoutBorders/pynwb.git@c27448d25b080054028c330533f52ed14c4375b8#egg=pynwb
pyparsing==3.1.4
pytest==7.0.1
pytest-asyncio==0.16.0
pytest-cov==4.0.0
pytest-mock==3.6.1
pytest-xdist==3.0.2
python-dateutil==2.7.2
pytz==2025.2
requests==2.18.4
ruamel.yaml==0.15.37
six==1.11.0
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.22
zipp==3.6.0

name: pynwb
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - certifi==2018.1.18
      - chardet==3.0.4
      - coverage==6.2
      - execnet==1.9.0
      - h5py==2.7.1
      - idna==2.6
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - numpy==1.14.2
      - packaging==21.3
      - pandas==0.19.2
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-asyncio==0.16.0
      - pytest-cov==4.0.0
      - pytest-mock==3.6.1
      - pytest-xdist==3.0.2
      - python-dateutil==2.7.2
      - pytz==2025.2
      - requests==2.18.4
      - ruamel-yaml==0.15.37
      - six==1.11.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.22
      - zipp==3.6.0
prefix: /opt/conda/envs/pynwb

tests/unit/pynwb_tests/test_ophys.py::TwoPhotonSeriesConstructor::test_args
tests/unit/pynwb_tests/test_ophys.py::TwoPhotonSeriesConstructor::test_init tests/unit/pynwb_tests/test_ophys.py::MotionCorrectionConstructor::test_init tests/unit/pynwb_tests/test_ophys.py::CorrectedImageStackConstructor::test_init tests/unit/pynwb_tests/test_ophys.py::RoiResponseSeriesConstructor::test_init tests/unit/pynwb_tests/test_ophys.py::DfOverFConstructor::test_init tests/unit/pynwb_tests/test_ophys.py::FluorescenceConstructor::test_init tests/unit/pynwb_tests/test_ophys.py::ImageSegmentationConstructor::test_init tests/unit/pynwb_tests/test_ophys.py::PlaneSegmentationConstructor::test_init
src/pynwb/data/nwb.image.yaml src/pynwb/image.py
src/pynwb/data/nwb.image.yaml src/pynwb/image.py