1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
|
import weakref
from collections import OrderedDict
from collections.abc import MutableMapping
import numpy as np
class Dimensions(MutableMapping):
def __init__(self, group):
self._group_ref = weakref.ref(group)
self._objects = OrderedDict()
@property
def _group(self):
return self._group_ref()
def __getitem__(self, name):
return self._objects[name]
def __setitem__(self, name, size):
# creating new dimensions
if not self._group._root._writable:
raise RuntimeError("H5NetCDF: Write to read only")
if name in self._objects:
raise ValueError(f"dimension {name!r} already exists")
self._objects[name] = Dimension(self._group, name, size, create_h5ds=True)
def add_phony(self, name, size):
self._objects[name] = Dimension(
self._group, name, size, create_h5ds=False, phony=True
)
def add(self, name):
# adding dimensions which are already created in the file
self._objects[name] = Dimension(self._group, name)
def __delitem__(self, key):
raise NotImplementedError("cannot yet delete dimensions")
def __iter__(self):
yield from self._objects
def __len__(self):
return len(self._objects)
def __repr__(self):
if self._group._root._closed:
return "<Closed h5netcdf.Dimensions>"
dims = ", ".join(f"{k}={v!r}" for k, v in self._objects.items())
return f"<h5netcdf.Dimensions: {dims}>"
def _join_h5paths(parent_path, child_path):
return "/".join([parent_path.rstrip("/"), child_path.lstrip("/")])
class Dimension:
def __init__(self, parent, name, size=None, create_h5ds=False, phony=False):
"""NetCDF4 Dimension constructor.
Parameters
----------
parent: h5netcdf.Group
Parent group.
name: str
Name of the dimension.
size : int
Size of the Netcdf4 Dimension. Defaults to None (unlimited).
create_h5ds : bool
For internal use only.
phony : bool
For internal use only.
"""
self._parent_ref = weakref.ref(parent)
self._phony = phony
self._root_ref = weakref.ref(parent._root)
self._h5path = _join_h5paths(parent.name, name)
self._name = name
self._size = 0 if size is None else size
if self._phony:
self._root._phony_dim_count += 1
else:
self._root._max_dim_id += 1
self._dimensionid = self._root._max_dim_id
if parent._root._writable and create_h5ds and not self._phony:
self._create_scale()
self._initialized = True
@property
def _root(self):
return self._root_ref()
@property
def _parent(self):
return self._parent_ref()
@property
def name(self):
"""Return dimension name."""
if self._phony:
return self._name
return self._h5ds.name.split("/")[-1]
@property
def size(self):
"""Return dimension size."""
size = len(self)
if self.isunlimited():
# return actual dimensions sizes, this is in line with netcdf4-python
# get sizes from all connected variables and calculate max
# because netcdf unlimited dimensions can be any length
# but connected variables dimensions can have a certain larger length.
reflist = self._h5ds.attrs.get("REFERENCE_LIST", None)
if reflist is not None:
for ref, axis in reflist:
var = self._parent._h5group["/"][ref]
size = max(var.shape[axis], size)
return size
def group(self):
"""Return parent group."""
return self._parent
def isunlimited(self):
"""Return ``True`` if dimension is unlimited, otherwise ``False``."""
if self._phony:
return False
return self._h5ds.maxshape == (None,)
@property
def _h5ds(self):
if self._phony:
return None
return self._root._h5file[self._h5path]
@property
def _isscale(self):
return self._root._h5py.h5ds.is_scale(self._h5ds.id)
@property
def _dimid(self):
if self._phony:
return False
return self._h5ds.attrs.get("_Netcdf4Dimid", self._dimensionid)
def _resize(self, size):
from .legacyapi import Dataset
if not self.isunlimited():
raise ValueError(
f"Dimension '{self.name}' is not unlimited and thus cannot be resized."
)
self._h5ds.resize((size,))
# resize all referenced datasets for new API
if not isinstance(self._root, Dataset):
refs = self._scale_refs
if refs:
for var, dim in refs:
self._parent._all_h5groups[var].resize(size, dim)
@property
def _scale_refs(self):
"""Return dimension scale references"""
return list(self._h5ds.attrs.get("REFERENCE_LIST", []))
def _create_scale(self):
"""Create dimension scale for this dimension"""
if self._name not in self._parent._h5group:
kwargs = {}
if self._size is None or self._size == 0:
kwargs["maxshape"] = (None,)
if self._root._h5py.__name__ == "h5py":
kwargs.update(dict(track_order=self._parent._track_order))
self._parent._h5group.create_dataset(
name=self._name,
shape=(self._size,),
dtype=">f4",
**kwargs,
)
self._h5ds.attrs["_Netcdf4Dimid"] = np.array(self._dimid, dtype=np.int32)
if len(self._h5ds.shape) > 1:
dims = self._parent._variables[self._name].dimensions
coord_ids = np.array(
[self._parent._dimensions[d]._dimid for d in dims], "int32"
)
self._h5ds.attrs["_Netcdf4Coordinates"] = coord_ids
# need special handling for size in case of scalar and tuple
size = self._size
if not size:
size = 1
if isinstance(size, tuple):
size = size[0]
dimlen = bytes(f"{size:10}", "ascii")
NOT_A_VARIABLE = b"This is a netCDF dimension but not a netCDF variable."
scale_name = (
self.name
if self.name in self._parent._variables
else NOT_A_VARIABLE + dimlen
)
# don't re-create scales if they already exist.
if not self._root._h5py.h5ds.is_scale(self._h5ds.id):
self._h5ds.make_scale(scale_name)
def _attach_scale(self, refs):
"""Attach dimension scale to references"""
for var, dim in refs:
self._parent._all_h5groups[var].dims[dim].attach_scale(self._h5ds)
def _detach_scale(self):
"""Detach dimension scale from all references"""
refs = self._scale_refs
if refs:
for var, dim in refs:
self._parent._all_h5groups[var].dims[dim].detach_scale(self._h5ds)
@property
def _maxsize(self):
return None if self.isunlimited() else self.size
def __len__(self):
if self._phony:
return self._size
return self._h5ds.shape[0]
_cls_name = "h5netcdf.Dimension"
def __repr__(self):
if not self._phony and self._parent._root._closed:
return f"<Closed {self._cls_name}>"
special = ""
if self._phony:
special += " (phony_dim)"
if self.isunlimited():
special += " (unlimited)"
header = f"<{self._cls_name} {self.name!r}: size {self.size}{special}>"
return "\n".join([header])
|