001/* An actor that reads NetCDF files. 002 * 003 * Copyright (c) 2011 The Regents of the University of California. 004 * All rights reserved. 005 * 006 * '$Author: crawl $' 007 * '$Date: 2012-09-18 18:39:51 +0000 (Tue, 18 Sep 2012) $' 008 * '$Revision: 30701 $' 009 * 010 * Permission is hereby granted, without written agreement and without 011 * license or royalty fees, to use, copy, modify, and distribute this 012 * software and its documentation for any purpose, provided that the above 013 * copyright notice and the following two paragraphs appear in all copies 014 * of this software. 015 * 016 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 017 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 018 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 019 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 020 * SUCH DAMAGE. 021 * 022 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 023 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 024 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 025 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 026 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 027 * ENHANCEMENTS, OR MODIFICATIONS. 028 * 029 */ 030package org.kepler.data.netcdf; 031 032import java.io.IOException; 033import java.util.HashMap; 034import java.util.HashSet; 035import java.util.LinkedList; 036import java.util.List; 037import java.util.Map; 038import java.util.Set; 039import java.util.regex.Matcher; 040import java.util.regex.Pattern; 041 042import ptolemy.actor.TypedIOPort; 043import ptolemy.actor.lib.LimitedFiringSource; 044import ptolemy.actor.parameters.FilePortParameter; 045import ptolemy.actor.parameters.PortParameter; 046import ptolemy.data.ArrayToken; 047import ptolemy.data.BooleanToken; 048import ptolemy.data.DoubleMatrixToken; 049import ptolemy.data.DoubleToken; 050import ptolemy.data.FloatToken; 051import ptolemy.data.IntMatrixToken; 052import ptolemy.data.IntToken; 053import ptolemy.data.LongToken; 054import ptolemy.data.ShortToken; 055import ptolemy.data.StringToken; 056import ptolemy.data.Token; 057import ptolemy.data.type.ArrayType; 058import ptolemy.data.type.BaseType; 059import ptolemy.data.type.Type; 060import ptolemy.kernel.CompositeEntity; 061import ptolemy.kernel.util.Attribute; 062import ptolemy.kernel.util.IllegalActionException; 063import ptolemy.kernel.util.NameDuplicationException; 064import ptolemy.kernel.util.Workspace; 065import ptolemy.math.DoubleMatrixMath; 066import ptolemy.math.IntegerMatrixMath; 067import ucar.ma2.Array; 068import ucar.ma2.DataType; 069import ucar.ma2.InvalidRangeException; 070import ucar.ma2.Range; 071import ucar.ma2.Section; 072import ucar.nc2.NetcdfFile; 073import ucar.nc2.Variable; 074 075/** 076 * This actor reads values from a NetCDF file. The <i>constraint</i> parameter 077 * specifies the variables to read and optionally how to subset them. For each 078 * variable, an output port with the same name is created. The type of the 079 * output port depends on how many dimensions are left unconstrained in the 080 * variable: scalar tokens for zero, array tokens for one, and matrix tokens for 081 * two. Unconstrained dimensions of greater than two are not supported. 082 * <p> 083 * The syntax for the <i>constraint</i> parameter is a space-separated list of 084 * variables. Each variable may optionally have a set of dimensional constraints 085 * in the form of [start:end:stride], where start is the starting index, end is 086 * the ending index, and stride is the increment. A dimension may be left 087 * unconstrained by specifying [:]. For example, suppose the variable is a 088 * two-dimensional matrix z[x,y]. To read the entire matrix, use z. To read all 089 * the values where y = 3, use z[:][3]. 090 * <p> 091 * 092 * @author Daniel Crawl 093 * @version $Id: NetCDFReader.java 30701 2012-09-18 18:39:51Z crawl $ 094 * 095 * TODO 096 * 097 * test reading hdf5, other types 098 * 099 */ 100public class NetCDFReader extends LimitedFiringSource { 101 102 public NetCDFReader(CompositeEntity container, String name) 103 throws NameDuplicationException, IllegalActionException { 104 super(container, name); 105 106 filename = new FilePortParameter(this, "filename"); 107 108 constraint = new PortParameter(this, "constraint"); 109 constraint.setStringMode(true); 110 constraint.getPort().setTypeEquals(BaseType.STRING); 111 112 // hide output port name 113 new Attribute(output, "_hide"); 114 } 115 116 /** React to a change in an attribute. */ 117 public void attributeChanged(Attribute attribute) throws IllegalActionException 118 { 119 if(attribute == filename) 120 { 121 _updateFileName(); 122 _updateOutputPorts(); 123 } 124 else if(attribute == constraint) 125 { 126 _parseConstraintExpression(); 127 _updateOutputPorts(); 128 } 129 else 130 { 131 super.attributeChanged(attribute); 132 } 133 } 134 135 /** Clone this actor into the specified workspace. 136 * @param workspace The workspace for the cloned object. 137 * @exception CloneNotSupportedException If cloned ports cannot have 138 * as their container the cloned entity (this should not occur), or 139 * if one of the attributes cannot be cloned. 140 * @return A new NetCDFReader. 141 */ 142 public Object clone(Workspace workspace) throws CloneNotSupportedException 143 { 144 NetCDFReader newObject = (NetCDFReader) super.clone(workspace); 145 newObject._constraint = null; 146 newObject._constraintMap = new HashMap<String,Section>(); 147 newObject._filenameStr = null; 148 newObject._ncFile = null; 149 return newObject; 150 } 151 152 public void fire() throws IllegalActionException 153 { 154 // read tokens in port parameters 155 constraint.update(); 156 filename.update(); 157 158 // output the variables for each connected output port. 159 for(Object object : outputPortList()) 160 { 161 TypedIOPort port = (TypedIOPort)object; 162 if(port.numberOfSinks() > 0) 163 { 164 _outputData(port); 165 } 166 } 167 } 168 169 public void initialize() throws IllegalActionException 170 { 171 super.initialize(); 172 _amFiring = true; 173 174 if(_ncFile == null) { 175 _openFile(); 176 } 177 } 178 179 /** Close the NetCDF file. */ 180 public void wrapup() throws IllegalActionException 181 { 182 _amFiring = false; 183 _closeFile(); 184 super.wrapup(); 185 } 186 187 /////////////////////////////////////////////////////////////////// 188 //// public fields //// 189 190 /** The name of the NetCDF file to read. */ 191 public FilePortParameter filename; 192 193 /** Space-separated list of variables with an optional set of 194 * constraints. Each dimension may be constrained using the 195 * syntax [start:end:stride], or use [:] for no constraint. 196 */ 197 public PortParameter constraint; 198 199 /////////////////////////////////////////////////////////////////// 200 //// private methods //// 201 202 /** Close the NetCDF file. */ 203 private void _closeFile() throws IllegalActionException 204 { 205 if(_ncFile != null) { 206 try { 207 _ncFile.close(); 208 _ncFile = null; 209 } catch (IOException e) { 210 throw new IllegalActionException(this, e, "Error closing " + _filenameStr); 211 } 212 } 213 214 } 215 216 /** Open the NetCDF file. */ 217 private void _openFile() throws IllegalActionException 218 { 219 if(_filenameStr != null && !_filenameStr.isEmpty()) 220 { 221 try { 222 _ncFile = NetcdfFile.open(_filenameStr); 223 } catch (IOException e) { 224 throw new IllegalActionException(this, e, "Error opening " + _filenameStr); 225 } 226 } 227 } 228 229 /** Read a token from the file name port parameter. */ 230 private void _updateFileName() throws IllegalActionException 231 { 232 Token token = filename.getToken(); 233 if(token != null) 234 { 235 String fileStr = ((StringToken)token).stringValue(); 236 if(_filenameStr == null || !_filenameStr.equals(fileStr)) 237 { 238 _filenameStr = fileStr; 239 240 // if the old file is open, close and open the new one 241 if(_ncFile != null) { 242 _closeFile(); 243 } 244 245 if(_ncFile == null) { 246 _openFile(); 247 } 248 } 249 } 250 } 251 252 private void _updateOutputPorts() throws IllegalActionException 253 { 254 if(!_amFiring && _filenameStr != null && !_filenameStr.isEmpty() && _constraint != null) 255 { 256 257 if(_ncFile == null) { 258 _openFile(); 259 } 260 261 Set<String> variableNames = new HashSet<String>(); 262 List<Variable> variables = null; 263 264 // see if there are constraints 265 if(_constraintMap.size() > 0) 266 { 267 // add all the variables in the constraints 268 variableNames.addAll(_constraintMap.keySet()); 269 variables = new LinkedList<Variable>(); 270 for(String name : variableNames) 271 { 272 Variable variable = _ncFile.findVariable(name); 273 if(variable == null) 274 { 275 throw new IllegalActionException(this, "Variable " + name + " is in constraint " + 276 "expression, but not found in file " + _filenameStr); 277 } 278 variables.add(variable); 279 } 280 } 281 else 282 { 283 // add all the variables in the file 284 variables = _ncFile.getVariables(); 285 for(Variable variable : variables) 286 { 287 variableNames.add(variable.getFullName()); 288 } 289 } 290 291 // add ports and set types 292 for(Variable variable : variables) 293 { 294 String name = variable.getFullName(); 295 296 // see if we need to add the port 297 TypedIOPort port = (TypedIOPort) getPort(name); 298 if(port == null) 299 { 300 try 301 { 302 port = new TypedIOPort(this, name, false, true); 303 } 304 catch (NameDuplicationException e) 305 { 306 throw new IllegalActionException(this, e, "Error creating port " + name); 307 } 308 } 309 // see if variable is named "output". we already have port called output, 310 // so unhide it if it is hidden. 311 else if(name.equals("output")) 312 { 313 Attribute attribute = port.getAttribute("_hide"); 314 if(attribute != null) 315 { 316 try 317 { 318 attribute.setContainer(null); 319 } 320 catch (NameDuplicationException e) 321 { 322 throw new IllegalActionException(this, e, "Unable to show output port."); 323 } 324 } 325 } 326 327 Type oldType = port.getType(); 328 329 // set the port type based on the netcdf type and decimation 330 Type newType = _getTokenTypeForVariable(variable); 331 332 if(!oldType.equals(newType)) 333 { 334 port.setTypeEquals(newType); 335 //System.out.println("setting type for " + name); 336 } 337 } 338 339 // delete ports for non-existing variables 340 for(Object obj : outputPortList()) 341 { 342 TypedIOPort port = (TypedIOPort) obj; 343 String portName = port.getName(); 344 if(!variableNames.contains(portName)) 345 { 346 // can't delete output port since belongs to parent class 347 if(portName.equals("output")) 348 { 349 if(port.getAttribute("_hide") == null) 350 { 351 // hide output port 352 try 353 { 354 new Attribute(port, "_hide"); 355 } 356 catch (NameDuplicationException e) 357 { 358 throw new IllegalActionException(this, e, "Unable to hide output port."); 359 } 360 } 361 } 362 else 363 { 364 // remove port 365 try 366 { 367 port.setContainer(null); 368 } 369 catch (NameDuplicationException e) 370 { 371 throw new IllegalActionException(this, e, "Error deleting " + port.getName()); 372 } 373 } 374 } 375 } 376 } 377 } 378 379 /** Get the number of dimensions of a variable after decimating. */ 380 private int _getDimensionsRemaining(Variable variable) 381 { 382 int decimationAmount = 0; 383 384 // see if this variable was decimated in the constraint expression 385 Section section = _constraintMap.get(variable.getFullName()); 386 387 if(section != null) 388 { 389 for(Range range : section.getRanges()) 390 { 391 // NOTE: range can be null if decimation specified as [:] 392 if(range != null && range.length() == 1) 393 { 394 decimationAmount++; 395 } 396 } 397 } 398 399 return variable.getShape().length - decimationAmount; 400 } 401 402 private Type _getTokenTypeForVariable(Variable variable) throws IllegalActionException 403 { 404 405 Type retval = null; 406 407 if(variable.isMetadata()) 408 { 409 throw new IllegalActionException(this, "variable " + variable.getFullName() + " is metadata."); 410 } 411 412 String name = variable.getFullName(); 413 414 DataType dataType = variable.getDataType(); 415 416 int dimensionsRemaining = _getDimensionsRemaining(variable); 417 418 //System.out.println("dim rem for " + name + " is " + dimensionsRemaining); 419 420 if(dimensionsRemaining < 0) 421 { 422 throw new IllegalActionException(this, "Variable " + name + " has " + 423 variable.getShape().length + " dimension(s), but more " + 424 " dimensions have been constrained."); 425 } 426 else if(dimensionsRemaining < 2) 427 { 428 switch(dataType) 429 { 430 case DOUBLE: 431 retval = BaseType.DOUBLE; 432 break; 433 case FLOAT: 434 retval = BaseType.FLOAT; 435 break; 436 case SHORT: 437 retval = BaseType.SHORT; 438 break; 439 case INT: 440 retval = BaseType.INT; 441 break; 442 case LONG: 443 retval = BaseType.LONG; 444 break; 445 case BOOLEAN: 446 retval = BaseType.BOOLEAN; 447 break; 448 default: 449 throw new IllegalActionException(this, "Variable " + name + 450 "has unsupported data type: " + dataType); 451 } 452 453 if(dimensionsRemaining == 1) 454 { 455 retval = new ArrayType(retval); 456 } 457 } 458 else if(dimensionsRemaining == 2) 459 { 460 switch(dataType) 461 { 462 case DOUBLE: 463 case FLOAT: 464 retval = BaseType.DOUBLE_MATRIX; 465 break; 466 case INT: 467 retval = BaseType.INT_MATRIX; 468 break; 469 case LONG: 470 retval = BaseType.LONG_MATRIX; 471 break; 472 case BOOLEAN: 473 retval = BaseType.BOOLEAN_MATRIX; 474 break; 475 default: 476 throw new IllegalActionException(this, "Unsupported matrix " + 477 "type for variable " + name + "(" + dataType + ")"); 478 } 479 } 480 else if(dimensionsRemaining > 2) 481 { 482 throw new IllegalActionException(this, "Variable " + name + " has" + 483 " been decimated to have more than two dimensions, which is" + 484 " currently not supported."); 485 } 486 487 return retval; 488 } 489 490 /** Write the data from the file to an output port. */ 491 private void _outputData(TypedIOPort port) throws IllegalActionException 492 { 493 Token token = null; 494 495 String name = port.getName(); 496 Variable variable = _ncFile.findVariable(name); 497 if(variable == null) 498 { 499 throw new IllegalActionException("Could not find variable " + name + " in file " + _filenameStr); 500 } 501 502 int dimensionsRemaing = _getDimensionsRemaining(variable); 503 504 Array array; 505 try { 506 Section section = _constraintMap.get(name); 507 if(section != null) { 508 array = variable.read(section); 509 } else { 510 array = variable.read(null, variable.getShape()); 511 } 512 } catch (Exception e) { 513 throw new IllegalActionException(this, e, "Unable to read variable " + name); 514 } 515 516 // get the shape from the read array 517 int[] readShape = array.getShape(); 518 519 Object arrayStorage = array.getStorage(); 520 521 DataType dataType = variable.getDataType(); 522 523 if(dimensionsRemaing == 0) 524 { 525 switch(dataType) 526 { 527 case DOUBLE: 528 token = new DoubleToken(((double[])arrayStorage)[0]); 529 break; 530 case FLOAT: 531 token = new FloatToken(((float[])arrayStorage)[0]); 532 break; 533 case SHORT: 534 token = new ShortToken(((short[])arrayStorage)[0]); 535 break; 536 case INT: 537 token = new IntToken(((int[])arrayStorage)[0]); 538 break; 539 case LONG: 540 token = new LongToken(((long[])arrayStorage)[0]); 541 break; 542 case BOOLEAN: 543 token = new BooleanToken(((boolean[])arrayStorage)[0]); 544 break; 545 default: 546 throw new IllegalActionException(this, "Variable " + name + 547 " has unsupported data type: " + dataType); 548 } 549 } 550 else if(dimensionsRemaing == 1) 551 { 552 int length = java.lang.reflect.Array.getLength(arrayStorage); 553 StringBuilder arrayStr = new StringBuilder("{"); 554 for(int i = 0; i < length - 1; i++) 555 { 556 arrayStr.append(java.lang.reflect.Array.get(arrayStorage, i)); 557 arrayStr.append(","); 558 } 559 arrayStr.append(java.lang.reflect.Array.get(arrayStorage, length - 1)); 560 arrayStr.append("}"); 561 token = new ArrayToken(arrayStr.toString()); 562 } 563 else // dimensionsRemaing == 2 564 { 565 switch(dataType) 566 { 567 case DOUBLE: 568 569 /* 570 double[][] data = new double[shape[0]][shape[1]]; 571 for(int i = 0; i < shape[0]; i++) 572 { 573 for(int j = 0; j < shape[1]; j++) 574 { 575 data[i][j] = arrayDouble.get(i,j); 576 } 577 } 578 */ 579 580 token = new DoubleMatrixToken((double[])array.getStorage(), 581 readShape[0], readShape[1]); 582 583 // XXX take the transpose to get elements in correct position 584 token = new DoubleMatrixToken( 585 DoubleMatrixMath.transpose(((DoubleMatrixToken)token).doubleMatrix())); 586 587 588 //System.out.println(getName() + ": array 5000,73 = " + arrayDouble.get(5000, 73)); 589 //System.out.println("rows = " + ((DoubleMatrixToken)token).getRowCount()); 590 //System.out.println("cols = " + ((DoubleMatrixToken)token).getColumnCount()); 591 //System.out.println(getName() + ": token 5000,73 = " + 592 //((DoubleMatrixToken)token).getElementAt(73, 5000)); 593 594 595 break; 596 597 case INT: 598 599 token = new IntMatrixToken((int[])array.getStorage(), 600 readShape[0], readShape[1]); 601 602 // XXX take the transpose to get elements in correct position 603 token = new IntMatrixToken( 604 IntegerMatrixMath.transpose(((IntMatrixToken)token).intMatrix())); 605 606 break; 607 608 default: 609 throw new IllegalActionException(this, "Variable " + name + 610 " has unsupported data type: " + dataType); 611 } 612 } 613 614 if(token != null) 615 { 616 //System.out.println(getFullName() + " output token type " + token.getType()); 617 port.broadcast(token); 618 } 619 } 620 621 private void _parseConstraintExpression() throws IllegalActionException 622 { 623 String origConstraintStr = ((StringToken)constraint.getToken()).stringValue(); 624 // see if constraint expression has changed 625 if(_constraint == null || !_constraint.equals(origConstraintStr)) 626 { 627 _constraint = origConstraintStr.trim(); 628 _constraintMap.clear(); 629 630 String constraintStr = _constraint; 631 while(constraintStr.length() > 0) 632 { 633 Matcher matcher = _VARIABLE_PATTERN.matcher(constraintStr); 634 if(!matcher.matches()) 635 { 636 throw new IllegalActionException(this, "Bad constraint: " + origConstraintStr); 637 } 638 String variableName = matcher.group(1); 639 640 Section section = null; 641 String sectionStr = ""; 642 if(matcher.groupCount() > 1) 643 { 644 sectionStr = matcher.group(2); 645 String formattedSectionStr = 646 sectionStr.replaceAll("\\[", "").replaceAll("\\]", ",").replaceAll(",$", ""); 647 648 try { 649 section = new Section(formattedSectionStr); 650 } catch (InvalidRangeException e) { 651 throw new IllegalActionException(this, e, "Invalid decimation " + sectionStr); 652 } 653 } 654 655 _constraintMap.put(variableName, section); 656 657 constraintStr = constraintStr.substring(variableName.length() + sectionStr.length()); 658 } 659 } 660 } 661 662 /////////////////////////////////////////////////////////////////// 663 //// private fields //// 664 665 /** Variable pattern: variable name optionally followed by dimension(s). */ 666 private static final Pattern _VARIABLE_PATTERN = 667 Pattern.compile("(\\w+)([\\d\\:\\[\\]]*).*"); 668 669 /** The name of the NetCDF file. */ 670 private String _filenameStr; 671 672 /** The constraint expression. */ 673 private String _constraint; 674 675 /** A map of variable name to dimension decimation. */ 676 private Map<String,Section> _constraintMap = new HashMap<String,Section>(); 677 678 /** NetCDF file object. */ 679 private NetcdfFile _ncFile; 680 681 /** If true, workflow is executing. */ 682 private boolean _amFiring; 683}