/**
 **
 ** ShowDoubleFloatArrays.java - Display utilities for double and float arrays as ImageJ images
 **
 ** Copyright (C) 2014-2026 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  ShowDoubleFloatArrays.java 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 3 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.
 **
 **  You should have received a copy of the GNU General Public License
 **  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ** -----------------------------------------------------------------------------**
 **
 */
package com.elphel.imagej.common;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import com.elphel.imagej.tileprocessor.QuadCLTCPU;

import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.gui.GenericDialog;
import ij.gui.Roi;
import ij.process.ColorProcessor;
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;

  public class ShowDoubleFloatArrays {
	  // defaults for color conversion
	  public int sliceRminusG=1;
	  public int sliceBminusG=2;
	  public double colorSpan=-.25;
	  public double brightnessModulate=1.0;
	  public double Kr=0.299;   // 0.299;
	  public double Kb=0.114;   // 0.114;
	  public double brightness=0.5;



/* For square arrays */

  public static void showArrays(double[][] pixels,  String title) { showArrays(pixels, false, title);}
  public static void showArrays(double[][] pixels,  boolean asStack, String title) {
    int width=0;
    int i;
    if (pixels==null) return;
    for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
      width= (int) Math.sqrt(pixels[i].length);
      break;
    }
    showArrays(pixels, width, width, asStack, title);
  }

  public static void showArrays(float[][] pixels,  String title) { showArrays(pixels, false, title);}
  public static void showArrays(float[][] pixels,  boolean asStack, String title) {
    int width=0;
    int i;
    if (pixels==null) return;
    for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
      width= (int) Math.sqrt(pixels[i].length);
      break;
    }
    showArrays(pixels, width, width, asStack, title);
  }


  public static void showArrays(double[] pixels,  String title) {
   int width;
    if (pixels!=null) {
      width=(int) Math.sqrt(pixels.length);
      showArrays(pixels,  width,width, title);
    }
  }

  public static void showArrays(float[] pixels,  String title) {
   int width;
    if (pixels!=null) {
      width=(int) Math.sqrt(pixels.length);
      showArrays(pixels,  width,width, title);
    }
  }


  public static void showArrays(double[][] pixels, int width, int height,  boolean asStack, String title) {
    int i,j;
    if (asStack) {
      float [] fpixels;
      ImageStack array_stack=new ImageStack(width,height);
      for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
          if (pixels[i].length!=(width*height)){
        	  System.out.println("showArrays(): pixels["+i+"].length="+pixels[i].length+" != width (+"+width+") * height("+height+")="+(width*height));
        	  return;
          }
        fpixels=new float[pixels[i].length];
        for (j=0;j<fpixels.length;j++) fpixels[j]=(float)pixels[i][j];
        array_stack.addSlice("chn-"+i,    fpixels);
        if (pixels[i].length!=(width*height)){

        }
      }
      ImagePlus imp_stack = new ImagePlus(title, array_stack); // stack is empty
      imp_stack.getProcessor().resetMinAndMax();
      imp_stack.show();
      return;
    } else showArrays(pixels, width, height, title);
  }
  
  
  public static void showArrays(double[][] pixels, int width, int height,  boolean asStack, String title, String [] titles) {
	    int i,j;
	    if (pixels==null) {
	    	System.out.println("showDoubleFloatArrays.showArrays(): - pixel array is null");
	    }
	    if (asStack) {
	      float [] fpixels;
	      ImageStack array_stack=new ImageStack(width,height);
	      boolean not_empty = false;
	      for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
	    	  not_empty = true;
	    	  fpixels=new float[pixels[i].length];
	    	  for (j=0;j<fpixels.length;j++) fpixels[j]=(float)pixels[i][j];
	    	  if (i < titles.length) {
	    		  array_stack.addSlice(titles[i], fpixels);
	    	  } else {
	    		  array_stack.addSlice("slice-"+i, fpixels);
	    	  }
	      }
	      if (not_empty) {
	    	  ImagePlus imp_stack = new ImagePlus(title, array_stack);
	    	  imp_stack.getProcessor().resetMinAndMax();
	    	  imp_stack.show();
	      }
	      return;
	    } else showArrays(pixels, width, height, titles);
	  }
  
  public static ImagePlus showArraysHyperstack(double[][] pixels, int width, int height,  int slices, String title, String [] titles, boolean show) {
	  int i,j;
	  if (pixels==null) {
		  System.out.println("showDoubleFloatArrays.showArrays(): - pixel array is null");
	  }
	  float [] fpixels;
	  ImageStack array_stack=new ImageStack(width,height);
	  boolean not_empty = false;
	  for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
		  not_empty = true;
		  fpixels=new float[pixels[i].length];
		  for (j=0;j<fpixels.length;j++) fpixels[j]=(float)pixels[i][j];
		  if (i < titles.length) {
			  array_stack.addSlice(titles[i], fpixels);
		  } else {
			  array_stack.addSlice("slice-"+i, fpixels);
		  }
	  }
	  ImagePlus imp_stack = null;
	  if (not_empty) {
		  imp_stack = IJ.createImage(
				  title,                         // String title,
				  "32-bit,grayscale-mode", // ,label", // String type,
				  width,                         // int width,
				  height,                        // int height,
				  1,                             // int channels,
				  slices,                        // int slices,
				  pixels.length / slices);       //  frames) 
		  imp_stack.setStack(array_stack);
		  imp_stack.getProcessor().resetMinAndMax();
		  if (show) {
			  imp_stack.show();
		  }
	  }
	  return imp_stack;
  }

  public static ImagePlus showArraysHyperstack(
		  double[][][] pixels,
		  int          width,
		  String       title,
		  String []    titles, // all slices*frames titles or just slice titles or null
		  String []    frame_titles, // frame titles or null
		  boolean      show) {
	  int num_frames =  pixels.length;
	  int num_slices =  0; // pixels[0].length;
	  for (int i=0; i < num_frames; i++) {
		  if (pixels[i] != null) {
			  num_slices = pixels[i].length;
			  break;
		  }
	  }
	  if (num_slices == 0) {
		  return null;
	  }
	  
	  double [][] dpixels = new double [num_frames*num_slices][];
	  for (int f = 0; f < num_frames; f++) {
		  for (int s = 0; s < num_slices; s ++) {
			  int indx = s + f * num_slices;
			  dpixels[indx] = (pixels[f] != null) ? pixels[f][s] : null;
		  }
	  }
	  String [] combo_titles;
	  if ((titles != null) && (titles.length == dpixels.length)) {
		  combo_titles = titles;
	  } else {
		  combo_titles = new String[dpixels.length];
		  if (titles == null) {
			  titles = new String [pixels[0].length];
			  for (int i = 0; i < titles.length; i++) {
				  titles[i] = ""+i;
			  }
		  }
		  if (frame_titles == null) {
			  frame_titles = new String [pixels.length];
			  for (int i = 0; i < titles.length; i++) {
				  frame_titles[i] = ""+i;
			  }
		  }
		  for (int f = 0; f < frame_titles.length; f++) {
			  for (int s = 0; s < titles.length; s++) {
				  combo_titles[s + f * titles.length] = frame_titles[f]+":"+titles[s];
			  }
		  }
	  }
	  int height = -1;
	  for (int slic = 0; slic < dpixels.length; slic++) {
		  if (dpixels[slic] != null) {
			  height = dpixels[slic].length/width;
			  break;
		  }
	  }
		  
	  return  showArraysHyperstack(
			  dpixels,                   // double[][] pixels,
			  width,                     // int width,
			  height,                    // int height,
			  num_slices,                // int slices,
			  title,                     // String title,
			  combo_titles,              // String [] titles);
			  show);                     // boolean show
  }
  
  public static ImagePlus showArraysHyperstack(
		  float[][][] pixels,
		  int          width,
		  String       title,
		  String []    titles, // all slices*frames titles or just slice titles or null
		  String []    frame_titles, // frame titles or null
		  boolean      show) {
	  int num_frames =  pixels.length;
	  int num_slices =  pixels[0].length;
	  
	  float [][] fpixels = new float [num_frames*num_slices][];
	  for (int f = 0; f < num_frames; f++) {
		  for (int s = 0; s < num_slices; s ++) {
			  int indx = s + f * num_slices;
			  fpixels[indx] = pixels[f][s];
		  }
	  }
	  String [] combo_titles;
	  if ((titles != null) && (titles.length == fpixels.length)) {
		  combo_titles = titles;
	  } else {
		  combo_titles = new String[fpixels.length];
		  if (titles == null) {
			  titles = new String [pixels[0].length];
			  for (int i = 0; i < titles.length; i++) {
				  titles[i] = ""+i;
			  }
		  }
		  if (frame_titles == null) {
			  frame_titles = new String [pixels.length];
			  for (int i = 0; i < titles.length; i++) {
				  frame_titles[i] = ""+i;
			  }
		  }
		  for (int f = 0; f < frame_titles.length; f++) {
			  for (int s = 0; s < titles.length; s++) {
				  combo_titles[s + f * titles.length] = frame_titles[f]+":"+titles[s];
			  }
		  }
	  }
	  int height = -1;
	  for (int slic = 0; slic < fpixels.length; slic++) {
		  if (fpixels[slic] != null) {
			  height = fpixels[slic].length/width;
			  break;
		  }
	  }
		  
	  return  showArraysHyperstack(
			  fpixels,                   // double[][] pixels,
			  width,                     // int width,
			  height,                    // int height,
			  pixels[0].length,          // int slices,
			  title,                     // String title,
			  combo_titles,              // String [] titles);
			  show);                     // boolean show
  }
  
  public static ImagePlus showArraysHyperstack(
		  float[][] pixels,
		  int width,
		  int height,
		  int slices,
		  String title,
		  String [] titles,
		  boolean show) {
	  int i,j;
	  if (pixels==null) {
		  System.out.println("showDoubleFloatArrays.showArrays(): - pixel array is null");
	  }
	  float [] fpixels;
	  ImageStack array_stack=new ImageStack(width,height);
	  boolean not_empty = false;
	  for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
		  not_empty = true;
		  fpixels=new float[pixels[i].length];
		  for (j=0;j<fpixels.length;j++) fpixels[j]=(float)pixels[i][j];
		  if (i < titles.length) {
			  array_stack.addSlice(titles[i], fpixels);
		  } else {
			  array_stack.addSlice("slice-"+i, fpixels);
		  }
	  }
	  ImagePlus imp_stack = null;
	  if (not_empty) {
		  imp_stack = IJ.createImage(
				  title,                         // String title,
				  "32-bit,grayscale-mode", // ,label", // String type,
				  width,                         // int width,
				  height,                        // int height,
				  1,                             // int channels,
				  slices,                        // int slices,
				  pixels.length / slices);       //  frames) 
		  imp_stack.setStack(array_stack);
		  imp_stack.getProcessor().resetMinAndMax();
		  if (show) {
			  imp_stack.show();
		  }
	  }
	  return imp_stack;
  }
  
  
  
  
  
  
  public static void showComplex(double[][][] cpixels, String title) {
	  int height = cpixels.length;
	  int width =  cpixels[0].length;
	  double [][]pixels = new double [height*width][];
	  int indx = 0;
	  for (int y = 0; y < height; y++) {
		  for (int x = 0; x < width; x++) {
			  pixels[indx++] = cpixels[y][x];
		  }
	  }
	  showComplex(pixels, width, title);
  }
  public static void showComplex(double[][] cpixels, int width, String title) {
	  int height = cpixels.length/width;
	  double [][]pixels = new double [2][cpixels.length];
	  for (int i = 0; i< cpixels.length; i++) {
		  pixels[0][i]= cpixels[i][0];
		  pixels[1][i]= cpixels[i][1];
	  }
	  String [] titles = {"Re", "Im"};
	  showArrays(pixels, width, height,  true, title, titles);
  }

  public static void showArrays(float[][] pixels, int width, int height,  boolean asStack, String title, String [] titles) {
	    int i,j;
	    if (pixels == null) {
	    	return;
	    }
	    if (asStack) {
	      float [] fpixels;
	      ImageStack array_stack=new ImageStack(width,height);
	      for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
	        fpixels=new float[pixels[i].length];
	        for (j=0;j<fpixels.length;j++) fpixels[j]=pixels[i][j];
	        array_stack.addSlice(titles[i], fpixels);
	      }
	      ImagePlus imp_stack = new ImagePlus(title, array_stack);
	      imp_stack.getProcessor().resetMinAndMax();
	      imp_stack.show();
	      return;
	    } else showArrays(pixels, width, height, titles);
	  }
  public static void showArraysSparse(double[][] pixels, int width, int height,  boolean asStack, String title, String [] titles) {
	  int i,j;
	  float [] fpixels;
	  if (asStack) {
		  ImageStack array_stack=new ImageStack(width,height);
		  for (i=0;i<titles.length;i++) {
			  fpixels=new float[pixels.length];
			  for (j=0;j<fpixels.length;j++) fpixels[j]= (pixels[j]==null)?0.0f: ((float)pixels[j][i]);
			  array_stack.addSlice(titles[i], fpixels);
		  }
		  ImagePlus imp_stack = new ImagePlus(title, array_stack);
		  imp_stack.getProcessor().resetMinAndMax();
		  imp_stack.show();
	  } else {
		  ImagePlus[]      imp= new ImagePlus[titles.length];
		  ImageProcessor[] ip= new ImageProcessor[titles.length];
		  for (i=0;i<titles.length;i++) {
			  fpixels=new float[pixels.length];
			  for (j=0;j<fpixels.length;j++) fpixels[j]= (pixels[j]==null)?0.0f: ((float)pixels[j][i]);
			  ip[i]=new FloatProcessor(width,height);
			  ip[i].setPixels(fpixels);
			  ip[i].resetMinAndMax();
			  imp[i]=  new ImagePlus(title+"_"+i, ip[i]);
			  imp[i].show();
		  }
	  }
  }

  public static void showArraysSparse(float[][] pixels, int width, int height,  boolean asStack, String title, String [] titles) {
	  int i,j;
	  float [] fpixels;
	  if (asStack) {
		  ImageStack array_stack=new ImageStack(width,height);
		  for (i=0;i<titles.length;i++) {
			  fpixels=new float[pixels.length];
			  for (j=0;j<fpixels.length;j++) fpixels[j]= (pixels[j]==null)?0.0f: ((float)pixels[j][i]);
			  array_stack.addSlice(titles[i], fpixels);
		  }
		  ImagePlus imp_stack = new ImagePlus(title, array_stack);
		  imp_stack.getProcessor().resetMinAndMax();
		  imp_stack.show();
	  } else {
		  ImagePlus[]      imp= new ImagePlus[titles.length];
		  ImageProcessor[] ip= new ImageProcessor[titles.length];
		  for (i=0;i<titles.length;i++) {
			  fpixels=new float[pixels.length];
			  for (j=0;j<fpixels.length;j++) fpixels[j]= (pixels[j]==null)?0.0f: ((float)pixels[j][i]);
			  ip[i]=new FloatProcessor(width,height);
			  ip[i].setPixels(fpixels);
			  ip[i].resetMinAndMax();
			  imp[i]=  new ImagePlus(title+"_"+i, ip[i]);
			  imp[i].show();
		  }
	  }
  }
  public static ImageStack makeStack(double[][] pixels, int width, int height) {
	  return makeStack(pixels, width,height, null);
  }

  public static ImageStack makeStack(double[][] pixels, int width, int height, String [] titles) {
	  return makeStack( pixels, width, height, titles, false);
  }
  public static ImageStack makeStack(double[][] pixels, int width, int height, String [] titles, boolean noNaN) {
      float [] fpixels;
      ImageStack array_stack=new ImageStack(width,height);
      for (int i=0;i<pixels.length;i++) if (pixels[i]!=null) {
          if (pixels[i].length!=(width*height)){
        	  System.out.println("showArrays(): pixels["+i+"].length="+pixels[i].length+" != width (+"+width+") * height("+height+")="+(width*height));
        	  return null;
          }
        fpixels=new float[pixels[i].length];
        if (noNaN){
        	for (int j=0;j<fpixels.length;j++) fpixels[j]= Double.isNaN(pixels[i][j])? 0.0F: ((float)pixels[i][j]);
        } else {
        	for (int j=0;j<fpixels.length;j++) fpixels[j]=(float)pixels[i][j];
        }
        if (titles!=null){
            array_stack.addSlice(titles[i],    fpixels);
        } else {
        	array_stack.addSlice("chn-"+i,    fpixels);
        }
      }
      return array_stack;
  }

  public static ImageStack makeStack(float[][] pixels, int width, int height, String [] titles, boolean noNaN) {
      float [] fpixels;
      ImageStack array_stack=new ImageStack(width,height);
      for (int i=0;i<pixels.length;i++) if (pixels[i]!=null) {
          if (pixels[i].length!=(width*height)){
        	  System.out.println("showArrays(): pixels["+i+"].length="+pixels[i].length+" != width (+"+width+") * height("+height+")="+(width*height));
        	  return null;
          }
        if (noNaN){
            fpixels=new float[pixels[i].length];
        	for (int j=0;j<fpixels.length;j++) fpixels[j]= Float.isNaN(pixels[i][j])? 0.0F: ((float)pixels[i][j]);
        } else {
        	fpixels=pixels[i];
        }
        if (titles!=null){
            array_stack.addSlice(titles[i],    fpixels);
        } else {
        	array_stack.addSlice("chn-"+i,    fpixels);
        }
      }
      return array_stack;
  }

  public static ImageStack makeStack(byte[][] pixels, int width, int height, String [] titles) {
      ImageStack array_stack=new ImageStack(width,height);
      for (int i=0;i<pixels.length;i++) if (pixels[i]!=null) {
          if (pixels[i].length!=(width*height)){
        	  System.out.println("showArrays(): pixels["+i+"].length="+pixels[i].length+" != width (+"+width+") * height("+height+")="+(width*height));
        	  return null;
          }
        if (titles!=null){
            array_stack.addSlice(titles[i],    pixels[i]);
        } else {
        	array_stack.addSlice("chn-"+i,    pixels[i]);
        }
      }
      return array_stack;
  }
  

  public static ImagePlus [] makeArrays(double[][] pixels, int width, int height, String title) {
	  int i,j;
	  float [] fpixels;
	  ImageProcessor[] ip= new ImageProcessor[pixels.length];
	  ImagePlus[]      imp=new ImagePlus[pixels.length];
	  for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
		  fpixels=new float[pixels[i].length];
		  for (j=0;j<fpixels.length;j++) fpixels[j]=(float)pixels[i][j];
		  ip[i]=new FloatProcessor(width,height);
		  ip[i].setPixels(fpixels);
		  ip[i].resetMinAndMax();
		  imp[i]=  new ImagePlus(title+"_"+i, ip[i]);
	  } else imp[i]=null;
	  return imp;
  }

  public static ImagePlus makeArrays(float[][] pixels, int width, int height,  String title, String [] titles) {
	  int i,j;
	  if (pixels==null) {
		  System.out.println("showDoubleFloatArrays.makeArrays(): - pixel array is null");
	  }
	  float [] fpixels;
	  ImageStack array_stack=new ImageStack(width,height);
	  boolean not_empty = false;
	  for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
		  not_empty = true;
		  fpixels=new float[pixels[i].length];
		  for (j=0;j<fpixels.length;j++) fpixels[j]=(float)pixels[i][j];
		  array_stack.addSlice(titles[i], fpixels);
	  }
	  if (not_empty) {
		  ImagePlus imp_stack = new ImagePlus(title, array_stack);
		  imp_stack.getProcessor().resetMinAndMax();
		  return imp_stack;
	  }
	  return null;
  }
  
  public static ImagePlus makeArrays(boolean[][] pixels, int width, int height,  String title, String [] titles) {
	  int i,j;
	  if (pixels==null) {
		  System.out.println("showDoubleFloatArrays.makeArrays(): - pixel array is null");
	  }
	  float [] fpixels;
	  ImageStack array_stack=new ImageStack(width,height);
	  boolean not_empty = false;
	  for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
		  not_empty = true;
		  fpixels=new float[pixels[i].length];
		  for (j=0;j<fpixels.length;j++) fpixels[j]=pixels[i][j]?1.0f:0.0f;
		  array_stack.addSlice(titles[i], fpixels);
	  }
	  if (not_empty) {
		  ImagePlus imp_stack = new ImagePlus(title, array_stack);
		  imp_stack.getProcessor().resetMinAndMax();
		  return imp_stack;
	  }
	  return null;
  }

  public static ImagePlus makeArrays(int[][] pixels, int width, int height,  String title, String [] titles) {
	  int i,j;
	  if (pixels==null) {
		  System.out.println("showDoubleFloatArrays.makeArrays(): - pixel array is null");
	  }
	  float [] fpixels;
	  ImageStack array_stack=new ImageStack(width,height);
	  boolean not_empty = false;
	  for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
		  not_empty = true;
		  fpixels=new float[pixels[i].length];
		  for (j=0;j<fpixels.length;j++) fpixels[j]=pixels[i][j];
		  array_stack.addSlice(titles[i], fpixels);
	  }
	  if (not_empty) {
		  ImagePlus imp_stack = new ImagePlus(title, array_stack);
		  imp_stack.getProcessor().resetMinAndMax();
		  return imp_stack;
	  }
	  return null;
  }
  
  
  public static ImagePlus makeArrays(double[][] pixels, int width, int height,  String title, String [] titles) {
	  int i,j;
	  if (pixels==null) {
		  System.out.println("showDoubleFloatArrays.makeArrays(): - pixel array is null");
	  }
	  float [] fpixels;
	  ImageStack array_stack=new ImageStack(width,height);
	  boolean not_empty = false;
	  for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
		  not_empty = true;
		  fpixels=new float[pixels[i].length];
		  for (j=0;j<fpixels.length;j++) fpixels[j]=(float)pixels[i][j];
		  String titlesi=(i < titles.length) ? titles[i] : ("st"+i); // when debugging, titles may to match pixels
		  array_stack.addSlice(titlesi, fpixels);
	  }
	  if (not_empty) {
		  ImagePlus imp_stack = new ImagePlus(title, array_stack);
		  imp_stack.getProcessor().resetMinAndMax();
//		  imp_stack.show();
		  return imp_stack;
	  }
	  return null;
  }

  public static ImagePlus [] makeArrays(double[][] pixels, int width, int height, String [] titles) {
	  int i,j;
	  float [] fpixels;
	  ImageProcessor[] ip= new ImageProcessor[pixels.length];
	  ImagePlus[]      imp=new ImagePlus[pixels.length];
	  for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
		  fpixels=new float[pixels[i].length];
		  for (j=0;j<fpixels.length;j++) fpixels[j]=(float)pixels[i][j];
		  ip[i]=new FloatProcessor(width,height);
		  ip[i].setPixels(fpixels);
		  ip[i].resetMinAndMax();
		  imp[i]=  new ImagePlus(titles[i], ip[i]);
	  } else imp[i]=null;
	  return imp;
  }
  public static ImagePlus [] makeArrays(float[][] pixels, int width, int height, String [] titles) {
	  int i,j;
	  float [] fpixels;
	  ImageProcessor[] ip= new ImageProcessor[pixels.length];
	  ImagePlus[]      imp=new ImagePlus[pixels.length];
	  for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
		  fpixels=new float[pixels[i].length];
		  for (j=0;j<fpixels.length;j++) fpixels[j]=pixels[i][j];
		  ip[i]=new FloatProcessor(width,height);
		  ip[i].setPixels(fpixels);
		  ip[i].resetMinAndMax();
		  imp[i]=  new ImagePlus(titles[i], ip[i]);
	  } else imp[i]=null;
	  return imp;
  }

  public static void showArrays(double[][] pixels, int width, int height, String title) {
	  int i;
	  ImagePlus[] imp=makeArrays(pixels, width, height, title);
	  if (imp==null) return;
	  for (i=0;i<imp.length;i++) if (imp[i]!=null) {
		  imp[i].show();
	  }
  }

  public static void showArrays(double[][] pixels, int width, int height, String [] titles) {
	  int i;
	  ImagePlus[] imp=makeArrays(pixels, width, height, titles);
	  if (imp==null) return;
	  for (i=0;i<imp.length;i++) if (imp[i]!=null) {
		  imp[i].show();
	  }
  }

  public static void showArrays(float[][] pixels, int width, int height, String [] titles) {
	  int i;
	  ImagePlus[] imp=makeArrays(pixels, width, height, titles);
	  if (imp==null) return;
	  for (i=0;i<imp.length;i++) if (imp[i]!=null) {
		  imp[i].show();
	  }
  }


  public static ImagePlus makeArrays(double[] pixels, int width, int height, String title) {
	  int j;
	  float [] fpixels;
	  if (pixels!=null) {
		  fpixels=new float[pixels.length];
		  for (j=0;j<pixels.length;j++) fpixels[j]=(float)pixels[j];
		  ImageProcessor ip=new FloatProcessor(width,height);
		  ip.setPixels(fpixels);
		  ip.resetMinAndMax();
		  ImagePlus imp=  new ImagePlus(title, ip);
		  return imp;
	  }
	  return null;
  }

  public static ImagePlus makeArrays(int[] pixels, int width, int height, String title) {
	  int j;
	  float [] fpixels;
	  if (pixels!=null) {
		  fpixels=new float[pixels.length];
		  for (j=0;j<pixels.length;j++) fpixels[j]=pixels[j];
		  ImageProcessor ip=new FloatProcessor(width,height);
		  ip.setPixels(fpixels);
		  ip.resetMinAndMax();
		  ImagePlus imp=  new ImagePlus(title, ip);
		  return imp;
	  }
	  return null;
  }

  public static ImagePlus makeArrays(boolean[] pixels, int width, int height, String title) {
	  int j;
	  float [] fpixels;
	  if (pixels!=null) {
		  fpixels=new float[pixels.length];
		  for (j=0;j<pixels.length;j++) fpixels[j]=pixels[j]?1.0f:0.0f;
		  ImageProcessor ip=new FloatProcessor(width,height);
		  ip.setPixels(fpixels);
		  ip.resetMinAndMax();
		  ImagePlus imp=  new ImagePlus(title, ip);
		  return imp;
	  }
	  return null;
  }


  public static ImagePlus makeArrays(float[] pixels, int width, int height, String title) {
	  int j;
	  float [] fpixels;
	  if (pixels!=null) {
		  fpixels=new float[pixels.length];
		  for (j=0;j<pixels.length;j++) fpixels[j]=pixels[j];
		  ImageProcessor ip=new FloatProcessor(width,height);
		  ip.setPixels(fpixels);
		  ip.resetMinAndMax();
		  ImagePlus imp=  new ImagePlus(title, ip);
		  return imp;
	  }
	  return null;
  }

  public static void showArrays(double[] pixels, int width, int height, String title) {
	  ImagePlus imp= makeArrays(pixels, width, height, title);
	  if (imp!=null) imp.show();
   }

  public static void showArrays(int[] pixels, int width, int height, String title) {
	  ImagePlus imp= makeArrays(pixels, width, height, title);
	  if (imp!=null) imp.show();
   }

  public static void showArrays(boolean[] pixels, int width, int height, String title) {
	  ImagePlus imp= makeArrays(pixels, width, height, title);
	  if (imp!=null) imp.show();
   }

  public static void showArrays(float[][] pixels, int width, int height, boolean asStack, String title) {
    int i;
    if (asStack) {
      ImageStack array_stack=new ImageStack(width,height);
      for (i=0;i<pixels.length;i++) if (pixels[i]!=null)  array_stack.addSlice("chn-"+i,    pixels[i]);
      ImagePlus imp_stack = new ImagePlus(title, array_stack);
      imp_stack.getProcessor().resetMinAndMax();
      imp_stack.show();
      return;
    } else showArrays(pixels, width, height, title);
  }

  public static void showArrays(float[][] pixels, int width, int height, String title) {
    int i;
    ImageProcessor[] ip= new ImageProcessor[pixels.length];
    ImagePlus[]      imp=new ImagePlus[pixels.length];
    for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
      ip[i]=new FloatProcessor(width,height);
      ip[i].setPixels(pixels[i]);
      ip[i].resetMinAndMax();
      imp[i]=  new ImagePlus(title+"_"+i, ip[i]);
      imp[i].show();
    }
  }

  public static void showArrays(float[] pixels, int width, int height, String title) {
    if (pixels!=null) {
      ImageProcessor ip=new FloatProcessor(width,height);
      ip.setPixels(pixels);
      ip.resetMinAndMax();
      ImagePlus imp=  new ImagePlus(title, ip);
      imp.show();
    }
  }
  public static ImagePlus showImageStack(ImageStack stack, String title) {
	  if (stack==null) return null;
      ImagePlus imp_stack = new ImagePlus(title, stack);
      imp_stack.getProcessor().resetMinAndMax();
      imp_stack.show();
      return imp_stack;
  }
  public static void showImageStackThree(ImageStack stack, String title) {
	  if (stack==null) return;
      ImageStack stack3=new ImageStack(stack.getWidth(),stack.getHeight());
      float [] fpixels_r= (float[]) stack.getPixels(1);
      float [] fpixels_g= (float[]) stack.getPixels(2);
      float [] fpixels_b= (float[]) stack.getPixels(3);
      stack3.addSlice("red",  fpixels_r);
      stack3.addSlice("green",fpixels_g);
      stack3.addSlice("blue", fpixels_b);

      ImagePlus imp_stack = new ImagePlus(title, stack3);
      imp_stack.getProcessor().resetMinAndMax();
      imp_stack.show();
  }

  // additional methods to show 2-d "flows" in color
  /*
  	  public int sliceRminusG=1;
	  public int sliceBminusG=2;
	  public double colorSpan=-1.0;
	  public double brightnessModulate=0.5;
	  public double Kr=0.299;   // 0.299;
	  public double Kb=0.114;   // 0.114;
  */
  public ImagePlus showFlowFromSlices(ImagePlus imp){
	  if ((imp==null) || (imp.getStackSize()<2))	{
		  String msg="Source image with at least two slices is required";
		  IJ.showMessage("Error",msg);
		  throw new IllegalArgumentException (msg);
	  }

	  GenericDialog gd = new GenericDialog("Select parameters to convert 2 slices (representing a vector field) to color");
	  gd.addMessage("Maximal values for normalization are calculated inside the selected area (or the full image if there is no selection");
	  gd.addNumericField("Slice number (1.."+imp.getStackSize()+" to convert to R/G", this.sliceRminusG, 0);
	  gd.addNumericField("Slice number (1.."+imp.getStackSize()+" to convert to B/G", this.sliceBminusG, 0);
	  gd.addNumericField("Color span - if positive - absolute value of the source data to get saturated color, -1 - calculate maximum", this.colorSpan, 3);
	  gd.addNumericField("Modulate brightness: 1.0 - use sqrt(x^2+y^2), 0.0 - constant brightness ", this.brightnessModulate, 3);
	  gd.addNumericField("Color conversion coefficient Kr (default =0.299) ", this.Kr, 3);
	  gd.addNumericField("Color conversion coefficient Kb (default =0.114) ", this.Kb, 3);
	  gd.addNumericField("Brightness (0..1.0)", this.brightness, 3);

	  gd.showDialog();
	  if (gd.wasCanceled()) return null;
	  this.sliceRminusG=     (int) gd.getNextNumber();
	  this.sliceBminusG=     (int) gd.getNextNumber();
	  this.colorSpan=              gd.getNextNumber();
	  this.brightnessModulate=     gd.getNextNumber();
	  this.Kr=                     gd.getNextNumber();
	  this.Kb=                     gd.getNextNumber();
	  this.brightness=             gd.getNextNumber();
	  return showFlowFromSlices(
			  imp,
			  this.sliceRminusG,
			  this.sliceBminusG,
			  this.colorSpan,
			  this.brightnessModulate,
			  this.brightness,
			  this.Kr,
			  this.Kb);
  }
	/**
	 *
	 * @param imp image containing at least 2 slices, rectangular selection will be used to find min/max
	 * @param sliceRminusG slice number to be interpreted as R-G
	 * @param sliceBminusG slice number to be interpreted as B-G
	 * @param colorSpan    when positive - absolute value in slice to have full color saturation, if negative -1 - full saturation
	 * @param brightnessModulate when 0 - same brightness, 1.0 - equals to sqrt(slice1^2 + slice2^2), normalized
	 * @return RGB24 color image
	 */
  public static ImagePlus showFlowFromSlices(
		  ImagePlus imp,
		  int sliceRminusG,
		  int sliceBminusG,
		  double colorSpan,
		  double brightnessModulate){
	  return showFlowFromSlices(
			  imp,
			  sliceRminusG,
			  sliceBminusG,
			  colorSpan,
			  brightnessModulate,0.5,0.299,0.114);

  }
  public static ImagePlus showFlowFromSlices(
		  ImagePlus imp,
		  int sliceRminusG,
		  int sliceBminusG,
		  double colorSpan,
		  double brightnessModulate,
		  double brightness,
		  double Kr,        // 0.299;
          double Kb        // 0.114;

  ){

	  if (	(sliceRminusG<1) || (sliceRminusG>imp.getStackSize()) ||
			  (sliceBminusG<1) || (sliceBminusG>imp.getStackSize()))	{
		  String msg="Source image does not contain specified slices";
		  IJ.showMessage("Error",msg);
		  throw new IllegalArgumentException (msg);
	  }
	  Roi roi= imp.getRoi();
	  Rectangle selection;
	  if (roi==null){
		  selection=new Rectangle(0, 0, imp.getWidth(), imp.getHeight());
	  } else {
		  selection=roi.getBounds();
	  }
	  float [][] pixels = new float[2][];
	  ImageStack stack = imp.getStack();
	  pixels[0]= (float[]) stack.getPixels(sliceRminusG);
	  pixels[1]= (float[]) stack.getPixels(sliceBminusG);
      double maxR=0.0; //Math.abs(pixels[0][0]);
      double maxB=0.0; //Math.abs(pixels[1][0]);
      double maxA=0.0;
      int width=imp.getWidth();
      for (int y=selection.y;y<selection.y+selection.height;y++)
      for (int x=selection.x;x<selection.x+selection.width;x++) {
    	  int index=y*width+x;
    	  if (maxR<Math.abs(pixels[0][index])) maxR=Math.abs(pixels[0][index]);
    	  if (maxB<Math.abs(pixels[1][index])) maxB=Math.abs(pixels[1][index]);
    	  double a=Math.sqrt(pixels[0][index]*pixels[0][index]+pixels[1][index]*pixels[1][index]);
    	  if (maxA<a) maxA=a;

      }
      double maxRB=Math.max(maxR,maxB);
      double Kg=1.0-Kr-Kb;
      double colorScale=1.0;
      if (colorSpan>0){
    	  colorScale=1.0/colorSpan;
      } else {
    	  colorScale=(-colorSpan)* 1.0/maxRB;
      }
/**
R= Y+ Pr*2.0*(1-Kr)
B= Y+ Pb*2.0*(1-Kb)
G= Y  +Pr*(- 2*Kr*(1-Kr))/Kg + Pb*(-2*Kb*(1-Kb))/Kg

*/
      double KPrR=  2.0*(1-Kr);
      double KPbB=  2.0*(1-Kb);
      double KPrG= -2.0*Kr*(1-Kr)/Kg;
      double KPbG= -2.0*Kb*(1-Kb)/Kg;
      System.out.println("maxR="+maxRB+" maxB="+maxB+" maxRB="+maxRB+" maxA="+maxA);
      System.out.println("KPrR="+KPrR+" KPbB="+KPbB+" KPrG="+KPrG+" KPbG="+KPbG);
      int [] pixelsRGB= new int [pixels[0].length];
      double [] rgb=new double[3];
      int debugIndex=selection.y*width+selection.x;
      for (int index=0;index<pixelsRGB.length;index++){
    	  double a=Math.sqrt(pixels[0][index]*pixels[0][index]+pixels[1][index]*pixels[1][index])/maxA;
    	  double Y= brightness*((1.0-brightnessModulate)+brightnessModulate*a);
    	  double Pr=colorScale*pixels[0][index];
    	  double Pb=colorScale*pixels[1][index];
    	  rgb[0]= (Y+ Pr*KPrR);
    	  rgb[1]= (Y+ Pb*KPbB);
    	  rgb[2]= (Y+ Pr*KPrG + Pb*KPbG);
    	  pixelsRGB[index]=0;
		  for (int c=0;c<3;c++) {
			  int d= (int) Math.round(255.0* rgb[c]);
			  if (d>255) d=255;
			  else if (d<0) d=0;
			  pixelsRGB[index]|= d<<(8*(2-c));
		  }
		  if (index==debugIndex){
		      System.out.println("a="+a+" Y="+Y+" Pr="+Pr+" Pb="+Pb);
		      System.out.println("rgb[0]="+rgb[0]+" rgb[1]="+rgb[1]+" rgb[2]="+rgb[2]);
		      System.out.println("pixelsRGB["+index+"]="+pixelsRGB[index]);
		  }
      }
      String title=imp.getTitle()+".png";
	  ColorProcessor cp=new ColorProcessor(imp.getWidth(),imp.getHeight());
	  cp.setPixels(pixelsRGB);
	  ImagePlus imp_color=new ImagePlus(title,cp);
	  return imp_color;

  }


  /**
   * Pad acquired Bayer image to the full sensor width/height. Used when optical center pixel coordinates do not match for channels
   * and WOI is adjusted during image capture to avoid ERS mismatch between horizontal pairs
   * @param imp_src source image with WOI specified as properties (sizes and offsets should be even)
   * @param wh {sesnor_width, sensor_height} in pixels
   * @param replicate fill gaps by replicating existing pixels
   * @return full size image
   */

  public static ImagePlus padBayerToFullSize(
		  ImagePlus imp_src,
		  int [] wh, // null OK - will use {woi_left+width, woi_top+height}
		  boolean replicate) {
	  int woi_top =    Integer.parseInt((String) imp_src.getProperty("WOI_TOP")); // enforce even
	  int woi_left =   Integer.parseInt((String) imp_src.getProperty("WOI_LEFT"));
	  int woi_width =  imp_src.getWidth(); // Integer.parseInt((String) imp_src.getProperty("WOI_WIDTH"));
	  int woi_height = imp_src.getHeight(); // Integer.parseInt((String) imp_src.getProperty("WOI_HEIGHT"));
	  Properties properties = imp_src.getProperties();

	  if (wh == null) {
		  wh = new int [2];
		  wh[0] = woi_left + woi_width;
		  wh[1] = woi_top  + woi_height;
	  }
	  if ((woi_top == 0) && (woi_left == 0) && (woi_width == wh[0])  && (woi_height == wh[1])){
		  return imp_src; // good as is
	  }
	  float [] full_pixels = new float [wh[0]*wh[1]];
	  float [] pixels=(float []) imp_src.getProcessor().getPixels();
	  int dst_col = woi_left;
	  int copy_width = woi_width;
	  if ((dst_col + copy_width) > wh[0]) {
		  copy_width = wh[0] - dst_col;
	  }
	  for (int src_row = 0; src_row < woi_height; src_row++) {
		  int dst_row = src_row + woi_top;
		  if (dst_row < wh[1]) {
			  System.arraycopy( pixels,   src_row * woi_width,  full_pixels, dst_row * wh[0] + dst_col,  copy_width);
		  }
	  }
	  if (replicate) {
		  // replicate top
		  for (int dst_row = 0; dst_row < woi_top; dst_row++) {
			  int src_row = woi_top + (dst_row & 1);
			  System.arraycopy( full_pixels,   src_row * wh[0] + dst_col,  full_pixels, dst_row * wh[0] + dst_col,  copy_width);
		  }
		  // replicate bottom
		  for (int dst_row = woi_top + woi_height; dst_row < wh[1]; dst_row++) {
			  int src_row = woi_top + woi_height - 2 + (dst_row & 1);
			  System.arraycopy( full_pixels,   src_row * wh[0] + dst_col,  full_pixels, dst_row * wh[0] + dst_col,  copy_width);
		  }
		  // right and left are not likely, as there is no need to use them - horizontal mismatch does not influence ERS
		  for (int col = 0; col < woi_left; col++) {
			  for (int row = 0; row < wh[1]; row++) {
				  full_pixels[row*wh[0] + col] = full_pixels[row*wh[0] + woi_left + (col & 1)];
			  }
		  }

		  for (int col = woi_left + woi_width; col < wh[0]; col++) {
			  for (int row = 0; row < wh[1]; row++) {
				  full_pixels[row*wh[0] + col] = full_pixels[row*wh[0] + woi_left + woi_width - 2 +(col & 1)];
			  }
		  }
	  }
	  ImageProcessor ip = new FloatProcessor(wh[0],wh[1]);
	  ip.setPixels(full_pixels);
	  ip.resetMinAndMax(); // is it needed here?
	  ImagePlus imp = new ImagePlus(imp_src.getTitle(),ip); // OK to have the same name?
	  for (Map.Entry<?, ?> entry: properties.entrySet()) {
		  String key = (String) entry.getKey();
		  String value = (String) entry.getValue();
		  imp.setProperty(key, value);
	  }
	  imp.setProperty("WOI_WIDTH", wh[0]+"");
	  imp.setProperty("WOI_HEIGHTH", wh[1]+"");
	  imp.setProperty("WOI_TOP", "0");
	  imp.setProperty("WOI_LEFT", "0");
	  return imp;
  }

  public static double [][] readDoubleArray(
		  ImagePlus   imp,
		  int         num_slices, // (0 - all)
		  int []      wh) {

	  float [][] fdata = readFloatArray(
			  imp, // 			ImagePlus   imp,
			  num_slices, // int         num_slices, // (0 - all)
			  wh);        // int []      wh)

	  if (fdata == null) {
		  return null;
	  }
	  double [][] result = new double [fdata.length][]; 
	  for (int n = 0; n < fdata.length; n++) if (fdata[n]!=null) {
		  result[n] = new double [fdata[n].length];
		  for (int i = 0; i < fdata[n].length; i++) {
			  result[n][i] = fdata[n][i];
		  }
	  }
	  return result;
  }

  public static float [][] readFloatArray(
		  ImagePlus   imp,
		  int         num_slices, // (0 - all)
		  int []      wh) {
	  if ((imp == null) || (imp.getTitle() == null) || (imp.getTitle().equals(""))) {
		  return null;
	  }
	  ImageStack imageStack = imp.getStack();
	  int nChn=imageStack.getSize();
	  if ((num_slices > 0) && (num_slices < nChn)) {
		  nChn = num_slices;
	  }
	  float [][] result = new float [nChn][]; 
	  for (int n = 0; n < nChn; n++) {
		  result[n] = (float[]) imageStack.getPixels(n + 1);
	  }
	  if (wh != null) {
		  wh[0] = imp.getWidth();
		  wh[1] = imp.getHeight();
	  }
	  return result;
  }

	public static float [][] readFloatArray(
			String      file_path,
			int         num_slices, // (0 - all)
			int []      wh) {
		ImagePlus imp = null;
		System.out.println("readFloatArray(): reading "+file_path);
		try {
			imp = new ImagePlus(file_path);
		} catch (Exception e) {
			System.out.println ("Failed to open "+file_path+", maybe will generate it");
		}
		if ((imp == null) || (imp.getTitle() == null) || (imp.getTitle().equals(""))) {
			return null;
		}
		System.out.println ("Read "+(imp.getStack().getSize())+" slices from "+file_path);
		return readFloatArray(
				imp, // ImagePlus   imp,
				num_slices, //int         num_slices, // (0 - all)
				wh); // int []      wh)
	}
  
	public static double [][][] readDoubleHyperstack(
			String      path,
			int []      wh,            // should be null or int[2] 
			String [][] ptop_titles,   // should be null or String [1][]
			String [][] pslice_titles){// should be null or String [1][]
		System.out.println("readDoubleHyperstack(): reading "+path);
		ImagePlus imp = new ImagePlus(path);
		if (imp.getWidth() == 0) {
			System.out.println("readDoubleHyperstack(): Failed reading [][][] from: " + path);
			return null;
		}
		int num_slices = imp.getStackSize();
		String [] slice_labels = imp.getStack().getSliceLabels();
			
		ArrayList<String> top_titles = new ArrayList<String>();
		ArrayList<String> slice_titles = new ArrayList<String>();
		for (int i = 0; i < num_slices; i++) {
			String [] label = slice_labels[i].split(":");
			if (label.length>1) {
				if (!top_titles.contains(label[0])) {
					top_titles.add(label[0]);
				}
				if (!slice_titles.contains(label[1])) {
					slice_titles.add(label[1]);
				}
			} else if (label.length>0) {
				if (!slice_titles.contains(label[0])) {
					slice_titles.add(label[0]);
				}
			}
		}
		String [] top_l =   top_titles.toArray(new String[0]);
		String [] slice_l = slice_titles.toArray(new String[0]);
		if (ptop_titles != null) {
			ptop_titles[0] = top_l;
		}
		if (pslice_titles != null) {
			pslice_titles[0] = slice_l;
		}
		double [][] file_data = readDoubleArray(
				imp,       // ImagePlus   imp,
				0,         // 	int         num_slices, // (0 - all)
				wh);       // int []      wh); // int []      wh)

		int num_top_slices = num_slices/slice_titles.size();
		double [][][] out_data = new double [num_top_slices][num_slices/num_top_slices][];
		for (int t = 0; t < out_data.length; t++) {
			for (int s = 0; s < out_data[t].length; s++) {
				out_data[t][s] = file_data[t * out_data[t].length + s];
			}
		}
		return out_data;
	}

	/**
	 * Transpose a jagged [outer][inner][comp] array to [outer][comp][inner],
	 * substituting NaN for null entries at either level.
	 *
	 * <p>This is a common pattern when tile-level results are stored as
	 * {@code arr[frame][tile][comp]} (inner entries may be null for missing tiles,
	 * outer entries may be null for missing frames) and need to be reorganised into
	 * the {@code [frame][comp][tile]} layout expected by
	 * {@link #showArraysHyperstack}.
	 *
	 * @param arr     source array; {@code arr[i]} may be null (whole frame absent);
	 *                {@code arr[i][j]} may be null (individual tile absent);
	 *                for non-null innermost entries the length must equal {@code n_comp}
	 * @param n_comp  number of components (innermost dimension in {@code arr})
	 * @param n_inner number of inner entries (tile count; used as second dimension of output)
	 * @return {@code [outer][n_comp][n_inner]} array with {@link Double#NaN} substituted
	 *         for null entries
	 */
	public static double[][][] transposeJagged(double[][][] arr, int n_comp, int n_inner) {
		double[][][] out = new double[arr.length][n_comp][n_inner];
		for (int i = 0; i < arr.length; i++) {
			for (int c = 0; c < n_comp; c++) {
				Arrays.fill(out[i][c], Double.NaN);
			}
			if (arr[i] != null) {
				for (int j = 0; j < n_inner; j++) {
					if (arr[i][j] != null) {
						for (int c = 0; c < n_comp; c++) {
							out[i][c][j] = arr[i][j][c];
						}
					}
				}
			}
		}
		return out;
	}

}