001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.image.model.asm.datasets;
031
032import java.io.BufferedReader;
033import java.io.IOException;
034import java.io.InputStream;
035import java.io.InputStreamReader;
036import java.util.List;
037
038import org.apache.commons.vfs2.FileObject;
039import org.apache.commons.vfs2.FileSystemManager;
040import org.apache.commons.vfs2.VFS;
041import org.openimaj.data.dataset.ListBackedDataset;
042import org.openimaj.data.dataset.VFSListDataset;
043import org.openimaj.image.Image;
044import org.openimaj.io.InputStreamObjectReader;
045import org.openimaj.io.ObjectReader;
046import org.openimaj.math.geometry.point.Point2dImpl;
047import org.openimaj.math.geometry.point.PointList;
048import org.openimaj.math.geometry.point.PointListConnections;
049import org.openimaj.util.pair.IndependentPair;
050
051/**
052 * Utilities for creating with {@link ShapeModelDataset} instances.
053 * 
054 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
055 * 
056 */
057public class ShapeModelDatasets
058{
059        /**
060         * Basic in memory dataset
061         * 
062         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
063         * 
064         * @param <IMAGE>
065         *            type of the images in the collection
066         */
067        private static class BasicDataset<IMAGE extends Image<?, IMAGE>>
068                        extends
069                        ListBackedDataset<IndependentPair<PointList, IMAGE>> implements ShapeModelDataset<IMAGE>
070        {
071                private PointListConnections connections;
072
073                public BasicDataset(List<IndependentPair<PointList, IMAGE>> data, PointListConnections connections) {
074                        this.data = data;
075                        this.connections = connections;
076                }
077
078                @Override
079                public PointListConnections getConnections() {
080                        return connections;
081                }
082
083                @Override
084                public List<PointList> getPointLists() {
085                        return IndependentPair.getFirst(this);
086                }
087
088                @Override
089                public List<IMAGE> getImages() {
090                        return IndependentPair.getSecond(this);
091                }
092        }
093
094        /**
095         * File-backed dataset
096         * 
097         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
098         * 
099         * @param <IMAGE>
100         *            type of the images in the collection
101         */
102        private abstract static class FileBackedDataset<IMAGE extends Image<?, IMAGE>>
103                        extends
104                        VFSListDataset<IndependentPair<PointList, IMAGE>> implements ShapeModelDataset<IMAGE>
105        {
106                protected PointListConnections connections;
107
108                public FileBackedDataset(String path, ObjectReader<IndependentPair<PointList, IMAGE>, FileObject> reader,
109                                PointListConnections conns)
110                                throws IOException
111                {
112                        super(path, reader);
113                        this.connections = conns;
114                }
115
116                @Override
117                public PointListConnections getConnections() {
118                        return connections;
119                }
120
121                @Override
122                public List<PointList> getPointLists() {
123                        return IndependentPair.getFirst(this);
124                }
125
126                @Override
127                public List<IMAGE> getImages() {
128                        return IndependentPair.getSecond(this);
129                }
130        }
131
132        private static class ASFDataset<IMAGE extends Image<?, IMAGE>> extends FileBackedDataset<IMAGE> {
133                private static class ASFReader<IMAGE extends Image<?, IMAGE>>
134                                implements
135                                ObjectReader<IndependentPair<PointList, IMAGE>, FileObject>
136                {
137                        private static String[] SUPPORTED_IMAGE_EXTS = { "jpg", "jpeg", "bmp", "png" };
138
139                        private InputStreamObjectReader<IMAGE> imReader;
140
141                        public ASFReader(InputStreamObjectReader<IMAGE> reader) {
142                                this.imReader = reader;
143                        }
144
145                        @Override
146                        public IndependentPair<PointList, IMAGE> read(FileObject source) throws IOException {
147                                final PointList pl = new PointList();
148                                BufferedReader br = null;
149
150                                try {
151                                        br = new BufferedReader(new InputStreamReader(source.getContent().getInputStream()));
152
153                                        String line;
154                                        while ((line = br.readLine()) != null) {
155                                                if (!line.startsWith("#")) {
156                                                        final String[] parts = line.split("\\s+");
157
158                                                        if (parts.length < 7)
159                                                                continue;
160
161                                                        final float x = Float.parseFloat(parts[2].trim());
162                                                        final float y = Float.parseFloat(parts[3].trim());
163
164                                                        pl.points.add(new Point2dImpl(x, y));
165                                                }
166                                        }
167                                } finally {
168                                        if (br != null)
169                                                try {
170                                                        br.close();
171                                                } catch (final IOException e) {
172                                                        // ignore
173                                                }
174                                }
175
176                                IMAGE image = null;
177                                if (imReader != null) {
178                                        for (final String ext : SUPPORTED_IMAGE_EXTS) {
179                                                String name = source.getName().getBaseName();
180                                                name = name.substring(0, name.lastIndexOf(".") + 1) + ext;
181                                                final FileObject file = source.getParent().getChild(name);
182
183                                                if (file != null && file.exists()) {
184                                                        InputStream imstream = null;
185                                                        try {
186                                                                imstream = file.getContent().getInputStream();
187                                                                image = imReader.read(imstream);
188                                                                break;
189                                                        } catch (final IOException e) {
190                                                                // ignore
191                                                        } finally {
192                                                                if (imstream != null) {
193                                                                        try {
194                                                                                imstream.close();
195                                                                        } catch (final IOException e) {
196                                                                                // ignore
197                                                                        }
198                                                                }
199                                                        }
200                                                }
201                                        }
202                                }
203
204                                if (image != null)
205                                        pl.scaleXY(image.getWidth(), image.getHeight());
206
207                                return new IndependentPair<PointList, IMAGE>(pl, image);
208                        }
209
210                        @Override
211                        public boolean canRead(FileObject source, String name) {
212                                return name.endsWith(".asf");
213                        }
214                }
215
216                public ASFDataset(String path, InputStreamObjectReader<IMAGE> reader) throws IOException {
217                        super(path, new ASFReader<IMAGE>(reader), null);
218                        readConnections();
219                }
220
221                void readConnections() throws IOException
222                {
223                        connections = new PointListConnections();
224                        final FileObject firstASF = this.getFileObject(0);
225                        BufferedReader br = null;
226
227                        try {
228                                br = new BufferedReader(new InputStreamReader(firstASF.getContent().getInputStream()));
229
230                                String line;
231                                while ((line = br.readLine()) != null) {
232                                        if (!line.startsWith("#")) {
233                                                final String[] parts = line.split("\\s+");
234
235                                                if (parts.length < 7)
236                                                        continue;
237
238                                                final int from = Integer.parseInt(parts[4].trim());
239                                                final int to = Integer.parseInt(parts[6].trim());
240
241                                                connections.addConnection(from, to);
242                                        }
243                                }
244                        } finally {
245                                if (br != null) {
246                                        try {
247                                                br.close();
248                                        } catch (final IOException e) {
249                                                // ignore
250                                        }
251                                        ;
252                                }
253                        }
254                }
255        }
256
257        private static class PTSDataset<IMAGE extends Image<?, IMAGE>> extends FileBackedDataset<IMAGE> {
258                private static class PTSReader<IMAGE extends Image<?, IMAGE>>
259                                implements
260                                ObjectReader<IndependentPair<PointList, IMAGE>, FileObject>
261                {
262                        private static String[] SUPPORTED_IMAGE_EXTS = { "jpg", "jpeg", "bmp", "png" };
263
264                        private InputStreamObjectReader<IMAGE> imReader;
265                        private FileObject ptsPath;
266                        private FileObject imgsPath;
267
268                        public PTSReader(InputStreamObjectReader<IMAGE> imReader, String ptsPath, String imgsPath) throws IOException
269                        {
270                                this.imReader = imReader;
271
272                                final FileSystemManager fsManager = VFS.getManager();
273
274                                this.ptsPath = fsManager.resolveFile(ptsPath);
275                                this.imgsPath = fsManager.resolveFile(imgsPath);
276                        }
277
278                        @Override
279                        public IndependentPair<PointList, IMAGE> read(FileObject source) throws IOException {
280                                final PointList pl = new PointList();
281                                BufferedReader br = null;
282
283                                try {
284                                        br = new BufferedReader(new InputStreamReader(source.getContent().getInputStream()));
285                                        br.readLine();
286                                        br.readLine();
287                                        br.readLine();
288
289                                        String line;
290                                        while ((line = br.readLine()) != null) {
291                                                if (!line.startsWith("}") && line.trim().length() > 0) {
292                                                        final String[] parts = line.split("\\s+");
293
294                                                        final float x = Float.parseFloat(parts[0].trim());
295                                                        final float y = Float.parseFloat(parts[1].trim());
296
297                                                        pl.points.add(new Point2dImpl(x, y));
298                                                }
299                                        }
300                                } finally {
301                                        if (br != null)
302                                                try {
303                                                        br.close();
304                                                } catch (final IOException e) {
305                                                }
306                                }
307
308                                IMAGE image = null;
309                                if (this.imReader != null) {
310                                        final String relPath = ptsPath.getName().getRelativeName(source.getName());
311                                        for (final String ext : SUPPORTED_IMAGE_EXTS) {
312                                                final String imRelPath = relPath.substring(0, relPath.lastIndexOf(".") + 1) + ext;
313                                                final FileObject imgPath = imgsPath.resolveFile(imRelPath);
314
315                                                if (imgPath.exists()) {
316                                                        InputStream imstream = null;
317                                                        try {
318                                                                imstream = imgPath.getContent().getInputStream();
319                                                                image = imReader.read(imstream);
320                                                                break;
321                                                        } catch (final IOException e) {
322                                                                // ignore
323                                                        } finally {
324                                                                if (imstream != null) {
325                                                                        try {
326                                                                                imstream.close();
327                                                                        } catch (final IOException e) {
328                                                                                // ignore
329                                                                        }
330                                                                }
331                                                        }
332                                                        break;
333                                                }
334                                        }
335                                }
336
337                                return IndependentPair.pair(pl, image);
338                        }
339
340                        @Override
341                        public boolean canRead(FileObject source, String name) {
342                                return name.endsWith(".pts") && !name.equals("dummy.pts");
343                        }
344                }
345
346                public PTSDataset(String imgsPath, String ptsPath, String modelPath, InputStreamObjectReader<IMAGE> reader)
347                                throws IOException
348                {
349                        super(ptsPath, new PTSReader<IMAGE>(reader, ptsPath, imgsPath), null);
350                        readConnections(modelPath);
351                }
352
353                void readConnections(String path)
354                                throws IOException
355                {
356                        BufferedReader br = null;
357                        try {
358                                final FileSystemManager fsManager = VFS.getManager();
359
360                                br = new BufferedReader(new InputStreamReader(fsManager.resolveFile(path).getContent().getInputStream()));
361                                this.connections = new PointListConnections();
362
363                                String line;
364                                while ((line = br.readLine()) != null) {
365                                        if (!line.trim().startsWith("indices"))
366                                                continue;
367
368                                        final String[] data = line.trim().replace("indices(", "").replace(")", "").split(",");
369                                        final boolean isOpen = (br.readLine().contains("open_boundary"));
370
371                                        int prev = Integer.parseInt(data[0]);
372                                        for (int i = 1; i < data.length; i++) {
373                                                final int next = Integer.parseInt(data[i]);
374                                                connections.addConnection(prev, next);
375                                                prev = next;
376                                        }
377
378                                        if (!isOpen) {
379                                                connections.addConnection(Integer.parseInt(data[data.length - 1]), Integer.parseInt(data[0]));
380                                        }
381                                }
382                        } finally {
383                                try {
384                                        if (br != null)
385                                                br.close();
386                                } catch (final IOException e) {
387                                }
388                        }
389                }
390
391        }
392
393        private ShapeModelDatasets() {
394        }
395
396        /**
397         * Create a dataset with the given data.
398         * 
399         * @param data
400         *            the image-pointset pairs
401         * @param connections
402         *            the connections across the points
403         * @return the dataset
404         */
405        public static <IMAGE extends Image<?, IMAGE>> ShapeModelDataset<IMAGE> create(
406                        List<IndependentPair<PointList, IMAGE>> data, PointListConnections connections)
407        {
408                return new BasicDataset<IMAGE>(data, connections);
409        }
410
411        /**
412         * Load a dataset from ASF format files as used by the IMM dataset. If the
413         * images are present, they will also be loaded (images must have the same
414         * name as the corresponding ASF files, but with a different extension).
415         * 
416         * @see IMMFaceDatabase
417         * @see "http://commons.apache.org/proper/commons-vfs/filesystems.html"
418         * @param path
419         *            the file system path or uri. See the Apache Commons VFS2
420         *            documentation for all the details.
421         * @param reader
422         *            the reader with which to load the images
423         * 
424         * @return the dataset
425         * @throws IOException
426         *             if an error occurs
427         */
428        public static <IMAGE extends Image<?, IMAGE>> ShapeModelDataset<IMAGE> loadASFDataset(String path,
429                        InputStreamObjectReader<IMAGE> reader) throws IOException
430        {
431                return new ASFDataset<IMAGE>(path, reader);
432        }
433
434        /**
435         * Load a dataset from PTS format files as used by Tim Cootes's ASM/AAM
436         * tools. If the images are present, they will also be loaded (images must
437         * have the same name as the corresponding PTS files, but with a different
438         * extension).
439         * 
440         * @param ptsDirPath
441         *            the directory containing the pts files
442         * @param imgDirPath
443         *            the directory containing the images
444         * @param modelFilePath
445         *            the path to the model (connections) file
446         * 
447         * @see IMMFaceDatabase
448         * @see "http://commons.apache.org/proper/commons-vfs/filesystems.html"
449         * @param reader
450         *            the reader with which to load the images
451         * 
452         * @return the dataset
453         * @throws IOException
454         *             if an error occurs
455         */
456        public static <IMAGE extends Image<?, IMAGE>> ShapeModelDataset<IMAGE> loadPTSDataset(String ptsDirPath,
457                        String imgDirPath, String modelFilePath,
458                        InputStreamObjectReader<IMAGE> reader) throws IOException
459        {
460                return new PTSDataset<IMAGE>(imgDirPath, ptsDirPath, modelFilePath, reader);
461        }
462}