|
| 1 | +""" |
| 2 | +.. _editing: |
| 3 | +
|
| 4 | +Editing NWB files |
| 5 | +================= |
| 6 | +
|
| 7 | +This tutorial demonstrates how to edit NWB files in-place to make small changes to |
| 8 | +existing containers. To add or remove containers from an NWB file, see |
| 9 | +:ref:`modifying_data`. How and whether it is possible to edit an NWB file depends on the |
| 10 | +storage backend and the type of edit. |
| 11 | +
|
| 12 | +.. warning:: |
| 13 | +
|
| 14 | + Manually editing an existing NWB file can make the file invalid if you are not |
| 15 | + careful. We highly recommend making a copy before editing and running a validation |
| 16 | + check on the file after editing it. See :ref:`validating`. |
| 17 | +
|
| 18 | +
|
| 19 | +Editing datasets |
| 20 | +---------------- |
| 21 | +When reading an HDF5 NWB file, PyNWB exposes :py:class:`h5py.Dataset` objects, which can |
| 22 | +be edited in place. For this to work, you must open the file in read/write mode |
| 23 | +(``"r+"`` or ``"a"``). |
| 24 | +
|
| 25 | +First, let's create an NWB file with data: |
| 26 | +""" |
| 27 | +from pynwb import NWBHDF5IO, NWBFile, TimeSeries |
| 28 | +from datetime import datetime |
| 29 | +from dateutil.tz import tzlocal |
| 30 | +import numpy as np |
| 31 | + |
| 32 | +nwbfile = NWBFile( |
| 33 | + session_description="my first synthetic recording", |
| 34 | + identifier="EXAMPLE_ID", |
| 35 | + session_start_time=datetime.now(tzlocal()), |
| 36 | + session_id="LONELYMTN", |
| 37 | +) |
| 38 | + |
| 39 | +nwbfile.add_acquisition( |
| 40 | + TimeSeries( |
| 41 | + name="synthetic_timeseries", |
| 42 | + description="Random values", |
| 43 | + data=np.random.randn(100, 100), |
| 44 | + unit="m", |
| 45 | + rate=10e3, |
| 46 | + ) |
| 47 | +) |
| 48 | + |
| 49 | +with NWBHDF5IO("test_edit.nwb", "w") as io: |
| 50 | + io.write(nwbfile) |
| 51 | + |
| 52 | +############################################## |
| 53 | +# Now, let's edit the values of the dataset |
| 54 | + |
| 55 | +with NWBHDF5IO("test_edit.nwb", "r+") as io: |
| 56 | + nwbfile = io.read() |
| 57 | + nwbfile.acquisition["synthetic_timeseries"].data[:10] = 0.0 |
| 58 | + |
| 59 | + |
| 60 | +############################################## |
| 61 | +# You can edit the attributes of that dataset through the ``attrs`` attribute: |
| 62 | + |
| 63 | +with NWBHDF5IO("test_edit.nwb", "r+") as io: |
| 64 | + nwbfile = io.read() |
| 65 | + nwbfile.acquisition["synthetic_timeseries"].data.attrs["unit"] = "volts" |
| 66 | + |
| 67 | +############################################## |
| 68 | +# Changing the shape of dataset |
| 69 | +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 70 | +# Whether it is possible to change the shape of a dataset depends on how the dataset was |
| 71 | +# created. If the dataset was created with a flexible shape, then it is possible to |
| 72 | +# change in-place. Creating a dataset with a flexible shape is done by specifying the |
| 73 | +# ``maxshape`` argument of the :py:class:`~hdmf.backends.hdf5.h5_utils.H5DataIO` class |
| 74 | +# constructor. Using a ``None`` value for a component of the ``maxshape`` tuple allows |
| 75 | +# the size of the corresponding dimension to grow, such that is can be be reset arbitrarily long |
| 76 | +# in that dimension. Chunking is required for datasets with flexible shapes. Setting ``maxshape``, |
| 77 | +# hence, automatically sets chunking to ``True``, if not specified. |
| 78 | +# |
| 79 | +# First, let's create an NWB file with a dataset with a flexible shape: |
| 80 | + |
| 81 | +from hdmf.backends.hdf5.h5_utils import H5DataIO |
| 82 | + |
| 83 | +nwbfile = NWBFile( |
| 84 | + session_description="my first synthetic recording", |
| 85 | + identifier="EXAMPLE_ID", |
| 86 | + session_start_time=datetime.now(tzlocal()), |
| 87 | + session_id="LONELYMTN", |
| 88 | +) |
| 89 | + |
| 90 | +data_io = H5DataIO(data=np.random.randn(100, 100), maxshape=(None, 100)) |
| 91 | + |
| 92 | +nwbfile.add_acquisition( |
| 93 | + TimeSeries( |
| 94 | + name="synthetic_timeseries", |
| 95 | + description="Random values", |
| 96 | + data=data_io, |
| 97 | + unit="m", |
| 98 | + rate=10e3, |
| 99 | + ) |
| 100 | +) |
| 101 | + |
| 102 | +with NWBHDF5IO("test_edit2.nwb", "w") as io: |
| 103 | + io.write(nwbfile) |
| 104 | + |
| 105 | +############################################## |
| 106 | +# The ``None``value in the first component of ``maxshape`` means that the |
| 107 | +# the first dimension of the dataset is unlimited. By setting the second dimension |
| 108 | +# of ``maxshape`` to ``100``, that dimension is fixed to be no larger than ``100``. |
| 109 | +# If you do not specify a``maxshape``, then the shape of the dataset will be fixed |
| 110 | +# to the shape that the dataset was created with. Here, you can change the shape of |
| 111 | +# the first dimension of this dataset. |
| 112 | + |
| 113 | + |
| 114 | +with NWBHDF5IO("test_edit2.nwb", "r+") as io: |
| 115 | + nwbfile = io.read() |
| 116 | + nwbfile.acquisition["synthetic_timeseries"].data.resize((200, 100)) |
| 117 | + |
| 118 | +############################################## |
| 119 | +# This will change the shape of the dataset in-place. If you try to change the shape of |
| 120 | +# a dataset with a fixed shape, you will get an error. |
| 121 | +# |
| 122 | +# .. note:: |
| 123 | +# There are several types of dataset edits that cannot be done in-place: changing the |
| 124 | +# shape of a dataset with a fixed shape, or changing the datatype, compression, |
| 125 | +# chunking, max-shape, or fill-value of a dataset. For any of these, we recommend using |
| 126 | +# the :py:class:`pynwb.NWBHDF5IO.export` method to export the data to a new file. See |
| 127 | +# :ref:`modifying_data` for more information. |
| 128 | +# |
| 129 | +# Editing groups |
| 130 | +# -------------- |
| 131 | +# Editing of groups is not yet supported in PyNWB. |
| 132 | +# To edit the attributes of a group, open the file and edit it using :py:mod:`h5py`: |
| 133 | + |
| 134 | +import h5py |
| 135 | + |
| 136 | +with h5py.File("test_edit.nwb", "r+") as f: |
| 137 | + f["acquisition"]["synthetic_timeseries"].attrs["description"] = "Random values in volts" |
| 138 | + |
| 139 | +############################################## |
| 140 | +# .. warning:: |
| 141 | +# Be careful not to edit values that will bring the file out of compliance with the |
| 142 | +# NWB specification. |
| 143 | +# |
| 144 | +# Renaming groups and datasets |
| 145 | +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 146 | +# Rename groups and datasets in-place using the :py:meth:`~h5py.Group.move` method. For example, to rename |
| 147 | +# the ``"synthetic_timeseries"`` group: |
| 148 | + |
| 149 | +with h5py.File("test_edit.nwb", "r+") as f: |
| 150 | + f["acquisition"].move("synthetic_timeseries", "synthetic_timeseries_renamed") |
| 151 | + |
| 152 | +############################################## |
| 153 | +# You can use this same technique to move a group or dataset to a different location in |
| 154 | +# the file. For example, to move the ``"synthetic_timeseries_renamed"`` group to the |
| 155 | +# ``"analysis"`` group: |
| 156 | + |
| 157 | +with h5py.File("test_edit.nwb", "r+") as f: |
| 158 | + f["acquisition"].move( |
| 159 | + "synthetic_timeseries_renamed", |
| 160 | + "/analysis/synthetic_timeseries_renamed", |
| 161 | + ) |
0 commit comments