Skip to content

Commit 0b673eb

Browse files
committed
WIP: template now creates netCDF file
* added method to automatically update dimensions from values arrays * added writing of variable values to file * create() method now does everything (don't need to set outfile first) * removed ability to specify attribute types in JSON for now
1 parent b77e116 commit 0b673eb

4 files changed

Lines changed: 85 additions & 39 deletions

File tree

ncwriter/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
__all__ = [
44
'DatasetTemplate'
5-
]
5+
]

ncwriter/template.py

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# TODO check_var too complex
1212
# TODO createVariables too complex
1313
# TODO implement from_file/from_cdl/from_json kwarg!?
14+
# TODO Allow for attribute types to be specified in JSON
1415

1516
import json
1617
from collections import OrderedDict
@@ -273,11 +274,6 @@ def from_json(cls, path):
273274
global_attributes=template.get('global_attributes')
274275
)
275276

276-
def set_output(self, outfile, mode='w', **kwargs):
277-
"""Create the dataset """
278-
self.outfile = outfile
279-
self.ncobj = netCDF4.Dataset(self.outfile, mode=mode, **kwargs)
280-
281277
def _create_var_opts(self, vdict):
282278
"""Return a list with attribute names required for the creation of variable
283279
defined by :vdict: This include creation/special options like:
@@ -295,6 +291,40 @@ def _create_var_opts(self, vdict):
295291
inside = inside.union(aliases)
296292
return list(inside)
297293

294+
def update_dimensinos(self):
295+
"""Update the sizes of dimensions to be consistent with the arrays set as variable values, if possible.
296+
Otherwise raise ValueError. Also raise ValueError if a dimension that already has a non-zero size is not
297+
consistent with variable array sizes.
298+
"""
299+
for name, var in self.variables.iteritems():
300+
values = var.get('values')
301+
if values is None:
302+
continue
303+
304+
var_shape = values.shape
305+
var_dims = var.get('dims', [])
306+
if len(var_shape) != len(var_dims):
307+
raise ValueError(
308+
"Variable '{name}' has {ndim} dimensions, but value array has {nshape} dimensions.".format(
309+
name=name, ndim=len(var_dims), nshape=len(var_shape)
310+
)
311+
)
312+
313+
for dim, size in zip(var_dims, var_shape):
314+
template_dim = self.dimensions[dim]
315+
if template_dim is None or template_dim == 0:
316+
self.dimensions[dim] = size
317+
318+
# check that shape is now consistent
319+
template_shape = tuple(self.dimensions[d] for d in var_dims)
320+
if var_shape != template_shape:
321+
raise ValueError(
322+
"Variable '{name}' has dimensions {var_dims} and shape {var_shape}, inconsistent with dimension "
323+
"sizes defined in template {template_shape}".format(
324+
name=name, var_dims=var_dims, var_shape=var_shape, template_shape=template_shape
325+
)
326+
)
327+
298328
def createDimensions(self):
299329
"""Create the dimensions on the netcdf file"""
300330
for dname, dval in zip(self.dimensions.keys(),
@@ -306,20 +336,17 @@ def createVariables(self, **kwargs):
306336
**kwargs are included here to overload all options for all variables
307337
like `zlib` and friends.
308338
"""
309-
for v in self.variables.keys():
310-
varname = v #self.variables[v]['name']
311-
datatype = self.variables[v]['type']
312-
dimensions = self.variables[v]['dims']
313-
314-
var_c_opts = {}
339+
for varname, var in self.variables.iteritems():
340+
datatype = var['type']
341+
dimensions = var['dims']
315342
cwargs = kwargs.copy()
316343
if dimensions is None: # no kwargs in createVariable
317-
self.ncobj.createVariable(varname, datatype)
344+
ncvar = self.ncobj.createVariable(varname, datatype)
318345
else:
319-
var_c_keys = list(self._create_var_opts(self.variables[v]))
346+
var_c_keys = list(self._create_var_opts(var))
320347

321348
var_c_opts = dict(
322-
(x, self.variables[v][x]) for x in var_c_keys)
349+
(x, var[x]) for x in var_c_keys)
323350

324351
ureq_fillvalue = [
325352
x for x in cwargs.keys() if x in self.fill_aliases
@@ -344,32 +371,33 @@ def createVariables(self, **kwargs):
344371
if fv_val:
345372
var_c_opts['fill_value'] = fv_val[-1]
346373

347-
self.ncobj.createVariable(
374+
ncvar = self.ncobj.createVariable(
348375
varname, datatype, dimensions=dimensions, **var_c_opts)
349376

350-
if 'attr' in self.variables[v].keys():
351-
attrs = self.variables[v]['attr'].copy()
377+
# add variable values
378+
ncvar[:] = var['values']
379+
380+
# add variable attributes
381+
if var.get('attr'):
382+
attrs = var['attr'].copy()
352383
for not_attr in self._create_var_opts(attrs):
353384
attrs.pop(not_attr)
354-
355-
for attname in attrs.keys():
356-
var = self.ncobj.variables[varname]
357-
value = np.array(attrs[attname]['value']).astype(
358-
attrs[attname]['type'])
359-
var.setncattr(attname, value)
385+
ncvar.setncatts(attrs)
360386

361387
def createGlobalAttrs(self):
362388
"""Add the global attributes for the current class"""
363389
for att in self.global_attributes.keys():
364390
self.ncobj.setncattr(att, self.global_attributes[att])
365391

366-
def create(self, **kwargs):
367-
"""Create in the dimensions/variable and attributes and fill with
368-
basic information"""
392+
def create(self, outfile, mode='w', var_args={}, **kwargs):
393+
"""Create the file according to all the information in the template"""
394+
self.outfile = outfile
395+
self.ncobj = netCDF4.Dataset(self.outfile, mode=mode, **kwargs)
396+
397+
self.update_dimensinos()
369398
self.createDimensions()
370-
self.createVariables(**kwargs)
399+
self.createVariables(**var_args)
371400
self.createGlobalAttrs()
372401
self.ncobj.sync()
373402
self.ncobj.close()
374403
self.ncobj = netCDF4.Dataset(self.outfile, 'a')
375-
pass

test_ncwriter/template1.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,18 @@
2626
"type": "float32",
2727
"attr": {
2828
"standard_name": "sea_water_temperature",
29-
"units": "degC"
29+
"units": "degC",
30+
"valid_min": 0.0,
31+
"valid_max": 42.00
3032
}
3133
}
3234
},
3335
"global_attributes": {
3436
"title": "test dataset",
3537
"abstract": "This is a dataset used for testing",
36-
"Conventions": "CF-1.6,IMOS-1.4"
38+
"Conventions": "CF-1.6,IMOS-1.4",
39+
"number": 10,
40+
"big_number": 12345678901234567890,
41+
"big_float": 1.234567890123456e777
3742
}
3843
}

test_ncwriter/test_template.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,21 +128,34 @@ def test_create_file(self):
128128
template = DatasetTemplate.from_json(TEMPLATE_JSON)
129129
template.variables['TIME']['values'] = self.values10
130130
template.variables['DEPTH']['values'] = self.values1
131-
template.variables['TEMP']['values'] = self.values10
131+
template.variables['TEMP']['values'] = self.values10.reshape((10, 1))
132132
template.create(self.temp_nc_file)
133+
print("Created nc file '{}'".format(self.temp_nc_file))
133134

134135
dataset = Dataset(self.temp_nc_file)
135136

137+
expected_dimensions = OrderedDict([
138+
('TIME', len(self.values10)),
139+
('DEPTH', len(self.values1))
140+
])
136141
ds_dimensions = OrderedDict((k, v.size) for k, v in dataset.dimensions.iteritems())
137-
self.assertEqual(self.dimensions, ds_dimensions)
142+
self.assertEqual(expected_dimensions, ds_dimensions)
138143

139-
for vname, vdict in self.variables:
144+
for vname, vdict in self.variables.iteritems():
140145
ds_var = dataset[vname]
141-
self.assertEqual(vdict['dims'], ds_var.dimensions)
142-
ds_var_attr = OrderedDict((k, ds_var[k]) for k in ds_var.ncattrs())
143-
self.assertEqual(vdict['attr'], ds_var_attr)
144-
145-
ds_global_attributes = OrderedDict((k, dataset[k]) for k in dataset.ncattrs())
146+
self.assertEqual(vdict['dims'], list(ds_var.dimensions))
147+
self.assertEqual(vdict['type'], ds_var.dtype)
148+
ds_var_attr = OrderedDict((k, ds_var.getncattr(k)) for k in ds_var.ncattrs())
149+
if vdict['attr'] is None:
150+
self.assertEqual({}, ds_var_attr)
151+
else:
152+
self.assertEqual(vdict['attr'], ds_var_attr)
153+
154+
self.assertTrue(all(dataset['TIME'] == self.values10))
155+
self.assertTrue(all(dataset['DEPTH'] == self.values1))
156+
self.assertTrue(all(dataset['TEMP'] == self.values10.reshape(10, 1)))
157+
158+
ds_global_attributes = OrderedDict((k, dataset.getncattr(k)) for k in dataset.ncattrs())
146159
self.assertEqual(self.global_attributes, ds_global_attributes)
147160

148161
# TODO: add data from multiple numpy arrays

0 commit comments

Comments
 (0)