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}