Search code examples
javaimagememorydebianwebcam

Why does Java app crash after processing x number of files?


I'm only asking this as a last resort. I'm stumped.

I have written a small app which performs very simple image processing. It is made using JDK 1.8, written in Netbeans 8.0.1 and is running on Debian Linux.

The application captures a large number of individual frames at a certain framerate, set by the user, by calling the 'streamer' webcam program via a process builder. Once it has begun capturing, it begins to translate the frames into RGB values and checks whether or not any pixels are above a user defined threshold. If no pixels exceed this threshold, it simply deletes the frame. If any pixels do exceed it, it moves the frame to a different folder for user inspection. This all works fine. It keeps up with even relatively high framerates and selects appropriate frames as expected. However, when the number of frames processed reaches around 1500 (or fewer for lower framerates), the program is freezing. I've tried issuing the commands to streamer manually at the command line, and it seems perfectly capable of producing as many as required, so I have to assume the issue is with my coding. The images are only small (320x240). Am I somehow maxxing out the available memory (I am getting no errors, just freezing).

The purpose of this program is to detect cosmic ray impacts on a CMOS sensor, part of a friend's dissertation. If I can't get this working reliably, the poor kid's going to have to go through the footage manually!

The code is attached below. Apologies for the length of the code, but as all of it is fairly crucial, I didn't want to omit anything.

package cosmicraysiii;

import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;

public class CosmicRaysIII {

    public static int dark = 0;//Dark current determined by averaging
    public static int tol = 0;//Tolerance set by user
    public static int frames = 0;//Total number of frames set by user
    public static int runs = 0;
    public static int rate = 0;//Framerate set by user
    public static int time = 0;//Total time calculated
    public static String zeros = "";
    public static int ready = 0;

    public static void main(String[] args) throws IOException,     InterruptedException {

        //Get directory ID from user
        String id = JOptionPane.showInputDialog("Enter a folder name for detections (no ., in name):");

        //Get dark current
        Dark d = new Dark();
        dark = d.getCurrent();

        //Get tolerance from user, will be added to dark current.
        String t = JOptionPane.showInputDialog("Dark Current = " + dark + "/255\n"
            + "Enter a tolerance (integer values only).\n "
            + "This will be added to the dark current:");
        tol = Integer.parseInt(t) + dark;

        //Get number of frames from user
        String fs = JOptionPane.showInputDialog("Enter the total number of frames required (Mulitples of 500 only):");
        frames = Integer.parseInt(fs);
        runs = frames / 500;

        //Get framerate from user
        String r = JOptionPane.showInputDialog("Enter the framerate required:");
        rate = Integer.parseInt(r);

        //Determine duration
        time = (int) Math.round(frames / rate);

        //Provide summary for user and request permission to continue
        int secs = time % 60;
        int mins = (time - secs) / 60;
        int hrs = (mins - (mins % 60)) / 60;
        if (hrs >= 1) {
            mins = mins % 60;
        }
        String theMessage = "The following parameters have been set:\n"
                + "Tolerance (including dark current): " + tol + "\n"
                + "Frames: " + frames + "\n"
                + "Frame rate: " + rate + " fps\n"
                + "Total capture time: " + time + " sec\n"
                + "                  " + hrs + " h " + mins + " m " + secs + " s\n"
                + "\n"
                + "Would you like to proceed?";
        int result = JOptionPane.showConfirmDialog(null, theMessage, "Continue?", JOptionPane.OK_CANCEL_OPTION);

        if (result == 2) {
            System.exit(0);
        }

        //Create directory for data acquisition
        ProcessBuilder pb1 = new ProcessBuilder("mkdir", "data");
        pb1.start();

        //Establish array of filenames
        String[] filenames = new String[frames];

        //Fill filenames array with filenames
        //Taking into consideration that the filename 
        //will have a varying number of zeros appended
        //before the frame number, dependent on the 
        //order of the frame number
        for (int i = 0; i < frames; i++) {
             if (i < 10) {
                 zeros = "00000000";
             } else if (i >= 10 && i < 100) {
                 zeros = "0000000";
             } else if (i >= 100 && i < 1000) {
                 zeros = "000000";
             } else if (i >= 1000 && i < 10000) {
                 zeros = "00000";
             } else if (i >= 10000 && i < 100000) {
                 zeros = "0000";
             } else if (i >= 100000 && i < 1000000) {
                 zeros = "000";
             } else if (i >= 1000000 && i < 10000000) {
                 zeros = "00";
            } else if (i >= 10000000 && i < 100000000) {
                zeros = "0";
            } else {
                zeros = "";
            }
            filenames[i] = "./data/frame" + zeros + i + ".ppm";
        }

        //Begin data acquisition
        new Thread(new Runnable() {
            public void run() {
                try {

                    //Capture images
                    ProcessBuilder pb2 = new ProcessBuilder("streamer", "-t", Integer.toString(frames), "-r", Integer.toString(rate), "-p", "0", "-o", "./data/frame000000000.ppm");
                    Process p = pb2.start();
                    p.waitFor();
                    ready = 1;
                } catch (IOException | InterruptedException ex) {
                    Logger.getLogger(CosmicRaysIII.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }).start();

        //Sleep to allow some image capture to prevent thread disordering
        Thread.sleep(3000);

        //Check array size
        System.out.println("Array size: " + filenames.length);

        //Conduct image analysis
        new Thread(new Runnable() {
            public void run() {
                int done = 0;
                int donea = 0;
                while (ready == 0) {
                    for (int i = 0; i < frames; i++) {
                        File f = new File(filenames[i]);
                        if (f.exists() && !filenames[i].equals(""))     {//Check file still exists
                            try {
                                 //Perform analysis steps
                                 Analysis a = new Analysis();

                                //STEP 1: Convert file from P6 to P3
                                String newfile = a.convert(filenames[i], zeros, i);
                                //STEP 2: Read file
                                a.read(newfile, tol, i, id);
                                filenames[i] = "";
                                done++;

                            } catch (IOException ex) {
                                Logger.getLogger(CosmicRaysIII.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }
                        if (done > donea) {
                            System.out.println(done + " files processed");
                            donea = done;
                        }
                    }
                 }
             }
        }).start();

    }

}

Then the Analyse.java class is as follows:

package cosmicraysiii;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class Analysis {

    public String convert(String ofile, String zeros, int number) throws IOException {
        //Create new file name
        String nfile = "./proc/frame" + zeros + number + ".ppm";

        //Ensure process directory exists
        ProcessBuilder mkdir = new ProcessBuilder("mkdir", "proc");
        mkdir.start();

        //Convert file to P3 PPM (RGB format) and move to process folder
        ProcessBuilder convert = new ProcessBuilder("convert", ofile, "-compress", "none", nfile);
        convert.start();

        //Delete original file
        ProcessBuilder del = new ProcessBuilder("sudo", "rm", ofile);
        del.start();

        //Return new filename
        return nfile;
    }

    public void read(String filename, int tol, int ix, String id) throws     FileNotFoundException, IOException {

        int move = 0;

        //Make directory for hits
        ProcessBuilder mkdir = new ProcessBuilder("mkdir", "hits" + id);
        mkdir.start();

        //Open reader to read file
        File f = new File(filename);
        if (f.exists()) {
            BufferedReader br = new BufferedReader(new FileReader(filename));
            String line;

            //To eliminate header
            int x = 0;

            //Iterate through text to find abnormal pixels
            while ((line = br.readLine()) != null) {
                x++;
                String[] pixrgb = line.split("\\ ");

                //Iterate through pixels on each line
                for (int i = 0; i < pixrgb.length; i++) {
                    if (x >= 4) {//Eliminate header

                        //Check each pixel value
                         try {
                             int pixval = Integer.parseInt(pixrgb[i]);
                             if (pixval > tol) {
                                move = 1;
                                break;
                            }
                        } catch (NumberFormatException ne) {

                        }
                    }
                }

            }
            if (move == 1) {
                 //Move file to hits folder
                 ProcessBuilder pb3 = new ProcessBuilder("sudo", "cp", filename, "./hits" + id + "/detection" + ix + ".ppm");
                pb3.start();

                //Delete original file
                ProcessBuilder del = new ProcessBuilder("sudo", "rm", filename);
                del.start();
            }
        }
                //Delete original file
                ProcessBuilder del = new ProcessBuilder("sudo", "rm", filename);
                del.start();
    }
}

I appreciate this is quite a lengthly chunk of code to be posting. Really appreciate any help that can be given.

G


Solution

  • OK I have managed to solve this by completely overhauling the analysis process. Firstly, rather than converting the image into a P3 .ppm file, I now examine the pixels directly from the image using BufferedReader. Then, I stopped looping through the file list repeatedly. Instead, the loop which calls Analysis() just runs through the list of filenames once. If it encounters a file which does not yet exist, it does Thread.sleep(500) and then tries again. I have now successfully run a batch of 50,000 frames without incident, and there is now much less of a drain on memory thanks to the improved process. Just thought I should place this answer up here in case anyone comes across it. I may post code if anyone wants it.