001/*
002 * Copyright (C) Photon Vision.
003 *
004 * This program is free software: you can redistribute it and/or modify
005 * it under the terms of the GNU General Public License as published by
006 * the Free Software Foundation, either version 3 of the License, or
007 * (at your option) any later version.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU General Public License for more details.
013 *
014 * You should have received a copy of the GNU General Public License
015 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
016 */
017
018package org.photonvision.vision.frame.provider;
019
020import java.nio.file.Files;
021import java.nio.file.Path;
022import java.nio.file.Paths;
023import org.opencv.core.Mat;
024import org.opencv.imgcodecs.Imgcodecs;
025import org.photonvision.common.util.math.MathUtils;
026import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
027import org.photonvision.vision.frame.FrameProvider;
028import org.photonvision.vision.frame.FrameStaticProperties;
029import org.photonvision.vision.opencv.CVMat;
030import org.photonvision.vision.opencv.Releasable;
031
032/**
033 * A {@link FrameProvider} that will read and provide an image from a {@link java.nio.file.Path
034 * path}.
035 */
036public class FileFrameProvider extends CpuImageProcessor implements Releasable {
037    public static final int MAX_FPS = 10;
038    private static int count = 0;
039
040    private final int thisIndex = count++;
041    private final Path path;
042    private final int millisDelay;
043    private final CVMat originalFrame;
044
045    private final FrameStaticProperties properties;
046
047    private long lastGetMillis = System.currentTimeMillis();
048
049    /**
050     * Instantiates a new FileFrameProvider.
051     *
052     * @param path The path of the image to read from.
053     * @param fov The fov of the image.
054     * @param maxFPS The max framerate to provide the image at.
055     */
056    public FileFrameProvider(Path path, double fov, int maxFPS) {
057        this(path, fov, maxFPS, null);
058    }
059
060    public FileFrameProvider(Path path, double fov, CameraCalibrationCoefficients calibration) {
061        this(path, fov, MAX_FPS, calibration);
062    }
063
064    public FileFrameProvider(
065            Path path, double fov, int maxFPS, CameraCalibrationCoefficients calibration) {
066        if (!Files.exists(path))
067            throw new RuntimeException("Invalid path for image: " + path.toAbsolutePath());
068        this.path = path;
069        this.millisDelay = 1000 / maxFPS;
070
071        Mat rawImage = Imgcodecs.imread(path.toString());
072        if (rawImage.cols() > 0 && rawImage.rows() > 0) {
073            properties = new FrameStaticProperties(rawImage.width(), rawImage.height(), fov, calibration);
074            originalFrame = new CVMat(rawImage);
075        } else {
076            throw new RuntimeException("Image loading failed!");
077        }
078    }
079
080    /**
081     * Instantiates a new File frame provider.
082     *
083     * @param pathAsString The path of the image to read from as a string.
084     * @param fov The fov of the image.
085     */
086    public FileFrameProvider(String pathAsString, double fov) {
087        this(Paths.get(pathAsString), fov, MAX_FPS);
088    }
089
090    /**
091     * Instantiates a new File frame provider.
092     *
093     * @param path The path of the image to read from.
094     * @param fov The fov of the image.
095     */
096    public FileFrameProvider(Path path, double fov) {
097        this(path, fov, MAX_FPS);
098    }
099
100    @Override
101    public CapturedFrame getInputMat() {
102        var out = new CVMat();
103        out.copyFrom(originalFrame);
104
105        // block to keep FPS at a defined rate
106        if (System.currentTimeMillis() - lastGetMillis < millisDelay) {
107            try {
108                Thread.sleep(millisDelay);
109            } catch (InterruptedException e) {
110                System.err.println("FileFrameProvider interrupted - not busywaiting");
111                // throw back up the stack
112                throw new RuntimeException(e);
113            }
114        }
115
116        lastGetMillis = System.currentTimeMillis();
117        return new CapturedFrame(out, properties, MathUtils.wpiNanoTime());
118    }
119
120    @Override
121    public String getName() {
122        return "FileFrameProvider" + thisIndex + " - " + path.getFileName();
123    }
124
125    @Override
126    public void release() {
127        originalFrame.release();
128    }
129
130    @Override
131    public boolean checkCameraConnected() {
132        return true;
133    }
134
135    @Override
136    public boolean isConnected() {
137        return true;
138    }
139
140    @Override
141    public boolean hasConnected() {
142        return true;
143    }
144}