//package edu.toronto.psi.vincent.util;

import java.io.*;
import java.util.*;
import java.awt.image.*;
import javax.imageio.*;
import javax.imageio.stream.*;

/**
 * Class for reading images from files to multidimensional arrays
 * and writing multidimensional arrays to image files.
 *
 * The supported image formats are those natively supported by Java, i.e. JPEG, GIF, PNG
 * 
 * <pre>
 * Copyright (C) 2005  Vincent Cheung (vincent@psi.toronto.edu, http://www.psi.toronto.edu/~vincent/)
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.</pre>
 * 
 * @author <a href="mailto:vincent@psi.toronto.edu">Vincent Cheung</a>
 * @version 1.5 11/23/05
 */
public class ImageReaderWriter {
	/** Images are assumed to have 3 bands (RGB) */
	private static final int NUMBANDS = 3;
	
	/**
	 * Reads an image from a file.
	 *
	 * The pixels are normalized between 0 and 1.
	 *
	 * @param filename the name of the image file
	 * @return a multidimensional array holding the separate RGB components normalized between 0 and 1
	 * @throws IOException
	 */
	public static double[][][] read(String filename) throws IOException {
		String filenameExtension = filename.substring(filename.lastIndexOf('.')+1);
		File fileImage = new File(filename);
		Iterator imageReaders = ImageIO.getImageReadersBySuffix(filenameExtension);

		ImageReader imageReader;

		if(imageReaders.hasNext())
			imageReader = (ImageReader)imageReaders.next();
		else
			throw new IOException("Unsupported image format");
			
		FileImageInputStream imageInputStream = new FileImageInputStream(fileImage);
		
		imageReader.setInput(imageInputStream);
		
		int width = imageReader.getWidth(0);
		int height = imageReader.getHeight(0);
		
		BufferedImage bufImage = imageReader.read(0);
		imageInputStream.close();
		
		WritableRaster wRaster = bufImage.getRaster();
		
		//int numBands = wRaster.getNumBands();

		double[][][] samples = new double[height][width][NUMBANDS];
		
		// get the samples and normalize to between 0 and 1
		for(int row = 0; row < height; row++)
			for(int col = 0; col < width; col++)
				for(int band = 0; band < NUMBANDS; band++)
					samples[row][col][band] = wRaster.getSample(col, row, band) / 255.0;
		
		return(samples);
	} // end read method

	/**
	 * Reads a video sequence from a set of image files.
	 *
	 * The pixels are normalized between 0 and 1.
	 *
	 * The filename of the images are formatted as:
	 *     prefix + frame# + postfix
	 *
	 * @param prefix the prefix of the name of the image file
	 * @param postfix the postfix of the name of the image file
	 * @param numFrames the number of frames to read
	 * @throws IOException
	 */
	public static double[][][][] read(String prefix, String postfix, int numFrames) throws IOException {
		double[][][][] samples = new double[numFrames][][][];
		
		for(int t = 0; t < samples.length; t++)
			samples[t] = read(prefix + t + postfix);
		
		return(samples);
	} // end read method
	
	/**
	 * Writes an image to a file.
	 *
	 * The image can be gray scale where samples[0][0].length == 1 instead of 3.
	 *
	 * The pixels must be normalized between 0 and 1.
	 *
	 * @param samples a multidimensional array holding the separate RGB components normalized between 0 and 1
	 * @param filename the name of the image file
	 * @throws IOException
	 */
	public static void write(double[][][] samples, String filename) throws IOException {
		String filenameExtension = filename.substring(filename.lastIndexOf('.')+1);
		File fileImage = new File(filename);

		// create the necessary directories
		if (fileImage.getParentFile() != null)
			fileImage.getParentFile().mkdirs();
		
		Iterator imageWriters = ImageIO.getImageWritersBySuffix(filenameExtension);
		
		ImageWriter imageWriter;
		
		if (imageWriters.hasNext())
			imageWriter = (ImageWriter)imageWriters.next();
		else
			throw new IOException("Unsupported image format");
		
		FileImageOutputStream imageOutputStream = new FileImageOutputStream(fileImage);
		imageWriter.setOutput(imageOutputStream);
		BufferedImage bufImage = new BufferedImage(samples[0].length, samples.length, BufferedImage.TYPE_INT_RGB);
		
		WritableRaster wRaster = bufImage.getRaster();
		
		if(samples[0][0].length == 3) {
			// recover the original 0 - 255 range of values
			for(int row = 0; row < samples.length; row++)
				for(int col = 0; col < samples[0].length; col++)
					for(int band = 0; band < NUMBANDS; band++)
						wRaster.setSample(col, row, band, (int)Math.round(samples[row][col][band] * 255));
		
		// gray scale
		} else {
			// recover the original 0 - 255 range of values
			for(int row = 0; row < samples.length; row++)
				for(int col = 0; col < samples[0].length; col++)
					for(int band = 0; band < NUMBANDS; band++)
						wRaster.setSample(col, row, band, (int)Math.round(samples[row][col][0] * 255));
		}
		
		imageWriter.write(bufImage);
		imageOutputStream.close();
	} // end write method

	/**
	 * Writes a gray scale image to a file.
	 *
	 * The pixels must be normalized between 0 and 1.
	 *
	 * @param samples a multidimensional array holding the gray values normalized between 0 and 1
	 * @param filename the name of the image file
	 * @throws IOException
	 */
	public static void write(double[][] samples, String filename) throws IOException {
		String filenameExtension = filename.substring(filename.lastIndexOf('.')+1);
		File fileImage = new File(filename);
		
		// create the necessary directories
		if (fileImage.getParentFile() != null)
			fileImage.getParentFile().mkdirs();
		
		Iterator imageWriters = ImageIO.getImageWritersBySuffix(filenameExtension);
		
		ImageWriter imageWriter;
		
		if (imageWriters.hasNext())
			imageWriter = (ImageWriter)imageWriters.next();
		else
			throw new IOException("Unsupported image format");
		
		FileImageOutputStream imageOutputStream = new FileImageOutputStream(fileImage);
		imageWriter.setOutput(imageOutputStream);
		BufferedImage bufImage = new BufferedImage(samples[0].length, samples.length, BufferedImage.TYPE_INT_RGB);
		
		WritableRaster wRaster = bufImage.getRaster();
		
		// recover the original 0 - 255 range of values
		for(int row = 0; row < samples.length; row++)
			for(int col = 0; col < samples[0].length; col++)
				for(int band = 0; band < NUMBANDS; band++)
					wRaster.setSample(col, row, band, (int)Math.round(samples[row][col] * 255));
		
		imageWriter.write(bufImage);
		imageOutputStream.close();
	} // end write method

	/**
	 * Writes a video sequence as a set of images.
	 *
	 * The video can be gray scale where samples[0][0][0].length == 1 instead of 3.
	 *
	 * The pixels must be normalized between 0 and 1.
	 *
	 * The filename of the images are formatted as:
	 *     prefix + frame# + postfix
	 *
	 * @param samples a multidimensional array holding the separate RGB components normalized between 0 and 1,
	 *                where the first dimension indexes the frame and the remaining dimensions represent the frame image
	 * @param prefix the prefix of the name of the image file
	 * @param postfix the postfix of the name of the image file
	 * @throws IOException
	 */
	public static void write(double[][][][] samples, String prefix, String postfix) throws IOException {

		for(int t = 0; t < samples.length; t++)
			write(samples[t], prefix + t + postfix);

	} // end write method

	
	/**
	 * Writes an image as a gray scale image by summing over the RGB values, then rescaling to use the full gray scale.
	 *
	 * @param samples a multidimensional array holding the separate RGB components normalized between 0 and 1
	 * @param filename the base filename of the image file
	 */
	public static void writeScaledGray(double[][][] samples, String filename) throws IOException {
		
		double[][] gray = new double[samples.length][samples[0].length];
		
		double maxVal = 0;
		double minVal = Double.MAX_VALUE;
		
		for(int i = 0; i < samples.length; i++) {
			for(int j = 0; j < samples[0].length; j++) {
				
				gray[i][j] = 0;
				
				for(int k = 0; k < samples[0][0].length; k++)
					gray[i][j] += samples[i][j][k];
				
				if(gray[i][j] > maxVal)
					maxVal = gray[i][j];
				
				if(gray[i][j] < minVal)
					minVal = gray[i][j];
			}
		}

		int index = filename.lastIndexOf('.');
		
		if(index == -1)
			throw new IOException("No image extension found");

		writeScaledGray(gray, maxVal, minVal, filename.substring(0, index) + "(" + (Math.round(minVal*1e4)/1e4) + "," + (Math.round(maxVal*1e4)/1e4) + ")" + filename.substring(index));
	}


	/**
	 * Writes an image as a gray scale image by summing over the RGB values, then rescaling to use the full gray scale
	 *
	 * @param gray a multidimensional array holding the summed RGB components
	 * @param filename the base filename of the image file
	 * @throws IOException
	 */
	public static void writeScaledGray(double[][] gray, double maxVal, double minVal, String filename) throws IOException {
		if(minVal == maxVal)
			if(minVal == 0)
				maxVal = 1;
			else
				minVal = 0;
	
		double range = maxVal - minVal;		
		
		for(int i = 0; i < gray.length; i++)
			for(int j = 0; j < gray[0].length; j++)
				gray[i][j] = (gray[i][j] - minVal) / range;

		write(gray, filename);
	}
	
	/**
	 * Writes a video as a set of gray scale images by summing over the RGB values, then rescaling to use the full gray scale.
	 *
	 * @param samples a multidimensional array holding the separate RGB components normalized between 0 and 1,
	 *                where the first dimension indexes the frame and the remaining dimensions represent the frame image
	 * @param prefix the prefix of the name of the image file
	 * @param postfix the postfix of the name of the image file
	 * @throws IOException
	 */
	public static void writeScaledGray(double[][][][] samples, String prefix, String postfix) throws IOException {
		double[][][] gray = new double[samples.length][samples[0].length][samples[0][0].length];
		
		double maxVal = 0;
		double minVal = Double.MAX_VALUE;
		
		for(int t = 0; t < samples.length; t++) {
			for(int i = 0; i < samples[0].length; i++) {
				for(int j = 0; j < samples[0][0].length; j++) {
					
					gray[t][i][j] = 0;
					
					for(int k = 0; k < samples[0][0][0].length; k++)
						gray[t][i][j] += samples[t][i][j][k];
					
					if(gray[t][i][j] > maxVal)
						maxVal = gray[t][i][j];
					
					if(gray[t][i][j] < minVal)
						minVal = gray[t][i][j];
				}
			}
		}

		for(int t = 0; t < gray.length; t++)
			writeScaledGray(gray[t], maxVal, minVal, prefix + t + "(" + (Math.round(minVal*1e4)/1e4) + "," + (Math.round(maxVal*1e4)/1e4) + ")" + postfix);
	}

	// --------------------------------------------------------------------------------------------------
	// int
	// --------------------------------------------------------------------------------------------------

	/**
	 * Reads an image from a file.
	 *
	 * @param filename the name of the image file
	 * @return a multidimensional array holding the separate RGB components with values between 0 and 255
	 * @throws IOException
	 */
	public static int[][][] readInt(String filename) throws IOException {
		String filenameExtension = filename.substring(filename.lastIndexOf('.')+1);
		File fileImage = new File(filename);
		Iterator imageReaders = ImageIO.getImageReadersBySuffix(filenameExtension);

		ImageReader imageReader;

		if(imageReaders.hasNext())
			imageReader = (ImageReader)imageReaders.next();
		else
			throw new IOException("Unsupported image format");
			
		FileImageInputStream imageInputStream = new FileImageInputStream(fileImage);
		
		imageReader.setInput(imageInputStream);
		
		int width = imageReader.getWidth(0);
		int height = imageReader.getHeight(0);
		
		BufferedImage bufImage = imageReader.read(0);
		imageInputStream.close();
		
		WritableRaster wRaster = bufImage.getRaster();
		
		//int numBands = wRaster.getNumBands();

		int[][][] samples = new int[height][width][NUMBANDS];
		
		// get the samples and normalize to between 0 and 1
		for(int row = 0; row < height; row++)
			for(int col = 0; col < width; col++)
				for(int band = 0; band < NUMBANDS; band++)
					samples[row][col][band] = wRaster.getSample(col, row, band);
		
		return(samples);
	} // end read method

	/**
	 * Reads a video sequence from a set of image files.
	 *
	 * The filename of the images are formatted as:
	 *     prefix + frame# + postfix
	 *
	 * @param prefix the prefix of the name of the image file
	 * @param postfix the postfix of the name of the image file
	 * @param numFrames the number of frames to read
	 * @throws IOException
	 */
	public static int[][][][] readInt(String prefix, String postfix, int numFrames) throws IOException {
		int[][][][] samples = new int[numFrames][][][];
		
		for(int t = 0; t < samples.length; t++)
			samples[t] = readInt(prefix + t + postfix);
		
		return(samples);
	} // end read method
	
	/**
	 * Writes an image to a file.
	 *
	 * The image can be gray scale where samples[0][0].length == 1 instead of 3.
	 *
	 * @param samples a multidimensional array holding the separate RGB components with values between 0 and 255
	 * @param filename the name of the image file
	 * @throws IOException
	 */
	public static void write(int[][][] samples, String filename) throws IOException {
		String filenameExtension = filename.substring(filename.lastIndexOf('.')+1);
		File fileImage = new File(filename);
		
		// create the necessary directories
		if (fileImage.getParentFile() != null)
			fileImage.getParentFile().mkdirs();
		
		Iterator imageWriters = ImageIO.getImageWritersBySuffix(filenameExtension);
		
		ImageWriter imageWriter;
		
		if (imageWriters.hasNext())
			imageWriter = (ImageWriter)imageWriters.next();
		else
			throw new IOException("Unsupported image format");
		
		FileImageOutputStream imageOutputStream = new FileImageOutputStream(fileImage);
		imageWriter.setOutput(imageOutputStream);
		BufferedImage bufImage = new BufferedImage(samples[0].length, samples.length, BufferedImage.TYPE_INT_RGB);
		
		WritableRaster wRaster = bufImage.getRaster();
		
		if(samples[0][0].length == 3) {
			// recover the original 0 - 255 range of values
			for(int row = 0; row < samples.length; row++)
				for(int col = 0; col < samples[0].length; col++)
					for(int band = 0; band < NUMBANDS; band++)
						wRaster.setSample(col, row, band, samples[row][col][band]);
		
		// gray scale
		} else {
			// recover the original 0 - 255 range of values
			for(int row = 0; row < samples.length; row++)
				for(int col = 0; col < samples[0].length; col++)
					for(int band = 0; band < NUMBANDS; band++)
						wRaster.setSample(col, row, band, samples[row][col][0]);
		}
		
		imageWriter.write(bufImage);
		imageOutputStream.close();
	} // end write method

	/**
	 * Writes a gray scale image to a file.
	 *
	 * @param samples a multidimensional array holding the gray values with values between 0 and 255
	 * @param filename the name of the image file
	 * @throws IOException
	 */
	public static void write(int[][] samples, String filename) throws IOException {
		String filenameExtension = filename.substring(filename.lastIndexOf('.')+1);
		File fileImage = new File(filename);
		
		// create the necessary directories
		if (fileImage.getParentFile() != null)
			fileImage.getParentFile().mkdirs();
		
		Iterator imageWriters = ImageIO.getImageWritersBySuffix(filenameExtension);
		
		ImageWriter imageWriter;
		
		if (imageWriters.hasNext())
			imageWriter = (ImageWriter)imageWriters.next();
		else
			throw new IOException("Unsupported image format");
		
		FileImageOutputStream imageOutputStream = new FileImageOutputStream(fileImage);
		imageWriter.setOutput(imageOutputStream);
		BufferedImage bufImage = new BufferedImage(samples[0].length, samples.length, BufferedImage.TYPE_INT_RGB);
		
		WritableRaster wRaster = bufImage.getRaster();
		
		// recover the original 0 - 255 range of values
		for(int row = 0; row < samples.length; row++)
			for(int col = 0; col < samples[0].length; col++)
				for(int band = 0; band < NUMBANDS; band++)
					wRaster.setSample(col, row, band, samples[row][col]);
		
		imageWriter.write(bufImage);
		imageOutputStream.close();
	} // end write method

	/**
	 * Writes a video sequence as a set of images.
	 *
	 * The video can be gray scale where samples[0][0][0].length == 1 instead of 3.
	 *
	 * The filename of the images are formatted as:
	 *     prefix + frame# + postfix
	 *
	 * @param samples a multidimensional array holding the separate RGB components with values between 0 and 255,
	 *                where the first dimension indexes the frame and the remaining dimensions represent the frame image
	 * @param prefix the prefix of the name of the image file
	 * @param postfix the postfix of the name of the image file
	 * @throws IOException
	 */
	public static void write(int[][][][] samples, String prefix, String postfix) throws IOException {

		for(int t = 0; t < samples.length; t++)
			write(samples[t], prefix + t + postfix);

	} // end write method

	
	/**
	 * Writes an image as a gray scale image by summing over the RGB values, then rescaling to use the full gray scale.
	 *
	 * @param samples a multidimensional array holding the separate RGB components with values between 0 and 255
	 * @param filename the base filename of the image file
	 * @throws IOException
	 */
	public static void writeScaledGray(int[][][] samples, String filename) throws IOException {
		
		double[][] gray = new double[samples.length][samples[0].length];
		
		double maxVal = 0;
		double minVal = Double.MAX_VALUE;
		
		for(int i = 0; i < samples.length; i++) {
			for(int j = 0; j < samples[0].length; j++) {
				
				gray[i][j] = 0;
				
				for(int k = 0; k < samples[0][0].length; k++)
					gray[i][j] += samples[i][j][k];
				
				// need to normalize between 0 and 1 to be compatible with the code for double arrays
				gray[i][j] /= 255;
				
				if(gray[i][j] > maxVal)
					maxVal = gray[i][j];
				
				if(gray[i][j] < minVal)
					minVal = gray[i][j];
			}
		}

		int index = filename.lastIndexOf('.');
		
		if(index == -1)
			throw new IOException("No image extension found");

		writeScaledGray(gray, maxVal, minVal, filename.substring(0, index) + "(" + (Math.round(minVal*1e4)/1e4) + "," + (Math.round(maxVal*1e4)/1e4) + ")" + filename.substring(index));
	}
	
	/**
	 * Writes a video as a set of gray scale images by summing over the RGB values, then rescaling to use the full gray scale.
	 *
	 * @param samples a multidimensional array holding the separate RGB components with values between 0 and 255,
	 *                where the first dimension indexes the frame and the remaining dimensions represent the frame image
	 * @param prefix the prefix of the name of the image file
	 * @param postfix the postfix of the name of the image file
	 * @throws IOException
	 */
	public static void writeScaledGray(int[][][][] samples, String prefix, String postfix) throws IOException {
		double[][][] gray = new double[samples.length][samples[0].length][samples[0][0].length];
		
		double maxVal = 0;
		double minVal = Double.MAX_VALUE;
		
		for(int t = 0; t < samples.length; t++) {
			for(int i = 0; i < samples[0].length; i++) {
				for(int j = 0; j < samples[0][0].length; j++) {
					
					gray[t][i][j] = 0;
					
					for(int k = 0; k < samples[0][0][0].length; k++)
						gray[t][i][j] += samples[t][i][j][k];
					
					// need to normalize between 0 and 1 to be compatible with the code for double arrays
					gray[t][i][j] /= 255;
					
					if(gray[t][i][j] > maxVal)
						maxVal = gray[t][i][j];
					
					if(gray[t][i][j] < minVal)
						minVal = gray[t][i][j];
				}
			}
		}

		for(int t = 0; t < gray.length; t++)
			writeScaledGray(gray[t], maxVal, minVal, prefix + t + "(" + (Math.round(minVal*1e4)/1e4) + "," + (Math.round(maxVal*1e4)/1e4) + ")" + postfix);
	}

	
	// --------------------------------------------------------------------------------------------------
	// float
	// --------------------------------------------------------------------------------------------------

	/**
	 * Reads an image from a file.
	 *
	 * The pixels are normalized between 0 and 1.
	 *
	 * @param filename the name of the image file
	 * @return a multidimensional array holding the separate RGB components normalized between 0 and 1
	 * @throws IOException
	 */
	public static float[][][] readFloat(String filename) throws IOException {
		String filenameExtension = filename.substring(filename.lastIndexOf('.')+1);
		File fileImage = new File(filename);
		
		// create the necessary directories
		if (fileImage.getParentFile() != null)
			fileImage.getParentFile().mkdirs();
		
		Iterator imageReaders = ImageIO.getImageReadersBySuffix(filenameExtension);

		ImageReader imageReader;

		if(imageReaders.hasNext())
			imageReader = (ImageReader)imageReaders.next();
		else
			throw new IOException("Unsupported image format");
			
		FileImageInputStream imageInputStream = new FileImageInputStream(fileImage);
		
		imageReader.setInput(imageInputStream);
		
		int width = imageReader.getWidth(0);
		int height = imageReader.getHeight(0);
		
		BufferedImage bufImage = imageReader.read(0);
		imageInputStream.close();
		
		WritableRaster wRaster = bufImage.getRaster();
		
		//int numBands = wRaster.getNumBands();

		float[][][] samples = new float[height][width][NUMBANDS];
		
		// get the samples and normalize to between 0 and 1
		for(int row = 0; row < height; row++)
			for(int col = 0; col < width; col++)
				for(int band = 0; band < NUMBANDS; band++)
					samples[row][col][band] = wRaster.getSample(col, row, band) / 255.0f;
		
		return(samples);
	} // end read method

	/**
	 * Reads a video sequence from a set of image files.
	 *
	 * The pixels are normalized between 0 and 1.
	 *
	 * The filename of the images are formatted as:
	 *     prefix + frame# + postfix
	 *
	 * @param prefix the prefix of the name of the image file
	 * @param postfix the postfix of the name of the image file
	 * @param numFrames the number of frames to read
	 * @throws IOException
	 */
	public static float[][][][] readFloat(String prefix, String postfix, int numFrames) throws IOException {
		float[][][][] samples = new float[numFrames][][][];
		
		for(int t = 0; t < samples.length; t++)
			samples[t] = readFloat(prefix + t + postfix);
		
		return(samples);
	} // end read method
	
	/**
	 * Writes an image to a file.
	 *
	 * The image can be gray scale where samples[0][0].length == 1 instead of 3.
	 *
	 * The pixels must be normalized between 0 and 1.
	 *
	 * @param samples a multidimensional array holding the separate RGB components normalized between 0 and 1
	 * @param filename the name of the image file
	 * @throws IOException
	 */
	public static void write(float[][][] samples, String filename) throws IOException {
		String filenameExtension = filename.substring(filename.lastIndexOf('.')+1);
		File fileImage = new File(filename);
		
		// create the necessary directories
		if (fileImage.getParentFile() != null)
			fileImage.getParentFile().mkdirs();

		Iterator imageWriters = ImageIO.getImageWritersBySuffix(filenameExtension);
		
		ImageWriter imageWriter;
		
		if (imageWriters.hasNext())
			imageWriter = (ImageWriter)imageWriters.next();
		else
			throw new IOException("Unsupported image format");
		
		FileImageOutputStream imageOutputStream = new FileImageOutputStream(fileImage);
		imageWriter.setOutput(imageOutputStream);
		BufferedImage bufImage = new BufferedImage(samples[0].length, samples.length, BufferedImage.TYPE_INT_RGB);
		
		WritableRaster wRaster = bufImage.getRaster();
		
		if(samples[0][0].length == 3) {
			// recover the original 0 - 255 range of values
			for(int row = 0; row < samples.length; row++)
				for(int col = 0; col < samples[0].length; col++)
					for(int band = 0; band < NUMBANDS; band++)
						wRaster.setSample(col, row, band, (int)Math.round(samples[row][col][band] * 255));
		
		// gray scale
		} else {
			// recover the original 0 - 255 range of values
			for(int row = 0; row < samples.length; row++)
				for(int col = 0; col < samples[0].length; col++)
					for(int band = 0; band < NUMBANDS; band++)
						wRaster.setSample(col, row, band, (int)Math.round(samples[row][col][0] * 255));
		}
		
		imageWriter.write(bufImage);
		imageOutputStream.close();
	} // end write method

	/**
	 * Writes a video sequence as a set of images.
	 *
	 * The video can be gray scale where samples[0][0][0].length == 1 instead of 3.
	 *
	 * The pixels must be normalized between 0 and 1.
	 *
	 * The filename of the images are formatted as:
	 *     prefix + frame# + postfix
	 *
	 * @param samples a multidimensional array holding the separate RGB components normalized between 0 and 1,
	 *                where the first dimension indexes the frame and the remaining dimensions represent the frame image
	 * @param prefix the prefix of the name of the image file
	 * @param postfix the postfix of the name of the image file
	 * @throws IOException
	 */
	public static void write(float[][][][] samples, String prefix, String postfix) throws IOException {

		for(int t = 0; t < samples.length; t++)
			write(samples[t], prefix + t + postfix);

	} // end write method

	/**
	 * Writes a gray scale image to a file.
	 *
	 * The pixels must be normalized between 0 and 1.
	 *
	 * @param samples a multidimensional array holding the gray values normalized between 0 and 1
	 * @param filename the name of the image file
	 * @throws IOException
	 */
	public static void write(float[][] samples, String filename) throws IOException {
		String filenameExtension = filename.substring(filename.lastIndexOf('.')+1);
		File fileImage = new File(filename);
		
		// create the necessary directories
		if (fileImage.getParentFile() != null)
			fileImage.getParentFile().mkdirs();
		
		Iterator imageWriters = ImageIO.getImageWritersBySuffix(filenameExtension);
		
		ImageWriter imageWriter;
		
		if (imageWriters.hasNext())
			imageWriter = (ImageWriter)imageWriters.next();
		else
			throw new IOException("Unsupported image format");
		
		FileImageOutputStream imageOutputStream = new FileImageOutputStream(fileImage);
		imageWriter.setOutput(imageOutputStream);
		BufferedImage bufImage = new BufferedImage(samples[0].length, samples.length, BufferedImage.TYPE_INT_RGB);
		
		WritableRaster wRaster = bufImage.getRaster();
		
		// recover the original 0 - 255 range of values
		for(int row = 0; row < samples.length; row++)
			for(int col = 0; col < samples[0].length; col++)
				for(int band = 0; band < NUMBANDS; band++)
					wRaster.setSample(col, row, band, (int)Math.round(samples[row][col] * 255));
		
		imageWriter.write(bufImage);
		imageOutputStream.close();
	} // end write method

	/**
	 * Writes an image as a gray scale image by summing over the RGB values, then rescaling to use the full gray scale.
	 *
	 * @param samples a multidimensional array holding the separate RGB components normalized between 0 and 1
	 * @param filename the base filename of the image file
	 */
	public static void writeScaledGray(float[][][] samples, String filename) throws IOException {
		
		double[][] gray = new double[samples.length][samples[0].length];
		
		double maxVal = 0;
		double minVal = Double.MAX_VALUE;
		
		for(int i = 0; i < samples.length; i++) {
			for(int j = 0; j < samples[0].length; j++) {
				
				gray[i][j] = 0;
				
				for(int k = 0; k < samples[0][0].length; k++)
					gray[i][j] += samples[i][j][k];
				
				if(gray[i][j] > maxVal)
					maxVal = gray[i][j];
				
				if(gray[i][j] < minVal)
					minVal = gray[i][j];
			}
		}

		int index = filename.lastIndexOf('.');
		
		if(index == -1)
			throw new IOException("No image extension found");

		writeScaledGray(gray, maxVal, minVal, filename.substring(0, index) + "(" + (Math.round(minVal*1e4)/1e4) + "," + (Math.round(maxVal*1e4)/1e4) + ")" + filename.substring(index));
	}
	
	/**
	 * Writes a video as a set of gray scale images by summing over the RGB values, then rescaling to use the full gray scale.
	 *
	 * @param samples a multidimensional array holding the separate RGB components normalized between 0 and 1,
	 *                where the first dimension indexes the frame and the remaining dimensions represent the frame image
	 * @param prefix the prefix of the name of the image file
	 * @param postfix the postfix of the name of the image file
	 * @throws IOException
	 */
	public static void writeScaledGray(float[][][][] samples, String prefix, String postfix) throws IOException {
		double[][][] gray = new double[samples.length][samples[0].length][samples[0][0].length];
		
		double maxVal = 0;
		double minVal = Double.MAX_VALUE;
		
		for(int t = 0; t < samples.length; t++) {
			for(int i = 0; i < samples[0].length; i++) {
				for(int j = 0; j < samples[0][0].length; j++) {
					
					gray[t][i][j] = 0;
					
					for(int k = 0; k < samples[0][0][0].length; k++)
						gray[t][i][j] += samples[t][i][j][k];
					
					if(gray[t][i][j] > maxVal)
						maxVal = gray[t][i][j];
					
					if(gray[t][i][j] < minVal)
						minVal = gray[t][i][j];
				}
			}
		}

		for(int t = 0; t < gray.length; t++)
			writeScaledGray(gray[t], maxVal, minVal, prefix + t + "(" + (Math.round(minVal*1e4)/1e4) + "," + (Math.round(maxVal*1e4)/1e4) + ")" + postfix);
	}


	// --------------------------------------------------------------------------------------------------
	// boolean
	// --------------------------------------------------------------------------------------------------

	/**
	 * Writes an image to a file.
	 *
	 * @param samples a multidimensional array holding the separate RGB components normalized between 0 and 1
	 * @param filename the name of the image file
	 * @throws IOException
	 */
	public static void write(boolean[][] samples, String filename) throws IOException {
		String filenameExtension = filename.substring(filename.lastIndexOf('.')+1);
		File fileImage = new File(filename);
		
		// create the necessary directories
		if (fileImage.getParentFile() != null)
			fileImage.getParentFile().mkdirs();
		
		Iterator imageWriters = ImageIO.getImageWritersBySuffix(filenameExtension);
		
		ImageWriter imageWriter;
		
		if (imageWriters.hasNext())
			imageWriter = (ImageWriter)imageWriters.next();
		else
			throw new IOException("Unsupported image format");
		
		FileImageOutputStream imageOutputStream = new FileImageOutputStream(fileImage);
		imageWriter.setOutput(imageOutputStream);
		BufferedImage bufImage = new BufferedImage(samples[0].length, samples.length, BufferedImage.TYPE_INT_RGB);
		
		WritableRaster wRaster = bufImage.getRaster();
		
		// recover the original 0 - 255 range of values
		for(int row = 0; row < samples.length; row++)
			for(int col = 0; col < samples[0].length; col++)
				for(int band = 0; band < NUMBANDS; band++)
					wRaster.setSample(col, row, band, samples[row][col] ? 255 : 0);
		
		imageWriter.write(bufImage);
		imageOutputStream.close();
	} // end write method

	/**
	 * Writes a video sequence as a set of images.
	 *
	 * The filename of the images are formatted as:
	 *     prefix + frame# + postfix
	 *
	 * @param samples a multidimensional array holding the separate RGB components normalized between 0 and 1,
	 *                where the first dimension indexes the frame and the remaining dimensions represent the frame image
	 * @param prefix the prefix of the name of the image file
	 * @param postfix the postfix of the name of the image file
	 * @throws IOException
	 */
	public static void write(boolean[][][] samples, String prefix, String postfix) throws IOException {

		for(int t = 0; t < samples.length; t++)
			write(samples[t], prefix + t + postfix);

	} // end write method
	
} // end ImageReaderWriter class
