source: issm/trunk/src/m/contrib/musselman/read_netCDF.m@ 27894

Last change on this file since 27894 was 27894, checked in by musselman, 19 months ago

Added functionality for cell arrays of objects like definitions in outputdefinition

File size: 20.6 KB
Line 
1%{
2Given a NetCDF4 file, this set of functions will perform the following:
3 1. Enter each group of the file.
4 2. For each variable in each group, update an empty model with the variable's data
5 3. Enter nested groups and repeat
6
7
8If the model you saved has subclass instances that are not in the standard model() class
9you can:
10 1. Copy lines 30-35, set the "results" string to the name of the subclass instance,
11 2. Copy and modify the make_results_subclasses() function to create the new subclass
12 instances you need.
13From there, the rest of this script will automatically create the new subclass
14instance in the model you're writing to and store the data from the netcdf file there.
15%}
16
17
18function model_copy = read_netCDF(filename, varargin)
19 if nargin > 1
20 verbose = true;
21 else
22 verbose = false;
23 end
24
25 if verbose
26 fprintf('NetCDF42C v1.1.14\n');
27 end
28 % make a model framework to fill that is in the scope of this file
29 model_copy = model();
30
31 % Check if path exists
32 if exist(filename, 'file')
33 if verbose
34 fprintf('Opening %s for reading\n', filename);
35 end
36
37 % Open the given netCDF4 file
38 NCData = netcdf.open(filename, 'NOWRITE');
39 % Remove masks from netCDF data for easy conversion: NOT WORKING
40 %netcdf.setMask(NCData, 'NC_NOFILL');
41
42 % see if results is in there, if it is we have to instantiate some classes
43 try
44 results_group_id = netcdf.inqNcid(NCData, "results");
45 model_copy = make_results_subclasses(model_copy, NCData, verbose);
46 catch
47 end % 'results' group doesn't exist
48
49 % see if inversion is in there, if it is we may have to instantiate some classes
50 try
51 inversion_group_id = netcdf.inqNcid(NCData, "inversion");
52 model_copy = check_inversion_class(model_copy, NCData, verbose);
53 catch
54 end % 'inversion' group doesn't exist
55
56 % loop over first layer of groups in netcdf file
57 for group = netcdf.inqGrps(NCData)
58 group_id = netcdf.inqNcid(NCData, netcdf.inqGrpName(group));
59 %disp(netcdf.inqGrpNameFull(group_id))
60 % hand off first level to recursive search
61 model_copy = walk_nested_groups(group_id, model_copy, NCData, verbose);
62 end
63
64 % Close the netCDF file
65 netcdf.close(NCData);
66 if verbose
67 disp('Model Successfully Copied')
68 end
69 else
70 fprintf('File %s does not exist.\n', filename);
71 end
72end
73
74
75function model_copy = make_results_subclasses(model_copy, NCData, verbose)
76 resultsGroup = netcdf.inqNcid(NCData, "results");
77 variables = netcdf.inqVarIDs(resultsGroup);
78 for name = variables
79 class_instance = netcdf.inqVar(resultsGroup, name);
80 class_instance_names_raw = netcdf.getVar(resultsGroup, name, 'char').';
81 class_instance_names = cellstr(class_instance_names_raw);
82 for index = 1:numel(class_instance_names)
83 class_instance_name = class_instance_names{index};
84 model_copy.results = setfield(model_copy.results, class_instance_name, struct());
85 end
86 %model_copy.results = setfield(model_copy.results, class_instance, class_instance_name);
87 end
88 model_copy = model_copy;
89 if verbose
90 disp('Successfully recreated results structs:')
91 for fieldname = string(fieldnames(model_copy.results))
92 disp(fieldname)
93 end
94 end
95end
96
97
98function model_copy = check_inversion_class(model_copy, NCData, verbose)
99 % get the name of the inversion class: either inversion or m1qn3inversion or taoinversion
100 inversionGroup = netcdf.inqNcid(NCData, "inversion");
101 varid = netcdf.inqVarID(inversionGroup, 'inversion_class_name');
102 inversion_class = convertCharsToStrings(netcdf.getVar(inversionGroup, varid,'char'));
103 if strcmp(inversion_class, 'm1qn3inversion')
104 model_copy.inversion = m1qn3inversion();
105 if verbose
106 disp('Successfully created inversion class instance: m1qn3inversion')
107 end
108 elseif strcmp(inversion_class, 'taoinversion')
109 model_copy.inversion = taoinversion();
110 if verbose
111 disp('Successfully created inversion class instance: taoinversion')
112 end
113 else
114 if verbose
115 disp('No inversion class was found')
116 end
117 end
118 model_copy = model_copy;
119end
120
121
122function model_copy = walk_nested_groups(group_location_in_file, model_copy, NCData, verbose)
123 % we search the current group level for variables by getting this struct
124 variables = netcdf.inqVarIDs(group_location_in_file);
125
126 % from the variables struct get the info related to the variables
127 for variable = variables
128 [varname, xtype, dimids, numatts] = netcdf.inqVar(group_location_in_file, variable);
129
130 % keep an eye out for nested structs:
131 if strcmp(varname, 'this_is_a_nested')
132 is_object = true;
133 model_copy = copy_nested_struct(group_location_in_file, model_copy, NCData, verbose);
134 elseif strcmp(varname, 'name_of_cell_array')
135 is_object = true;
136 model_copy = copy_cell_array_of_objects(variables, group_location_in_file, model_copy, NCData, verbose);
137 elseif strcmp(varname, 'solution')
138 % band-aid pass..
139 else
140 if logical(exist('is_object', 'var'))
141 % already handled
142 else
143 model_copy = copy_variable_data_to_new_model(group_location_in_file, varname, xtype, model_copy, NCData, verbose);
144 end
145 end
146 end
147
148 % try to find groups in current level, if it doesn't work it's because there is nothing there
149 %try
150 % if it's a nested struct the function copy_nested_struct has already been called
151 if logical(exist('is_object', 'var'))
152 % do nothing
153 else
154 % search for nested groups in the current level to feed back to this function
155 groups = netcdf.inqGrps(group_location_in_file);
156 if not(isempty(groups))
157 for group = groups
158 group_id = netcdf.inqNcid(group_location_in_file, netcdf.inqGrpName(group));
159 %disp(netcdf.inqGrpNameFull(group_id))
160 model_copy = walk_nested_groups(group, model_copy, NCData, verbose);
161 end
162 end
163 end
164 %catch % no nested groups here
165 %end
166end
167
168
169% to read cell arrays with objects:
170function model_copy = copy_cell_array_of_objects(variables, group_location_in_file, model_copy, NCData, verbose);
171 %{
172 The structure in netcdf for groups with the name_of_cell_array variable is like:
173
174 group: 2x6_cell_array_of_objects {
175 name_of_cell_array = <name_of_cell_array>
176
177 group: Row_1_of_2 {
178 group: Col_1_of_6 {
179 ... other groups can be here that refer to objects
180 } // group Col_6_of_6
181 } // group Row_1_of_2
182
183 group: Row_2_of_2 {
184 group: Col_1_of_6 {
185 ... other groups can be here that refer to objects
186 } // group Col_6_of_6
187 } // group Row_2_of_2
188 } // group 2x6_cell_array_of_objects
189
190 We have to navigate this structure to extract all the data and recreate the
191 original structure when the model was saved
192 %}
193
194 % get the name_of_cell_array, rows and cols vars
195 name_of_cell_array_varID = netcdf.inqVarID(group_location_in_file, 'name_of_cell_array');
196 rows_varID = netcdf.inqVarID(group_location_in_file, 'rows');
197 cols_varID = netcdf.inqVarID(group_location_in_file, 'cols');
198
199 name_of_cell_array = netcdf.getVar(group_location_in_file, name_of_cell_array_varID).'; % transpose
200 rows = netcdf.getVar(group_location_in_file, rows_varID);
201 cols = netcdf.getVar(group_location_in_file, cols_varID);
202
203 % now we work backwards: make the cell array, fill it in, and assign it to the model
204
205 % make the cell array
206 cell_array_placeholder = cell(rows, cols);
207
208 % get subgroups which are elements of the cell array
209 subgroups = netcdf.inqGrps(group_location_in_file); % numerical cell array with ID's of subgroups
210
211 % enter each subgroup, get the data, assign it to the corresponding index of cell array
212 if rows > 1
213 % we go over rows
214 % set index for cell array rows
215 row_idx = 1;
216 for row = subgroups
217 % now columns
218 columns = netcdf.inqGrps(group_location_in_file);
219
220 % set index for cell array cols
221 col_idx = 1;
222 for column = columns
223 % now variables
224 current_column_varids = netcdf.inqVarIDs(column);
225
226 % if 'class_is_a' or 'this_is_a_nested' variables is present at this level we have to handle them accordingly
227 try
228 class_is_aID = netcdf.inqVarID(column, 'class_is_a');
229 col_data = deserialize_class(column, NCData, verbose);
230 is_object = true;
231 catch
232 end
233
234 try
235 this_is_a_nestedID = netcdf.inqVarID(column, 'this_is_a_nested');
236 % functionality not supported
237 disp('Error: Cell Arrays of structs not yet supported!')
238 % copy_nested_struct(column, model_copy, NCData, verbose)
239 is_object = true;
240 catch
241 end
242
243 if logical(exist('is_object', 'var'))
244 % already taken care of
245 else
246 % store the variables as normal -- to be added later
247 disp('Error: Cell Arrays of mixed objects not yet supported!')
248 for var = current_column_varids
249 % not supported
250 end
251 end
252
253 cell_array_placeholder{row_idx, col_idx} = col_data;
254 col_idx = col_idx + 1;
255 end
256 row_idx = row_idx + 1;
257 end
258 else
259 % set index for cell array
260 col_idx = 1;
261 for column = subgroups
262 % now variables
263 current_column_varids = netcdf.inqVarIDs(column);
264
265 % if 'class_is_a' or 'this_is_a_nested' variables is present at this level we have to handle them accordingly
266 try
267 classID = netcdf.inqVarID(column, 'class_is_a');
268 col_data = deserialize_class(classID, column, NCData, verbose);
269 is_object = true;
270 catch ME
271 rethrow(ME)
272 end
273
274 try
275 this_is_a_nestedID = netcdf.inqVarID(column, 'this_is_a_nested');
276 % functionality not supported
277 disp('Error: Cell Arrays of structs not yet supported!')
278 % col_data = copy_nested_struct(column, model_copy, NCData, verbose);
279 is_object = true;
280 catch
281 end
282 if logical(exist('is_object', 'var'))
283 % already taken care of
284 else
285 % store the variables as normal -- to be added later
286 disp('Error: Cell Arrays of mixed objects not yet supported!')
287 for var = current_column_varids
288 % col_data = not supported
289 end
290 end
291
292 cell_array_placeholder{col_idx} = col_data;
293 col_idx = col_idx + 1;
294
295 end
296 end
297
298
299 % Like in copy_nested_struct, we can only handle things 1 layer deep.
300 % assign cell array to model
301 address_to_attr_list = split(netcdf.inqGrpNameFull(group_location_in_file), '/');
302 address_to_attr = address_to_attr_list{2};
303 if isprop(model_copy.(address_to_attr), name_of_cell_array);
304 model_copy.(address_to_attr).(name_of_cell_array) = cell_array_placeholder;
305 else
306 model_copy = addprop(model_copy.(address_to_attr), name_of_cell_array, cell_array_placeholder);
307 end
308
309 if verbose
310 fprintf("Successfully loaded cell array %s to %s\n", name_of_cell_array,address_to_attr_list{2})
311 end
312end
313
314
315
316
317function output = deserialize_class(classID, group, NCData, verbose)
318 %{
319 This function will recreate a class
320 %}
321
322 % get the name of the class
323 name = netcdf.getVar(group, classID).';
324
325 % instantiate it
326 class_instance = eval([name, '()']);
327
328 % get and assign properties
329 subgroups = netcdf.inqGrps(group); % numerical cell array with ID's of subgroups
330
331 if numel(subgroups) == 1
332 % get properties
333 varIDs = netcdf.inqVarIDs(subgroups);
334 for varID = varIDs
335 % var metadata
336 [varname, xtype, dimids, numatts] = netcdf.inqVar(subgroups, varID);
337 % data
338 data = netcdf.getVar(subgroups, varID);
339
340 % netcdf uses Row Major Order but MATLAB uses Column Major Order so we need to transpose all arrays w/ more than 1 dim
341 if all(size(data)~=1) || xtype == 2
342 data = data.';
343 end
344
345 % some classes have permissions... so we skip those
346 try
347 % if property already exists, assign new value
348 if isprop(class_instance, varname)
349 class_instance.(varname) = data;
350 else
351 addprop(class_instance, varname, data);
352 end
353 catch
354 end
355 end
356 else
357 % not supported
358 end
359 output = class_instance;
360end
361
362
363function model_copy = copy_nested_struct(group_location_in_file, model_copy, NCData, verbose)
364 %{
365 A common multidimensional struct array is the 1xn md.results.TransientSolution struct.
366 The process to recreate is as follows:
367 1. Get the name of the struct from group name
368 2. Get the fieldnames from the subgroups
369 3. Recreate the struct with fieldnames
370 4. Populate the fields with their respective values
371 %}
372
373 % step 1
374 name_of_struct = netcdf.inqGrpName(group_location_in_file);
375
376 % step 2
377 subgroups = netcdf.inqGrps(group_location_in_file); % numerical cell array with ID's of subgroups
378 % get single subgroup's data
379 single_subgroup_ID = subgroups(1);
380 subgroup_varids = netcdf.inqVarIDs(single_subgroup_ID);
381 fieldnames = {};
382 for variable = subgroup_varids
383 [varname, xtype, dimids, numatts] = netcdf.inqVar(single_subgroup_ID, variable);
384 fieldnames{end+1} = varname;
385 end
386
387 % step 3
388 address_in_model_raw = split(netcdf.inqGrpNameFull(group_location_in_file), '/');
389 address_in_model = address_in_model_raw{2};
390
391 % we cannot assign a variable to represent this object as MATLAB treats all variables as copies
392 % and not pointers to the same memory address
393 % this means that if address_in_model has more than 1 layer, we need to modify the code. For now,
394 % we just hope this will do. An example of a no-solution would be model().abc.def.ghi.field whereas we're only assuming model().abc.field now
395
396 model_copy.(address_in_model).(name_of_struct) = struct();
397 % for every fieldname in the subgroup, create an empty field
398 for fieldname = string(fieldnames)
399 model_copy.(address_in_model).(name_of_struct).(fieldname) = {};
400 end
401
402 % use repmat to make the struct array multidimensional along the fields axis
403 number_of_dimensions = numel(subgroups);
404 model_copy.(address_in_model).(name_of_struct) = repmat(model_copy.(address_in_model).(name_of_struct), 1, number_of_dimensions);
405
406 % step 4
407 % for every layer of the multidimensional struct array, populate the fields
408 for current_layer = 1:number_of_dimensions
409 % choose subgroup
410 current_layer_subgroup_ID = subgroups(current_layer);
411 % get all vars
412 current_layer_subgroup_varids = netcdf.inqVarIDs(current_layer_subgroup_ID);
413 % get individual vars and set fields at layer current_layer
414 for varid = current_layer_subgroup_varids
415 [varname, xtype, dimids, numatts] = netcdf.inqVar(current_layer_subgroup_ID, varid);
416 data = netcdf.getVar(current_layer_subgroup_ID, varid);
417
418 % netcdf uses Row Major Order but MATLAB uses Column Major Order so we need to transpose all arrays w/ more than 1 dim
419 if all(size(data)~=1) || xtype == 2
420 data = data.';
421 end
422
423 % set the field
424 model_copy.(address_in_model).(name_of_struct)(current_layer).(varname) = data;
425 %address_to_struct_in_model = setfield(address_to_struct_in_model(current_layer), varname, data)
426 end
427 model_copy.(address_in_model).(name_of_struct)(current_layer);
428 if verbose
429 fprintf("Successfully loaded layer %s to multidimension struct array\n", num2str(current_layer))
430 end
431 end
432 model_copy = model_copy;
433 if verbose
434 fprintf('Successfully recreated multidimensional structure array %s in md.%s\n', name_of_struct, address_in_model)
435 end
436end
437
438
439
440
441%{
442Since there are two types of objects that MATLAB uses (classes and structs), we have to check
443which object we're working with before we can set any fields/attributes of it. After this is completed,
444we can write the data to that location in the model.
445%}
446
447function model_copy = copy_variable_data_to_new_model(group_location_in_file, varname, xtype, model_copy, NCData, verbose)
448 %disp(varname)
449 % this is an inversion band-aid
450 if strcmp(varname, 'inversion_class_name') || strcmp(varname, 'name_of_struct') || strcmp(varname, 'solution')
451 % we don't need this
452 else
453 % putting try/catch here so that any errors generated while copying data are logged and not lost by the try/catch in walk_nested_groups function
454 try
455 %disp(netcdf.inqGrpNameFull(group_location_in_file))
456 %disp(class(netcdf.inqGrpNameFull(group_location_in_file)))
457 address_to_attr = strrep(netcdf.inqGrpNameFull(group_location_in_file), '/', '.');
458 varid = netcdf.inqVarID(group_location_in_file, varname);
459 data = netcdf.getVar(group_location_in_file, varid);
460
461
462 % if we have an empty string
463 if xtype == 2 && isempty(all(data))
464 data = cell(char());
465 % if we have an empty cell-char array
466 elseif numel(data) == 1 && xtype == 3 && data == -32767
467 data = cell(char());
468 elseif isempty(all(data))
469 data = []
470 end
471 % band-aid for some cell-char-arrays:
472 if xtype == 2 && strcmp(data, 'default')
473 data = {'default'};
474 end
475
476 % netcdf uses Row Major Order but MATLAB uses Column Major Order so we need to transpose all arrays w/ more than 1 dim
477 if all(size(data)~=1) || xtype == 2
478 data = data.';
479 end
480
481 % if we have a list of strings
482 if xtype == 2
483 try
484 if strcmp(netcdf.getAtt(group_location_in_file, varid, "type_is"), 'cell_array_of_strings')
485 data = cellstr(data);
486 end
487 catch
488 % no attr found so we pass
489 end
490 end
491
492 % the issm c compiler does not work with int64 datatypes, so we need to convert those to int16
493 % reference this (very hard to find) link for netcdf4 datatypes: https://docs.unidata.ucar.edu/netcdf-c/current/netcdf_8h_source.html
494 %xtype
495 if xtype == 10
496 arg_to_eval = ['model_copy', address_to_attr, '.', varname, ' = ' , 'double(data);'];
497 eval(arg_to_eval);
498 %disp('Loaded int64 as int16')
499 else
500 arg_to_eval = ['model_copy', address_to_attr, '.', varname, ' = data;'];
501 eval(arg_to_eval);
502 end
503
504 if verbose
505 full_addy = netcdf.inqGrpNameFull(group_location_in_file);
506 %disp(xtype)
507 %class(data)
508 fprintf('Successfully loaded %s to %s\n', varname, full_addy);
509 end
510
511 catch ME %ME is an MException struct
512 % Some error occurred if you get here.
513 fprintf(1,'There was an error with %s! \n', varname)
514 errorMessage = sprintf('Error in function %s() at line %d.\n\nError Message:\n%s', ME.stack.name, ME.stack.line, ME.message);
515 fprintf(1, '%s\n', errorMessage);
516 uiwait(warndlg(errorMessage));
517 %line = ME.stack.line
518 %fprintf(1,'There was an error with %s! \n', varname)
519 %fprintf('The message was:\n%s\n',ME.message);
520 %fprintf(1,'The identifier was:\n%s\n',ME.identifier);
521
522 % more error handling...
523 end
524 end
525 model_copy = model_copy;
526end
Note: See TracBrowser for help on using the repository browser.