####################################################################################################
#  Facade to ImageJ functionality
####################################################################################################

#More information on:
#   Image:   https://imagej.nih.gov/ij/docs/guide/146-28.html#toc-Section-28
#   Process: https://imagej.nih.gov/ij/docs/guide/146-29.html#toc-Section-29
#   Analyze: https://imagej.nih.gov/ij/docs/guide/146-30.html#toc-Section-30

import ch.psi.utils.Convert as Convert
import ch.psi.pshell.imaging.Utils as Utils
from startup import get_context
import java.awt.image.BufferedImage as BufferedImage
import jarray

import ij.IJ as IJ
import ij.ImageJ as ImageJ
import ij.WindowManager as WindowManager
import ij.ImagePlus as ImagePlus
import ij.Prefs as Prefs
import ij.io.FileSaver as FileSaver

import ij.process.ImageProcessor as ImageProcessor
import ij.process.ByteProcessor as ByteProcessor
import ij.process.ShortProcessor as ShortProcessor
import ij.process.ColorProcessor as ColorProcessor
import ij.process.FloatProcessor as FloatProcessor
import ij.process.ImageConverter as ImageConverter
import ij.process.AutoThresholder as AutoThresholder
import ij.process.LUT as LUT
import ij.measure.Measurements as Measurements
import ij.measure.ResultsTable as ResultsTable
import ij.plugin.filter.Analyzer as Analyzer
import ij.plugin.filter.GaussianBlur as GaussianBlur
import ij.plugin.filter.Filters as Filters
import ij.plugin.filter.FFTFilter as FFTFilter
import ij.plugin.filter.BackgroundSubtracter as BackgroundSubtracter
import ij.plugin.filter.EDM as EDM
import ij.plugin.filter.Shadows as Shadows
import ij.plugin.filter.UnsharpMask as UnsharpMask
import ij.plugin.filter.MaximumFinder as MaximumFinder 
import ij.plugin.filter.EDM as EDM
import ij.plugin.filter.Shadows as Shadows
import ij.plugin.filter.UnsharpMask as UnsharpMask
import ij.plugin.filter.RankFilters as RankFilters
import ij.plugin.filter.Convolver as Convolver
import ij.plugin.filter.ParticleAnalyzer as ParticleAnalyzer

import ij.plugin.ContrastEnhancer as ContrastEnhancer
import ij.plugin.Thresholder as Thresholder
import ij.plugin.ImageCalculator as ImageCalculator
import ij.plugin.FFT as FFT
import ij.plugin.Concatenator as Concatenator

#ImageJ customizations
import ch.psi.pshell.imaging.ij.FFTMath as FFTMath
import ch.psi.pshell.imaging.ij.FFTFilter as FFTFilter
import ch.psi.pshell.imaging.ij.Binary as Binary
import ch.psi.pshell.imaging.ij.Slicer as Slicer


#This eliminates the error messages due to the bug on ij.gui.ImageWindow row 555 (ij is null)
if not "_image_j" in globals().keys():
    _image_j = ImageJ(None, ImageJ.NO_SHOW)
    
###################################################################################################
#Image creation, copying & saving
###################################################################################################
def load_image(image, title = "img"):    
    """
    image: file name or BufferedImage
    """    
    if isinstance(image, str):
        try:
            file = get_context().setup.expandPath(image)
        except:
            pass
        try:
            image = Utils.newImage(file)
        except:
            #try loading from assembly
            image = get_context().setup.getAssemblyImage(image)
    return ImagePlus(title, image)



def load_array(array, width=None, height=None, title = "img"):    
    """
    array: 1d array if width and height defined , or else 2d array to be flattened.
    """    
    #2D
    if (width==None) and (height==None):
        if array.typecode == '[B': proc = ByteProcessor(len(array[0]), len(array), Convert.flatten(array))
        elif array.typecode == '[S': proc = ShortProcessor(len(array[0]), len(array), Convert.flatten(array), None)
        elif array.typecode in ['[I','[F', '[D']: proc = FloatProcessor(len(array[0]), len(array), Convert.flatten(array))        
        else: raise Exception("Invalid array type")   
    #1D
    else:
        if (len(array) > width*height):
            array = array[:(width*height)]
        if array.typecode == 'b': proc = ByteProcessor(width, height, array)
        elif array.typecode == 'h': proc = ShortProcessor(width, height, array, None)
        elif array.typecode in ['i','f','d']: proc = FloatProcessor(width, height, array)
        else: raise Exception("Invalid array type")   
    return ImagePlus(title, proc)

def save_image(ip, path=None, format = None):
    """
    Saves image or stack
    If parameters omitted, saves image again in same location, with same format.
    """
    fs = FileSaver(ip)
    if path == None: fs.save()
    else:
        try:
            path = get_context().setup.expandPath(path)
        except:
            pass           
        if format == "bmp": fs.saveAsBmp(path) 
        elif format == "fits": fs.saveAsFits(path) 
        elif format == "gif": fs.saveAsGif(path) 
        elif format == "jpeg": fs.saveAsJpeg(path) 
        elif format == "lut": fs.saveAsLut(path) 
        elif format == "pgm": fs.saveAsPgm(path) 
        elif format == "png": fs.saveAsPng(path) 
        elif format == "raw" and ip.getImageStackSize()>1: fs.saveAsRawStack(path) 
        elif format == "raw": fs.saveAsRaw(path) 
        elif format == "txt": fs.saveAsText(path)
        elif format == "tiff" and ip.getImageStackSize()>1: fs.saveAsTiffStack(path)
        elif format == "tiff": fs.saveAsTiff(path)
        elif format == "zip": fs.saveAsZip(path)   

def new_image(width, height, image_type="byte", title = "img", fill_color = None):
    """
    type = "byte", "short", "color" or "float"
    """
    if image_type == "byte": p=ByteProcessor(width, height)
    elif image_type == "short": p=ShortProcessor(width, height)
    elif image_type == "color": p=ColorProcessor(width, height)
    elif image_type == "float": p=FloatProcessor(width, height)
    else: raise Exception("Invalid image type " + str(image_type))
    ret = ImagePlus(title, p)    
    if fill_color is not None:
        p.setColor(fill_color)
        p.resetRoi()
        p.fill()
    return ret    

def sub_image(ip, x, y, width, height):
    """
    Returns new ImagePlus
    """
    ip.setRoi(x, y, width, height)
    p=ip.getProcessor().crop()
    return ImagePlus(ip.getTitle() + " subimage", p)    

def copy_image(ip):
    return ip.duplicate()

def copy_image_to(ip_source, ip_dest, x, y):
    ip_source.deleteRoi()    
    ip_source.copy()
    ip_dest.setRoi(x, y, ip_source.getWidth(), ip_source.getHeight())
    ip_dest.paste()
    ip_dest.changes = False
    ip_dest.deleteRoi()
        
def pad_image(ip, left=0, right=0, top=0, bottom=0, fill_color = None):
    p=ip.getProcessor()
    width = p.getWidth() + left + right
    height =  p.getHeight() + top + bottom        
    image_type = get_image_type(ip)
    ret = new_image(width, height, image_type, ip.getTitle() + " padded", fill_color)
    ip.deleteRoi()
    ip.copy()
    ret.setRoi(left, top, p.getWidth(), p.getHeight())
    ret.paste()
    ret.changes = False
    ret.deleteRoi()
    return ret    

def get_image_type(ip):
    """
    Returns: "byte", "short", "color" or "float"
    """
    p=ip.getProcessor()
    if type(p) == ShortProcessor: return "short"
    elif type(p) == ColorProcessor: return "color"
    elif type(p) == FloatProcessor: return "float"
    return "byte"

###################################################################################################
#Image type conversion
###################################################################################################
def grayscale(ip, do_scaling=None, in_place=True):
    ip = ip if in_place else ip.duplicate()
    ic = ImageConverter(ip)
    if do_scaling is not None:
        ic.setDoScaling(do_scaling)
    ic.convertToGray8()
    return ip

def get_channel(ip, channel):
    """
    Return a channel from a color image as a new  ImagePlus.
    channel: "red", "green","blue", "alpha", "brightness", 
    """
    proc = ip.getProcessor()    
    if channel ==   "red": ret = proc.getChannel(1, None)
    elif channel == "green": ret = proc.getChannel(2, None)
    elif channel == "blue": ret = proc.getChannel(3, None)
    elif channel == "alpha": ret = proc.getChannel(4, None)
    elif channel == "brightness": ret =	proc.getBrightness()
    else: raise Exception("Invalid channel " + str(channel))
    return ImagePlus(ip.getTitle() + " channel: " + channel, ret)

###################################################################################################
#Thresholder
###################################################################################################
def threshold(ip, min_threshold, max_threshold, in_place=True):
    ip = ip if in_place else ip.duplicate()
    ip.getProcessor().setThreshold(min_threshold, max_threshold, ImageProcessor.NO_LUT_UPDATE)    
    WindowManager.setTempCurrentImage(ip)
    Thresholder().run("mask")
    return ip

def auto_threshold(ip, dark_background = False, method = AutoThresholder.getMethods()[0], in_place=True):
    ip = ip if in_place else ip.duplicate()
    ip.getProcessor().setAutoThreshold(method, dark_background , ImageProcessor.NO_LUT_UPDATE)
    WindowManager.setTempCurrentImage(ip)
    thresholder=Thresholder().run("mask")
    return ip

###################################################################################################
#Binary functions    
###################################################################################################
def binary_op(ip, op, dark_background=False, iterations=1, count=1, in_place=True):
    """
    op = "erode","dilate", "open","close", "outline", "fill holes", "skeletonize"
    """
    ip = ip if in_place else ip.duplicate()
    binary = Binary(count, iterations, dark_background )        
    binary.setup(op, ip)
    binary.run(ip.getProcessor())
    return ip

def binary_erode(ip, dark_background=False, iterations=1, count=1, in_place=True):
    return binary_op(ip, "erode", dark_background, iterations, count, in_place)

def binary_dilate(ip, dark_background=False, iterations=1, count=1, in_place=True):
    return binary_op(ip, "dilate", dark_background, iterations, count, in_place)

def binary_open(ip, dark_background=False, iterations=1, count=1, in_place=True):
    return binary_op(ip, "open", dark_background, iterations, count, in_place)

def binary_close(ip, dark_background=False, iterations=1, count=1, in_place=True):
    return binary_op(ip, "close", dark_background, iterations, count)

def binary_outline(ip, dark_background=False, in_place=True):
    return binary_op(ip, "outline", dark_background, in_place=in_place)
    
def binary_fill_holes(ip, dark_background=False, in_place=True):
    return binary_op(ip, "fill holes", dark_background, in_place=in_place)

def binary_skeletonize(ip, dark_background=False, in_place=True):
    return binary_op(ip, "skeletonize", dark_background, in_place=in_place)

def  analyse_particles(ip, min_size, max_size, fill_holes = True, exclude_edges = True, extra_measurements = 0, \
                       print_table = False, output_image = "outlines", minCirc = 0.0, maxCirc = 1.0):
    """
    Returns: tuple (ResultsTable results_table, ImagePlus output_image)
    output_image = "outlines", "overlay_outlines", "masks", "overlay_masks", "roi_masks" or None
    extra_measurements = mask with Measurements.CENTROID, PERIMETER, RECT, MIN_MAX, ELLIPSE, CIRCULARITY, AREA_FRACTION, INTEGRATED_DENSITY, INVERT_Y, FERET, KURTOSIS, MEDIAN, MODE, SKEWNESS, STD_DEV 
    Measurements is a mask of flags: https://imagej.nih.gov/ij/developer/api/ij/measure/Measurements.html.
    Returned ResultsTable hold public fields: https://imagej.nih.gov/ij/developer/api/ij/measure/ResultsTable.html
    
    """
    rt = ResultsTable()
    show_summary = False 
    options = ParticleAnalyzer.SHOW_RESULTS | ParticleAnalyzer.CLEAR_WORKSHEET 
    """
        ParticleAnalyzer.SHOW_ROI_MASKS | \
        #ParticleAnalyzer.RECORD_STARTS | \
        #ParticleAnalyzer.ADD_TO_MANAGER | \
        #ParticleAnalyzer.FOUR_CONNECTED | \
        #ParticleAnalyzer.IN_SITU_SHOW | \
        #ParticleAnalyzer.SHOW_NONE | \
    """
    if show_summary:             options = options | ParticleAnalyzer.DISPLAY_SUMMARY 
    if output_image == "outlines": options = options | ParticleAnalyzer.SHOW_OUTLINES 
    elif output_image == "overlay_outlines": options = options | ParticleAnalyzer.SHOW_OVERLAY_OUTLINES
    elif output_image == "masks":     options = options | ParticleAnalyzer.SHOW_MASKS
    elif output_image == "overlay_masks":     options = options | ParticleAnalyzer.SHOW_OVERLAY_MASKS
    elif output_image == "roi_masks":     options = options | ParticleAnalyzer.SHOW_ROI_MASKS
    #ParticleAnalyzer.SHOW_ROI_MASKS
    if exclude_edges:            options = options | ParticleAnalyzer.EXCLUDE_EDGE_PARTICLES
    if fill_holes:               options = options | ParticleAnalyzer.INCLUDE_HOLES
    measurements = Measurements.AREA  | Measurements.MEAN  |  Measurements.CENTER_OF_MASS | Measurements.RECT
    pa = ParticleAnalyzer(options, measurements, rt, min_size, max_size, minCirc, maxCirc)
    pa.setHideOutputImage(True)
    pa.setResultsTable(rt)
    if pa.analyze(ip):
        if print_table:
            print rt.getColumnHeadings()
            for row in range (rt.counter):
                print rt.getRowAsString(row)    
        return (rt, pa.getOutputImage())

###################################################################################################
#Image operators
###################################################################################################
def op_image(ip1, ip2, op, float_result=False, in_place=True):
    """
    op = "add","subtract",  "multiply","divide", "and", "or", "xor", "min", "max", "average", "difference" or "copy"
    """
    ip1 = ip1 if in_place else ip1.duplicate()
    ic = ImageCalculator()
    pars = op
    if float_result:
        op = op + " float" 
    ic.run(pars, ip1, ip2)
    return ip1

def op_const(ip, op, val, in_place=True):
    """
    op = "add","subtract",  "multiply","divide", "and", "or", "xor", "min", "max", "gamma", "set" or "log", "exp", "sqr", "sqrt","abs"
    """
    ip = ip if in_place else ip.duplicate()
    pr = ip.getProcessor()
    if op == 'add': pr.add(val)
    elif op == 'sub': pr.subtract(val)
    elif op == 'multiply': pr.multiply(val)
    elif op == 'divide' and val!=0: pr.multiply(1.0/val)
    elif op == 'and': pr.and(val)
    elif op == 'or': pr.or(val)
    elif op == 'xor': pr.xor(val)
    elif op == 'min': pr.min(val);pr.resetMinAndMax()
    elif op == 'max': pr.max(val);pr.resetMinAndMax()
    elif op == 'gamma' and 0.05 < val < 5.0: pr.gamma(val)
    elif op == 'set': pr.set(val)
    elif op == 'log': pr.log()
    elif op == 'exp': pr.exp()        
    elif op == 'sqr': pr.sqr()        
    elif op == 'sqrt': pr.sqrt()        
    elif op == 'abs': pr.abs();pr.resetMinAndMax()
    else: raise Exception("Invalid operation " + str(op))    
    return ip

def op_fft(ip1, ip2, op, do_inverse = True) :
    """
    Images must have same sizes, and multiple of  2  height and width.
    op = "correlate" (complex conjugate multiply), "convolve" (Fourier domain multiply), "deconvolve" (Fourier domain divide)
    """
    if   op == "correlate": op_index = 0
    elif op == "convolve":  op_index = 1
    elif op == "deconvolve":  op_index = 2
    else: raise Exception("Invalid operation " + str(op))    
    return FFTMath().doMath(ip1, ip2, op_index, do_inverse)

def op_rank(ip, op, kernel_radius =1 ,  dark_outliers = False ,threshold = 50, in_place=True):
    """
    op = "mean", "min", "max", "variance", "median", "close_maxima", "open_maxima", "remove_outliers", "remove_nan", "despeckle"
    """
    if   op == "mean": filter_type = RankFilters.MEAN
    elif op == "min": filter_type = RankFilters.MIN
    elif op == "max": filter_type = RankFilters.MAX
    elif op == "variance": filter_type = RankFilters.VARIANCE
    elif op == "median": filter_type = RankFilters.MEDIAN
    elif op == "close_maxima": filter_type = RankFilters.CLOSE
    elif op == "open_maxima": filter_type = RankFilters.OPEN
    elif op == "remove_outliers": filter_type = RankFilters.OUTLIERS
    elif op == "remove_nan": filter_type = RankFilters.REMOVE_NAN
    elif op == "despeckle": filter_type, kernel_radius = RankFilters.MEDIAN, 1
    else: raise Exception("Invalid operation " + str(op))
    ip = ip if in_place else ip.duplicate()            
    RankFilters().rank(ip.getProcessor(), kernel_radius, filter_type, RankFilters.DARK_OUTLIERS if dark_outliers else RankFilters.BRIGHT_OUTLIERS ,threshold)
    return ip    

def op_edm(ip, op="edm", dark_background=False, in_place=True):
    """
    Euclidian distance map & derived  operations
    op ="edm", "watershed","points", "voronoi"
    """    
    ip = ip if in_place else ip.duplicate()        
    pr = ip.getProcessor()
    edm=EDM()
    Prefs.blackBackground=dark_background      
    if  op=="edm":        
        #pr.setPixels(0, edm.makeFloatEDM(pr, 0, False));
        #pr.resetMinAndMax();                
        if dark_background:
            pr.invert()
        edm.toEDM(pr)
    else:
        edm.setup(op, ip)
        edm.run(pr)
    return ip

def watershed(ip, dark_background=False, in_place=True):
    return op_edm(ip, "watershed", dark_background, in_place)

def ultimate_points(ip, dark_background=False, in_place=True):
    return op_edm(ip, "points", dark_background, in_place)

def veronoi(ip, dark_background=False, in_place=True):
    return op_edm(ip, "voronoi", dark_background, in_place)

def edm(ip, dark_background=False, in_place=True):
    return op_edm(ip, "edm", dark_background, in_place)

def op_filter(ip, op, in_place=True):    
    """
    This is redundant as just calls processor methods.
    op ="invert", "smooth", "sharpen", "edge", "add"
    """    
    ip = ip if in_place else ip.duplicate()
    f = Filters()
    f.setup(op, ip )
    f.run(ip.getProcessor())
    return ip

###################################################################################################
#Other operations
###################################################################################################
def gaussian_blur(ip, sigma_x=3.0, sigma_y=3.0, accuracy = 0.01, in_place=True):
    ip = ip if in_place else ip.duplicate()
    GaussianBlur().blurGaussian(ip.getProcessor(),  sigma_x, sigma_y, accuracy)
    return ip

def find_maxima(ip, tolerance=25, threshold = ImageProcessor.NO_THRESHOLD, output_type=MaximumFinder.IN_TOLERANCE, exclude_on_edges = False, is_edm = False):
    """
    Returns new ImagePlus
    tolerance:  maxima are accepted only if protruding more than this value  from the ridge to a higher maximum
    threshhold: minimum height of a maximum (uncalibrated);
    output_type = SINGLE_POINTS, IN_TOLERANCE or SEGMENTED.  No output image is created for output types POINT_SELECTION, LIST and COUNT.   
    """
    byte_processor =  MaximumFinder().findMaxima(ip.getProcessor(), tolerance, threshold, output_type, exclude_on_edges, is_edm)     
    return ImagePlus(ip.getTitle() + " maxima", byte_processor)


def get_maxima_points(ip, tolerance=25, exclude_on_edges = False):
    polygon =  MaximumFinder().getMaxima(ip.getProcessor(), tolerance, exclude_on_edges)
    return (polygon.xpoints, polygon.ypoints)
    
def enhance_contrast(ip, equalize_histo = True, saturated_pixels = 0.5, normalize = False, stack_histo = False, in_place=True):
    ip = ip if in_place else ip.duplicate()
    ce = ContrastEnhancer()
    if equalize_histo:
        ce.equalize(ip.getProcessor());
    else:
        ce.stretchHistogram(ip.getProcessor(), saturated_pixels)
        if normalize:
            ip.getProcessor().setMinAndMax(0,1.0 if (ip.getProcessor().getBitDepth()==32) else ip.getProcessor().maxValue())
    return ip   

def shadows(ip, op, in_place=True):
    """
    op ="north","northeast", "east", "southeast","south", "southwest", "west","northwest"
    """
    ip = ip if in_place else ip.duplicate()
    shadows= Shadows()
    shadows.setup(op, ip)
    shadows.run(ip.getProcessor())
    return ip

def unsharp_mask(ip, sigma, weight, in_place=True):
    """
    Float processor
    """    
    ip = ip if in_place else ip.duplicate()
    ip.getProcessor().snapshot()
    unsharp=UnsharpMask()
    USmask.setup("  ", ip)
    USmask.sharpenFloat( ip.getProcessor(),sigma, weight)
    return ip

def subtract_background(ip, radius = 50, create_background=False, dark_background=False, use_paraboloid =True, do_presmooth = True, correctCorners = True, rgb_brightness=False, in_place=True):
    ip = ip if in_place else ip.duplicate()
    if rgb_brightness:        
        BackgroundSubtracter().rollingBallBrightnessBackground(ip.getProcessor(), radius, create_background,not dark_background, use_paraboloid, do_presmooth, correctCorners)
    else:
        BackgroundSubtracter().rollingBallBackground(ip.getProcessor(), radius, create_background, not dark_background, use_paraboloid, do_presmooth, correctCorners)        
    return ip

###################################################################################################
#FFT
###################################################################################################
def image_fft(ip, show = True):   
    WindowManager.setTempCurrentImage(ip)
    fft = FFT()
    fft.run("fft")    
    #TODO: how to avoid it to be created?
    #ret =  ImagePlus("FHT of " + ip.getTitle(), WindowManager.getCurrentImage().getProcessor())
    ret = WindowManager.getCurrentImage()
    if not show:
        WindowManager.getCurrentImage().hide()
    return ret
    

def image_ffti(ip, show = True):   
    WindowManager.setTempCurrentImage(ip)
    fft = FFT()
    fft.run("inverse")
    #WindowManager.getCurrentImage().hide()
    #TODO: how to avoid it to be created?
    #ret = WindowManager.getCurrentImage()
    #WindowManager.getCurrentImage().hide()
    #ret =  ImagePlus(ip.getTitle() + " ffti", WindowManager.getCurrentImage().getProcessor())
    ret = WindowManager.getCurrentImage()
    if not show:
        WindowManager.getCurrentImage().hide()
    
    return ret

def bandpass_filter(ip, small_dia_px, large_dia_px, suppress_stripes = 0, stripes_tolerance_direction = 5.0, autoscale_after_filtering = False, saturate_if_autoscale = False, display_filter = False, in_place=True):
    """
    suppress_stripes = 0 for none, 1 for horizontal, 2 for vertical
    """
    ip = ip if in_place else ip.duplicate()
    filter=  FFTFilter();
    FFTFilter.filterLargeDia = large_dia_px
    FFTFilter.filterSmallDia = small_dia_px
    FFTFilter.choiceIndex = suppress_stripes
    FFTFilter.toleranceDia = stripes_tolerance_direction
    FFTFilter.doScalingDia = autoscale_after_filtering
    FFTFilter.saturateDia = saturate_if_autoscale
    FFTFilter.displayFilter =display_filter
    filter.setup(None, ip);
    filter.run(ip.getProcessor())
    return ip

###################################################################################################
#Convolution
###################################################################################################

KERNEL_BLUR = [[0.1111, 0.1111, 0.1111], [0.1111, 0.1111, 0.1111], [0.1111, 0.1111, 0.1111]]
KERNEL_SHARPEN = [[0.0, -0.75, 0.0], [-0.75, 4.0, -0.75], [0.0, -0.75, 0.0]]
KERNEL_SHARPEN_2 = [[-1.0, -1.0, -1.0], [-1.0, 9.0, -1.0], [-1.0, -1.0, -1.0]]
KERNEL_LIGHT = [[0.1, 0.1, 0.1], [0.1, 1.0, 0.1],[0.1, 0.1, 0.1]]
KERNEL_DARK = [[0.01, 0.01, 0.01],[0.01, 0.5, 0.01],[0.01, 0.01, 0.01]]
KERNEL_EDGE_DETECT = [[0.0, -0.75, 0.0], [-0.75, 3.0, -0.75], [0.0, -0.75, 0.0]]
KERNEL_EDGE_DETECT_2 = [[-0.5, -0.5, -0.5], [-0.5, 4.0, -0.5], [-0.5, -0.5, -0.5]]
KERNEL_DIFFERENTIAL_EDGE_DETECT = [[-1.0, 0.0, 1.0], [0.0, 0.0, 0.0], [1.0, 0.0, -1.0]]
KERNEL_PREWITT = [[-2.0, -1.0, 0.0], [-1.0,  0.0,  1.0 ], [0.0, 1.0, 2.0]]
KERNEL_SOBEL = [[2.0, 2.0, 0.0], [2.0,  0.0,  -2.0 ],  [0.0, -2.0, -2.0]]


def convolve(ip, kernel, in_place=True):
    """
    kernel: list of lists
    """
    ip = ip if in_place else ip.duplicate()
    kernel_width = len(kernel)
    kernel_height= len(kernel[0])
    kernel = [item for row in kernel for item in row]
    #Convolver().convolve(ip.getProcessor(), kernel, kernel_width, kernel_height) 
    ip.getProcessor().convolve(kernel, kernel_width, kernel_height)
    return ip

        
###################################################################################################
#Shortcut  to ImageProcessor methods
###################################################################################################
def invert(ip, in_place=True):
    ip = ip if in_place else ip.duplicate()
    ip.getProcessor().invert()
    return ip

def smooth(ip, in_place=True):
    ip = ip if in_place else ip.duplicate()
    ip.getProcessor().smooth()
    return ip
            
def sharpen(ip, in_place=True):
    ip = ip if in_place else ip.duplicate()
    ip.getProcessor().sharpen()
    return ip

def edges(ip, in_place=True): #Sobel
    ip = ip if in_place else ip.duplicate()
    ip.getProcessor().findEdges()
    return ip

def noise(ip, sigma = 25.0, in_place=True):
    ip = ip if in_place else ip.duplicate()
    ip.getProcessor().noise(sigma)
    return ip

def remap(ip, min=None, max=None, in_place=True):
    ip = ip if in_place else ip.duplicate()
    if min is None or max is None:
        stats = get_statistics(ip, Measurements.MIN_MAX)
        if min is None: min = stats.min
        if max is None: max = stats.max
    ip.getProcessor().setMinAndMax(min, max)
    return ip

def set_lut(ip, r, g, b):
    """
    r,g and b are lists of 256 integers
    """
    r = [x if x<128 else x-256 for x in r]
    g = [x if x<128 else x-256 for x in g]
    b = [x if x<128 else x-256 for x in b]    
    ip.setLut(LUT(jarray.array(r,'b'),jarray.array(g,'b'),jarray.array(b,'b')))

def resize(ip, width, height):
    """
    Returns new ImagePlus
    """
    p = ip.getProcessor().resize(width, height)
    return ImagePlus(ip.getTitle() + " resized", p)

def binning(ip, factor):
    p=ip.getProcessor().bin(factor)
    return ImagePlus(ip.getTitle() + " resized", p)

def get_histogram(ip, hist_min = 0, hist_max = 0, hist_bins = 256, roi=None):
    """
    hist_min, hist_max, hist_bins used only for float images (otherwise fixed to 0,255,256)
    roi is list  [x,y,w,h]
    """
    if roi == None: ip.deleteRoi()    
    else: ip.setRoi(roi[0],roi[1],roi[2],roi[3])
    image_statistics = ip.getStatistics(0, hist_bins, hist_min, hist_max)    
    return image_statistics.getHistogram()


def get_array(ip):
    return ip.getProcessor().getIntArray()

def get_line(ip, x1, y1, x2, y2):
    return ip.getProcessor().getLine(x1, y1, x2, y2)

def get_pixel_range(ip):
    return (ip.getProcessor().getMin(), ip.getProcessor().getMax())

def get_num_channels(ip):
    return ip.getProcessor().getNChannels()

def is_binary(ip):
    return ip.getProcessor().isBinary()    

def get_pixel(ip, x, y):
    return ip.getProcessor().getPixel(x,y)

def get_pixel_array(ip, x, y):
    a = [0]*get_num_channels(ip)
    return ip.getProcessor().getPixel(x,y,a)

def get_pixels(ip):
    return ip.getProcessor().getPixels()

def get_width(ip):
    return ip.getProcessor().getWidth()

def get_height(ip):
    return ip.getProcessor().getHeight()

def get_row(ip, y):
    a = [0]*get_width(ip)
    array = jarray.array(a,'i')
    ip.getProcessor().getRow(0, y, array, get_width(ip))
    return array

def get_col(ip, x):
    a = [0]*get_height(ip)
    array = jarray.array(a,'i')
    ip.getProcessor().getColumn(x, 0, array, get_height(ip))
    return array     

def get_statistics(ip, measurements = None):
    """
    Measurements is a mask of flags: https://imagej.nih.gov/ij/developer/api/ij/measure/Measurements.html.
    Statistics object hold public fields: https://imagej.nih.gov/ij/developer/api/ij/process/ImageStatistics.html
    """
    if measurements is None:
        return ip.getStatistics()
    else:
        return ip.getStatistics(measurements)

###################################################################################################
#Image stack functions
###################################################################################################
def create_stack(ip_list, keep=True, title = None):
    stack = Concatenator().concatenate(ip_list, keep)
    if title is not None:
        stack.setTitle(title)
    return stack

def reslice(stack, start_at = "Top", vertically = True, flip = True, output_pixel_spacing=1.0, avoid_interpolation = True, title = None):
    ss = Slicer()
    ss.rotate = vertically
    ss.startAt = start_at 
    ss.flip = flip
    ss.nointerpolate =  avoid_interpolation
    ss.outputZSpacing = output_pixel_spacing
    stack =  ss.reslice(stack)
    if title is not None:
        stack.setTitle(title)
    return stack
    
    
 
