001/* Generate an occupancy grid from a PGM image. 002 003 Copyright (c) 2014-2018 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.actor.lib.image; 029 030import java.io.DataInputStream; 031import java.io.FileInputStream; 032import java.io.IOException; 033import java.util.Scanner; 034 035import ptolemy.actor.lib.Source; 036import ptolemy.data.ArrayToken; 037import ptolemy.data.IntToken; 038import ptolemy.data.RecordToken; 039import ptolemy.data.Token; 040import ptolemy.data.expr.FileParameter; 041import ptolemy.data.expr.Parameter; 042import ptolemy.data.type.ArrayType; 043import ptolemy.data.type.BaseType; 044import ptolemy.data.type.RecordType; 045import ptolemy.data.type.Type; 046import ptolemy.kernel.CompositeEntity; 047import ptolemy.kernel.util.Attribute; 048import ptolemy.kernel.util.IllegalActionException; 049import ptolemy.kernel.util.NameDuplicationException; 050 051/** Read a PGM file and output it as an occupancy grid. Most ROS maps 052 * are represented as a portable bitmap therefore this actor is a 053 * helper for reading a saved map scan. 054 * 055 * @author Ilge Akkaya 056 * @version $Id$ 057 * @since Ptolemy II 11.0 058 * @Pt.ProposedRating Red (ilgea) 059 * @Pt.AcceptedRating 060 */ 061public class PGMReader extends Source { 062 /** Construct an actor with the given container and name. 063 * The output and trigger ports are also constructed. 064 * @param container The container. 065 * @param name The name of this actor. 066 * @exception IllegalActionException If the entity cannot be contained 067 * by the proposed container. 068 * @exception NameDuplicationException If the container already has an 069 * actor with this name. 070 */ 071 public PGMReader(CompositeEntity container, String name) 072 throws IllegalActionException, NameDuplicationException { 073 super(container, name); 074 075 fileOrURL = new FileParameter(this, "fileOrURL"); 076 077 levelMap = new Parameter(this, "levelMap"); 078 levelMap.setExpression("{}"); 079 levelMap.setTypeEquals(new ArrayType(BaseType.INT)); 080 String[] outputLabels = { "width", "height", "grid" }; 081 Type[] types = { BaseType.INT, BaseType.INT, 082 new ArrayType(BaseType.INT) }; 083 output.setTypeEquals(new RecordType(outputLabels, types)); 084 } 085 086 /////////////////////////////////////////////////////////////////// 087 //// ports and parameters //// 088 089 /** The file name or URL from which to read. This is a string with 090 * any form accepted by File Attribute. 091 * @see FileParameter 092 */ 093 public FileParameter fileOrURL; 094 095 /** A mapping between image input and output values to generate a quantized output. 096 * Set this parameter to empty array for no quantization. Empty array means no quantization*/ 097 public Parameter levelMap; 098 099 @Override 100 public void attributeChanged(Attribute attribute) 101 throws IllegalActionException { 102 if (attribute == levelMap) { 103 int length = ((ArrayToken) levelMap.getToken()).length(); 104 _quantize = true; 105 if (length > 0) { 106 _levels = new int[length]; 107 for (int i = 0; i < _levels.length; i++) { 108 _levels[i] = ((IntToken) ((ArrayToken) levelMap.getToken()) 109 .getElement(i)).intValue(); 110 } 111 } else { 112 _quantize = false; 113 } 114 } else { 115 super.attributeChanged(attribute); 116 } 117 } 118 119 @Override 120 public boolean prefire() throws IllegalActionException { 121 if (!super.prefire()) { 122 return false; 123 } 124 Scanner scan = null; 125 FileInputStream fileInputStream = null; 126 DataInputStream dis = null; 127 try { 128 scan = new Scanner(new FileInputStream(fileOrURL.asFile())); 129 fileInputStream = new FileInputStream(fileOrURL.asFile()); 130 dis = new DataInputStream(fileInputStream); 131 scan.next(); // the magic number: used to determine the PGM format. 132 _width = scan.nextInt(); 133 _height = scan.nextInt(); 134 135 // Skip header. 136 int lines = 0; 137 while (lines < 4) { 138 char c; 139 do { 140 c = (char) dis.readUnsignedByte(); 141 } while (c != '\n'); 142 lines++; 143 } 144 145 _grid = new int[_height * _width]; 146 147 for (int col = 0; col < _width; col++) { 148 for (int row = 0; row < _height; row++) { 149 int intVal = dis.readUnsignedByte(); 150 if (!_quantize) { 151 _grid[row * _width + col] = intVal; 152 } else { 153 int index = 0; 154 for (int i = 0; i < _levels.length; i++) { 155 if (i == 0) { 156 if (intVal < _levels[i]) { 157 index = 0; 158 } else { 159 continue; 160 } 161 } else if (intVal > _levels[i - 1] 162 && intVal <= _levels[i]) { 163 index = i; 164 } else if (intVal > _levels[i] 165 && i >= _levels.length - 1) { 166 index = i; 167 } 168 } 169 _grid[row + col * _height] = _levels[index]; 170 171 } 172 } 173 } 174 } catch (IOException e) { 175 throw new IllegalActionException(this, e, 176 "Failed to read " + fileOrURL); 177 } finally { 178 try { 179 if (dis != null) { 180 dis.close(); 181 } 182 } catch (IOException ex) { 183 throw new IllegalActionException(this, ex, 184 "Failed to close data input stream " + fileOrURL); 185 } 186 try { 187 if (fileInputStream != null) { 188 fileInputStream.close(); 189 } 190 } catch (IOException ex) { 191 throw new IllegalActionException(this, ex, 192 "Failed to close file input stream " + fileOrURL); 193 } 194 try { 195 if (scan != null) { 196 scan.close(); 197 } 198 } catch (Throwable ex) { 199 throw new IllegalActionException(this, ex, 200 "Failed to close scanner " + fileOrURL); 201 } 202 } 203 204 return super.prefire(); 205 } 206 207 /** Output the data read in the prefire. 208 * @exception IllegalActionException If there's no director. 209 */ 210 @Override 211 public void fire() throws IllegalActionException { 212 super.fire(); 213 // convert grid to array token 214 Token[] grid = new IntToken[_grid.length]; 215 for (int i = 0; i < _grid.length; i++) { 216 grid[i] = new IntToken(_grid[i]); 217 } 218 String[] labels = { "width", "height", "grid" }; 219 Token[] tokens = { new IntToken(_width), new IntToken(_height), 220 new ArrayToken(grid) }; 221 RecordToken outputToken = new RecordToken(labels, tokens); 222 output.broadcast(outputToken); 223 } 224 225 /** Image that is read in. */ 226 private int[] _grid; 227 228 /** Image width. */ 229 private int _width; 230 /** Image height. */ 231 private int _height; 232 233 private int[] _levels; 234 235 private boolean _quantize; 236 237}