# -*- coding: utf-8 -*-
r"""
IOimagefile module is made for reading data contained in binary image file
fully or partially.

More tools can be found in LaueTools package at sourceforge.net and gitlab.esrf.fr
March 2020
"""
__author__ = "Jean-Sebastien Micha, CRG-IF BM32 @ ESRF"

# built-in modules
import sys
import os
import copy
import struct

# third party modules
try:
    import warnings
    with warnings.catch_warnings():
        warnings.filterwarnings("ignore",category=FutureWarning)
        import fabio

    FABIO_EXISTS = True
except ImportError:
    print("Missing fabio module. Please install it if you need open some tiff images "
            "from the sCMOS camera")
    FABIO_EXISTS = False

try:
    from libtiff import TIFF, libtiff_ctypes

    libtiff_ctypes.suppress_warnings()
    LIBTIFF_EXISTS = True
except (ImportError, ValueError):
    print("Missing library libtiff, Please install: pylibtiff if you need open some tiff images")
    LIBTIFF_EXISTS = False

try:
    from PIL import Image

    PIL_EXISTS = True
except ImportError:
    print("Missing python module called PIL. Please install it if you need open some tiff "
            "images from vhr camera")
    PIL_EXISTS = False
import numpy as np


# lauetools modules
if sys.version_info.major == 3:
    from . import dict_LaueTools as DictLT
    from . import imageprocessing as ImProc
else:
    import dict_LaueTools as DictLT
    import imageprocessing as ImProc

listfile = os.listdir(os.curdir)

def stringint(k, n):
    r""" returns string of integer k with n zeros padding
    (by placing zeros before to have n characters)

    :param k: integer to convert
    :param n: nb of digits for zero padding

    :return: string of length n containing integer k

    Example: 1 -> '0001'
    15 -> '0015'
    """
    #     strint = str(k)
    #     res = '0' * (n - len(strint)) + strint

    encodingdigits = "{" + ":0{}".format(int(n)) + "}"
    res = encodingdigits % k

    return res

# --- ---------------- read images functions
def setfilename(imagefilename, imageindex, nbdigits=4, CCDLabel=None, verbose=0):
    r"""
    reconstruct filename string from imagefilename and update filename index with imageindex

    :param imagefilename: filename string (full path or not)
    :param imageindex: index in filename
    :type imageindex: string

    :return filename: input filename with index replaced by input imageindex
    :rtype: string
    """
    if nbdigits in (0,'0','None'):
        nbdigits = None
    #     print "imagefilename",imagefilename
    if imagefilename.endswith("mccd"):
        lenext = 5 #length of extension including '.'
        imagefilename = imagefilename[: -(lenext + nbdigits)] + "{:04d}.mccd".format(imageindex)

    elif CCDLabel in ("sCMOS", "sCMOS_fliplr"):
        #default file extension for sCMOS camera
        ext = "tif"
        lenext = 4  #length of extension including '.'

        if imagefilename.endswith("tiff"):
            ext = "tiff"
            lenext = 5
        
        prepart = str(imagefilename).rsplit('.',1)[0]
        prefix0, strindex = str(prepart).rsplit('_', 1)

        if nbdigits is not None or nbdigits == 0:  # zero padded   (4 digits)
            SPEC_controlled_SCMOS = True
            imagefilename = prefix0 + "_{:04d}.{}".format(imageindex, ext)
        else:  # bliss psl control of sCMOS
            SPEC_controlled_SCMOS = False
            imagefilename = prefix0 + "_{}.{}".format(imageindex, ext)

        # if int(strindex)<1000:
        #     if len(strindex)<4: # no zero padded
        #         SPEC_controlled_SCMOS = False
        #         imagefilename = prefix0 + "_{}.{}".format(imageindex, ext)

        #     else:  # 4 only  zeros padded 
        #         SPEC_controlled_SCMOS = True
        #         imagefilename = prefix0 + "_{:04d}.{}".format(imageindex, ext)
        
        # # zero padded index for filename
        # if nbdigits is not None:
        #     if imagefilename.endswith(ext):
        #         #imagefilename = imagefilename[: - (lenext + nbdigits)] + "{:04d}.{}".format(imageindex, ext)

        #         #imagefilename = imagefilename[: - (lenext + nbdigits)].zfill(nbdigits) + "%s" % ext

        #         prepart = str(imagefilename).rsplit('.',1)[0]
        #         prefix, strindex = str(prepart).rsplit('_', 1)
                

        #         current_nbdigits = len(strindex)
        #         imagefilename = prefix + "_" + str(imageindex).zfill(current_nbdigits) + "." + ext
                
        #     elif imagefilename.endswith(ext+".gz"):
        #         imagefilename = imagefilename[: -(lenext+3 + nbdigits)] + "{:04d}.{}.gz".format(imageindex, ext)
        # # no zero padded index for filename
        # else:
        #     if imagefilename.endswith(ext):
        #         prefix, _ = imagefilename.split(".")
        #         prefix0, strindex = prefix.rsplit("_",1)

        #         if len(strindex)<=4:

        #         if 
        #         SPEC_controlled_SCMOS = True

        #         # version SPEC and SCMOS up to March 2022
        #         if imageindex > 9999:
        #             imagefilename = prefix0 + "_{}.{}".format(imageindex, ext)
        #         else: # 4   0s padding for index < 10000
        #             imagefilename = prefix0 + "_{:04d}.{}".format(imageindex, ext)

    elif CCDLabel in ("EIGER_4Mstack",):
        # only stackimageindex is changed not imagefilename
        pass

    #     print "imagefilename archetype", imagefilename
    elif imagefilename.endswith("mar.tiff"):

        imagefilename = imagefilename[: -(9 + nbdigits)] + "{:04d}_mar.tiff".format(imageindex)

    elif imagefilename.endswith("mar.tif"):
        # requires two underscores with number in between
        # map1_105_mar.tif
        sname = imagefilename.split("_")
        if len(sname) >= 4:  # pathfilename contain more than 1 underscore

            sname2 = sname[-2] + "_" + sname[-1]
            prefix = imagefilename.rstrip(sname2)
        else:
            prefix = imagefilename.rsplit("_")[0]
        imagefilename = prefix + "_{}_mar.tif".format(imageindex)

    # special case for image id15 frelon corrected form distorsion
    elif imagefilename.endswith((".tif", ".edf")):
        if CCDLabel in ("ImageStar_raw", ):
            prefixwihtindex = imagefilename[:-4]
            prefixwithindex_list = list(prefixwihtindex)
            indexnodigit = 0
            while len(prefixwithindex_list) > 0:
                lastelem = prefixwithindex_list.pop(-1)
                if verbose>0: print("lastelem", lastelem)
                if not lastelem.isdigit():
                    break
                indexnodigit += 1
            prefix = prefixwihtindex[:-(indexnodigit)]
            if verbose > 0: print("prefix", prefix)
            imagefilename = prefix + "{}.tif".format(imageindex)

        else:
            suffix = imagefilename[-4:]
            prefix = imagefilename[: -(4 + nbdigits)]

            imagefilename = prefix + "{:04d}{}".format(imageindex, suffix)

    elif imagefilename.endswith("mccd"):

        imagefilename = imagefilename[: -(5 + nbdigits)] + "{:04d}.mccd".format(imageindex)

    elif imagefilename.endswith("edf"):

        imagefilename = imagefilename[: -(4 + nbdigits)] + "{:04d}.edf".format(imageindex)

    elif imagefilename.endswith("unstacked"):

        imagefilename = imagefilename[: -(10 + nbdigits)] + "{:04d}.unstacked".format(imageindex)

    #     print "set filename to:", imagefilename
    return imagefilename


def getIndex_fromfilename(imagefilename, nbdigits=4, CCDLabel=None, stackimageindex=-1, verbose=0):
    r"""
    get integer index from imagefilename string

    :param imagefilename: filename string (full path or not)

    :return: file index
    """
    if CCDLabel in ("sCMOS", "sCMOS_fliplr"):
        #default file extension for sCMOS camera
        ext = "tif"
        lenext = 4 #length of extension including '.'

        if imagefilename.endswith("tiff"):
            ext = "tiff"
            lenext = 5
        if verbose > 0: print('imagefilename', imagefilename)
        if nbdigits is not None:
            if imagefilename.endswith(ext):
                #imageindex = int(imagefilename[-(lenext + nbdigits): - (lenext)])
                prepart = str(imagefilename).rsplit('.',1)[0]
                digitpart = str(prepart).rsplit('_',1)[-1]
                print("prepart", prepart)
                print("digitpart",digitpart)
                imageindex = int(digitpart)
            elif imagefilename.endswith(ext+".gz"):
                imageindex = int(imagefilename[-(lenext+3 + nbdigits) : -(lenext+3)])
        else:
            if imagefilename.endswith(ext):
                prefix, _ = imagefilename.rsplit(".")
                imageindex = int(prefix.rsplit("_")[1])

    # for stacked images we return the position of image data in the stack as imagefileindex
    elif CCDLabel in ("EIGER_4Mstack",):
        imageindex = stackimageindex

    elif imagefilename.endswith("mar.tiff"):
        imageindex = int(imagefilename[-(9 + nbdigits) : -9])
    elif imagefilename.endswith("tiff"):
        imageindex = int(imagefilename[-(5 + nbdigits) : -5])
    elif imagefilename.endswith("mar.tif"):
        imageindex = int(imagefilename.split("_")[-2])
    elif imagefilename.endswith(".tif"):
        # TODO: treat the case of HN56.tif  without underscore (see setfilename)
        imageindex = int(imagefilename.split("_")[-1][:-4])
    elif imagefilename.endswith("mccd"):
        imageindex = int(imagefilename[-(5 + nbdigits) : -5])
    elif imagefilename.endswith("edf"):
        imageindex = int(imagefilename[-(4 + nbdigits) : -4])
    elif imagefilename.endswith("unstacked"):
        imageindex = int(imagefilename[-(10 + nbdigits) : -10])

    return imageindex


def getfilename(dirname, imfileprefix, imfilesuffix=None, numim=None,
                nbdigits_filename=4):
    """
    to get the global file name (name+path) for given components of the name
    put %4d instead of stringint
    """

    fileim = imfileprefix + stringint(numim, nbdigits_filename) + imfilesuffix
    filename = os.path.join(dirname, fileim)

    return filename

def getwildcardstring(CCDlabel):
    r"""  return smart wildcard to open binary CCD image file with priority of CCD type of CCDlabel

    :param CCDlabel: string label defining the CCD type
    :type CCDlabel: str
    :return: string from concatenated strings to be used in wxpython open file dialog box
    :rtype: str

    .. see also::
        - :func:`getIndex_fromfilename`

        - LaueToolsGUI.AskUserfilename

        - wx.FileDialog

    .. example::
        >>> from readmccd import getwildcardstring
        >>> getwildcardstring('MARCCD165')
        'MARCCD, ROPER(*.mccd)|*mccd|mar tif(*.tif)|*_mar.tiff|tiff(*.tiff)|*tiff|Princeton(*.spe)|*spe|Frelon(*.edf)|*edf|tif(*.tif)|*tif|All files(*)|*'
    """
    ALL_EXTENSIONS = ["mccd", "_mar.tiff", "tiff", "spe", "edf", "tif", "h5", ""]
    INFO_EXTENSIONS = ["MARCCD, ROPER(*.mccd)", "mar tif(*.tif)", "tiff(*.tiff)",
                    "Princeton(*.spe)", "Frelon(*.edf)", "tif(*.tif)", "hdf5(*.h5)",
                    "All files(*)"]

    extensions = copy.copy(ALL_EXTENSIONS)
    infos = copy.copy(INFO_EXTENSIONS)
    chosen_extension = DictLT.dict_CCD[CCDlabel][7]

    if chosen_extension in ALL_EXTENSIONS:
        index = ALL_EXTENSIONS.index(chosen_extension)
        ce = extensions.pop(index)
        extensions.insert(0, ce)

        inf = infos.pop(index)
        infos.insert(0, inf)

    wcd = ""
    for inf, ext in zip(infos, extensions):
        wcd += "{}|*{}|".format(inf, ext)

    wildcard_extensions = wcd[:-1]

    return wildcard_extensions


def getpixelValue(filename, x, y, ccdtypegeometry="edf"):
    r"""return pixel value at x,y

    .. warning::
        Very old function. To be checked. Use better readpixelvalue in plotdip.py

    :param filename: path to image file
    :type filename: str
    :param x: x pixel value
    :type x: int
    :param y: y pixel value
    :type y: int
    :param ccdtypegeometry: CCD label, defaults to "edf"
    :type ccdtypegeometry: str, optional
    :return: pixel intensity
    :rtype: int
    """
    if ccdtypegeometry == "edf":
        # frelon camera as mounted on BM32 Oct2012
        y = 2047 - y
    if ccdtypegeometry in ("mccd",):
        pass

    f = open(filename, "rb")
    f.seek(1024 + 2 * (2048 * y + x))
    val = struct.unpack("H", f.read(2))
    f.close()
    return val[0]


def readheader(filename, offset=4096, CCDLabel="MARCCD165"):
    r"""
    return header in a raw format

    default offset for marccd image
    """
    if CCDLabel.startswith("sCMOS"):
        filesize = os.path.getsize(filename)
        framedim = DictLT.dict_CCD[CCDLabel][0]
        offset = filesize - np.prod(framedim) * 2
    elif CCDLabel.startswith("MARCCD165"):
        offset = 4096
    with open(filename, "rb") as f:
        myheader = f.read(offset)
        #myheader.replace("\x00", " ")

    return myheader


def read_header_marccd(filename):
    r"""
    return string of parameters found in header in marccd image file .mccd

    - print allsentences  displays the header
    - use allsentences.split('\n') to get a list
    """
    f = open(filename, "rb")
    f.seek(2048)
    posbyte = 0
    allsentences = ""
    for _ in list(range(32)):
        tt = f.read(32)
        s1 = tt.strip("\x00")
        if s1 != "":
            allsentences += s1 + "\n"
        # print posbyte, s1
        posbyte += 32
    tt = f.read(1024)
    s1 = tt.strip("\x00")
    if s1 != "":
        allsentences += s1 + "\n"

    f.close()
    return allsentences


def read_header_marccd2(filename):
    r"""
    return string of parameters comments and exposure time
    found in header in marccd image file .mccd

    - print allsentences  displays the header
    - use allsentences.split('\n') to get a list
    """
    f = open(filename, "rb")
    f.seek(3072)
    tt = f.read(512)
    # from beamline designed input
    dataset_comments = str(tt).strip("\x00")

    f.seek(1024 + 2 * 256 + 128 + 12)
    s = struct.Struct("I I I")
    unpacked_data = s.unpack(f.read(3 * 4))
    #     print 'Unpacked Values:', unpacked_data
    #integration_time, expo_time, readout_time = unpacked_data
    _, expo_time, _ = unpacked_data

    f.close()
    return dataset_comments, expo_time


def read_header_scmos(filename, verbose=0):
    r"""
    return string of parameters comments and exposure time
    found in header in scmis image file .tif

    - print allsentences  displays the header
    - use allsentences.split('\n') to get a list
    """
    if not PIL_EXISTS:
        return {}

    img = Image.open(filename)


    # img.tag.keys()
    # tag[270]   = (u'0 (thf=-50.4850 mon=24923 exposure=0.400)',)

    dictpar = {}
    strcom = img.tag[270][0]
    if verbose>0: print('read_header_scmos', strcom, type(strcom))
    try:
        si = strcom.index("(")
        fi = strcom.index(")")
        listpar = strcom[si + 1 : fi].split()
    except:
        return {}
    #     print "listpar",listpar
    for elem in listpar:
        if "=" in elem:
            key, val = elem.split("=")
            dictpar[key] = float(val)

    return dictpar


def read_motorsposition_fromheader(filename, CCDLabel="MARCCD165"):
    r""" return xyzpositions, expo_time from image file header
    available for "MARCCD165", "sCMOS", "sCMOS_fliplr"
    """
    if CCDLabel in ("MARCCD165",):
        dataset_comments, expo_time = read_header_marccd2(filename)
        xyz = dataset_comments.split(" ")[:3]
        xyzpositions = []
        for pos in xyz:
            xyzpositions.append(float(pos))
    elif CCDLabel in ("sCMOS", "sCMOS_fliplr"):
        dictpar = {}
        import re

        img = Image.open(filename)
        ff = img.tag[315]
        aa = re.findall(r"([^:]+)=([^:]+)(?:,|$)", ff[0])
        listeq = aa[0][0].split(" ")
        for elem in listeq:
            if "Xech" in elem or "Yech" in elem or "Zech" in elem:
                dictpar[elem.lstrip("(")] = float(elem.rstrip(")"))

        headerdict = read_header_scmos(filename)
        if headerdict:
            expo_time = headerdict["exposure"]
        xyzpositions = []
        for motorname in ("Xech", "Yech", "Zech"):
            xyzpositions.append(dictpar[motorname])

    return xyzpositions, expo_time


def readoneimage_full(filename, frametype="mccd", dirname=None):
    r"""
    too SLOW!
    reads 1 entire image (marCCD format)
    :return: PILimage, image object of PIL module (16 bits integer) and
    arrayofdata: 2D array of intensity
    #TODO: manage framedim like readoneimage() just below"""

    if frametype == "mccd":
        shapeCCD = (2048, 2048)
    elif frametype == "martiff":
        # opposite of fit2d displays in loading data
        shapeCCD = (2591, 2751)
    elif frametype == "spe":
        # opposite of fit2d displays in loading data
        shapeCCD = (2048, 2048)

    if dirname is None:
        dirname = os.curdir

    pilimage = Image.open(os.path.join(dirname, filename))
    ravdata = np.array(pilimage.getdata())

    return pilimage, np.reshape(ravdata, shapeCCD)


def readCCDimage(filename, CCDLabel="MARCCD165", dirname=None, stackimageindex=-1, verbose=0):
    r"""general function to read raw data binary (Laue pattern) image file recorder on 2D detector.

    Read raw data binary image file and return pixel intensity 2D array such as
    to fit the data (2theta, chi) scattering angles representation convention.

    :param filename: path to image file (fullpath if ` dirname` =None)
    :type filename: str
    :param CCDLabel: label, defaults to "MARCCD165"
    :type CCDLabel: str, optional
    :param dirname: folder path, defaults to None
    :type dirname: str, optional
    :param stackimageindex: index of images bunch, defaults to -1
    :type stackimageindex: int, optional
    :param verbose: 0 or 1, defaults to 0
    :type verbose: int, optional
    :raises ValueError: if data format and CCD parameters from label are not compatible
    :return:
        - dataimage, 2D array image data pixel intensity properly oriented
        - framedim, iterable of 2 integers shape of dataimage
        - fliprot : string, key for CCD frame transform to orient image
    :rtype: tuple of 3 elements
    """
    (framedim, _, _, fliprot, offsetheader, formatdata, _, _) = DictLT.dict_CCD[CCDLabel]

    USE_RAW_METHOD = False

    if verbose > 1:
        print("CCDLabel in readCCDimage", CCDLabel)
        print('FABIO_EXISTS',FABIO_EXISTS)
        print('LIBTIFF_EXISTS',LIBTIFF_EXISTS)
        print('PIL_EXISTS',PIL_EXISTS)
    #    if extension != extension:
    #        print "warning : file extension does not match CCD type set in Set CCD File Parameters"
    if FABIO_EXISTS:
        if CCDLabel in ('MARCCD165', "EDF", "EIGER_4M", "EIGER_1M","IMSTAR_bin2","IMSTAR_bin1",
                        "sCMOS", "sCMOS_fliplr", "sCMOS_fliplr_16M", "sCMOS_16M",
                        "Rayonix MX170-HS", 'psl_weiwei', 'ImageStar_dia_2021','ImageStar_dia_2021_2x2'):

            if verbose > 1:
                print('----> Using fabio ... to open %s\n'%filename)
            # warning import Image  # for well read of header only

            if dirname is not None:
                img = fabio.open(os.path.join(dirname, filename))
            else:
                img = fabio.open(filename)

            dataimage = img.data
            framedim = dataimage.shape

            # pythonic way to change immutable tuple...
            initframedim = list(DictLT.dict_CCD[CCDLabel][0])
            initframedim[0] = framedim[0]
            initframedim[1] = framedim[1]
            initframedim = tuple(initframedim)
        else:
            USE_RAW_METHOD = True

    elif CCDLabel in ("EIGER_4Mstack", ):

        import tables as Tab

        if dirname is not None:
            pathtofile = os.path.join(dirname, filename)
        else:
            pathtofile = filename

        # TODO check to correct version transition
        if Tab.__version__ >= "3.4.2":
            hdf5file = Tab.open_file(pathtofile)
        else:
            hdf5file = Tab.openFile(pathtofile)

        if verbose > 0: print("opening hdf5 stacked data table")
        alldata = hdf5file.root.entry.data.data
        if verbose > 0: print("alldata.shape", alldata.shape)

        dataimage = alldata[stackimageindex]
        framedim = dataimage.shape

    elif LIBTIFF_EXISTS:
        if verbose > 1:
            print("----> Using libtiff...")
        if CCDLabel in ("sCMOS", "MARCCD165", "TIFF Format", "FRELONID15_corrected", "VHR_PSI",
                            "VHR_DLS", "MARCCD225", "Andrea", "pnCCD_Tuba", "sCMOS_16M"):

            #         print "tiff format", CCDLabel
            #             print "dirname, filename", dirname, filename
            if dirname is not None:
                tifimage = TIFF.open(os.path.join(dirname, filename), mode="r")
            else:
                tifimage = TIFF.open(filename, mode="r")

            dataimage = tifimage.read_image()
            framedim = (int(tifimage.GetField("ImageLength")),
                        int(tifimage.GetField("ImageWidth")))
            if tifimage.IsByteSwapped():
                dataimage = dataimage.byteswap()
        else:
            USE_RAW_METHOD = True

    elif PIL_EXISTS:
        if verbose > 1:
            print("using PIL's module Image")
        if CCDLabel in ("sCMOS", "MARCCD165", "sCMOS_16M"):
            if verbose > 1: print('PIL is too slow. Better install libtiff or fabio. Meanwhile I will use PIL...')
            USE_RAW_METHOD = True
        elif CCDLabel in ("VHR_PSI", "VHR_DLS", "MARCCD225", "Andrea", "pnCCD_Tuba"):
            # data are compressed!

            if dirname is not None:
                fullpath = os.path.join(dirname, filename)
            else:
                fullpath = filename

            im = Image.open(fullpath, "r")
            dataimage = np.array(im.getdata()).reshape(framedim)

    # RAW method knowing or deducing offsetheader and dataformat
    if USE_RAW_METHOD:
        print("----> !!! not using libtiff, nor fabio, nor PIL!!!  ")
        if CCDLabel in ("MARCCD165",):
            print("for MARCCD not using libtiff, raw method ...")
            # offsetheader may change ...
            #filesize=getfilesize(dirname, filename)
            offsetheader = 4096 #filesize - 2048*2048 * 2
        # offset header varying
        elif CCDLabel.startswith("ImageStar_raw"):
            filesize=getfilesize(dirname, filename)
            bytes_per_pixels = 2
            if CCDLabel.endswith("32bits"):
                bytes_per_pixels = 4

            nbpixels = 1500
            if CCDLabel in ("ImageStar_1528x1528",):
                nbpixels = 1528
            offsetheader = filesize - nbpixels * nbpixels * bytes_per_pixels

        elif CCDLabel == "ImageStar_dia_2021":
            filesize=getfilesize(dirname, filename)
            bytes_per_pixels = 2
            nbpixels = 3056

            offsetheader = filesize - nbpixels * nbpixels * bytes_per_pixels
        elif CCDLabel in ("sCMOS",):
            if verbose > 0: print("for sCMOS not using libtiff, raw method ...")
            # offsetheader may change ...
            filesize = getfilesize(dirname, filename)
            offsetheader = filesize - 2016*2018 * 2

        dataimage = readoneimage(filename,
                                framedim=framedim,
                                dirname=dirname,
                                offset=offsetheader,
                                formatdata=formatdata)

        print("dataimage", dataimage)

        if CCDLabel in ("FRELONID15_corrected",):

            dataimage = dataimage.byteswap()

        if CCDLabel in ("EIGER_4Munstacked",):
            dataimage = np.ma.masked_where(dataimage > 4000000, dataimage)
            if verbose > 0:
                print("framedim", framedim)
                print("offsetheader", offsetheader)
                print("formatdata", formatdata)
                print("dataimage", dataimage)

    if verbose:
        print("CCDLabel: ", CCDLabel)
        print("nb of pixels", np.shape(dataimage))

    # need to reshape data from 1D to 2D
    try:
        if len(dataimage.shape) == 1:
            if verbose:
                print("nb elements", len(dataimage))
                print("framedim", framedim)
                print("framedim nb of elements", framedim[0] * framedim[1])
            dataimage = np.reshape(dataimage, framedim)
    except ValueError:
        raise ValueError(
            "Selected CCD type :{} may be wrong (or nb of pixels, dimensions...)".format(
                CCDLabel))

    # some array transformations if needed depending on the CCD mounting
    if fliprot == "spe":
        dataimage = np.rot90(dataimage, k=1)

    elif fliprot == "VHR_Feb13":
        #            self.dataimage_ROI = np.rot90(self.dataimage_ROI, k=3)
        dataimage = np.fliplr(dataimage)

    elif fliprot == "sCMOS_fliplr":
        dataimage = np.fliplr(dataimage)

    elif fliprot == "vhr":  # july 2012 close to diamond monochromator crystal
        dataimage = np.rot90(dataimage, k=3)
        dataimage = np.fliplr(dataimage)

    elif fliprot == "vhrdiamond":  # july 2012 close to diamond monochromator crystal
        dataimage = np.rot90(dataimage, k=3)
        dataimage = np.fliplr(dataimage)

    elif fliprot == "Imagestar_dia_2021":  # starting Feb july 2021 close to diamond monochromator crystal
        dataimage = np.rot90(dataimage, k=1)
        dataimage = np.fliplr(dataimage)

    elif fliprot in  ("frelon2", "flipud"):
        dataimage = np.flipud(dataimage)

    return dataimage, framedim, fliprot

def getfilesize(dirname,filename):
    if dirname is not None:
        filesize = os.path.getsize(os.path.join(dirname, filename))
    else:
        filesize = os.path.getsize(filename)
    return filesize

def readoneimage(filename, framedim=(2048, 2048), dirname=None, offset=4096, formatdata="uint16"):
    r""" crude way to open binary image. it returns a 1d array of integers from a binary image file (full data)

    :param filename: image file name (full path if dirname=0)
    :type filename: str
    :param framedim: detector dimensions, defaults to (2048, 2048)
    :type framedim: tuple of 2 integers, optional
    :param dirname: folder path, defaults to None
    :type dirname: str, optional
    :param offset: file header in byte (octet), defaults to 4096
    :type offset: int, optional
    :param formatdata: numpy format of raw binary image pixel value, defaults to "uint16"
    :type formatdata: str, optional
    :return: dataimage : image data pixel intensity
    :rtype: 1D array
    """

    nb_elem = framedim[0] * framedim[1]

    if dirname is None:
        dirname = os.curdir

    f = open(os.path.join(dirname, filename), "rb")
    f.seek(offset)

    # d=scipy.io.fread(f,2048*2048,np.oldnumeric.Int16)
    # d = scipy.io.fread(f,nb_elem,np.oldnumeric.UInt16)
    d = np.fromfile(f, dtype=formatdata, count=nb_elem)
    f.close()
    return d


def readoneimage_band(filename,
                        framedim=(2048, 2048),
                        dirname=None,
                        offset=4096,
                        line_startindex=0,
                        line_finalindex=2047,
                        formatdata="uint16"):
    r"""
    returns a 1d array of integers from a binary image file. Data located in band according shape of data (framedim)

    :param filename: string
               path to image file (fullpath if `dirname`=None)
    :param offset: integer
             nb of file header bytes
    :param framedim: iterable of 2 integers
               shape of expected 2D data
    :param formatdata: string
                 key for numpy dtype to decode binary file

    :return: dataimage, 1D array, image data pixel intensity
    """
    if dirname is None:
        dirname = os.curdir

    if formatdata in ("uint16",):
        nbBytesPerElement = 2
    if formatdata in ("uint32",):
        nbBytesPerElement = 4

    nbElems = (line_finalindex - line_startindex + 1) * framedim[1]

    f = open(os.path.join(dirname, filename), "rb")
    f.seek(offset + line_startindex * framedim[1] * nbBytesPerElement)

    # d=scipy.io.fread(f,2048*2048,np.oldnumeric.Int16)
    # d = scipy.io.fread(f,nb_elem,np.oldnumeric.UInt16)
    #     print "line_finalindex-line_startindex",line_finalindex-line_startindex
    #     print "line_startindex*framedim[1]*nbBytesPerElement",line_startindex*framedim[1]*nbBytesPerElement
    #     print "line_startindex*framedim[1]",line_startindex*framedim[1]
    #     print "nbElems",nbElems

    band = np.fromfile(f, dtype=formatdata, count=nbElems)
    f.close()
    return band


def readoneimage_crop_fast(filename, dirname=None, CCDLabel="MARCCD165",
                                                    firstElemIndex=0, lastElemIndex=2047, verbose=0):
    r""" Returns a 2d array of integers from a binary image file. Data are taken only from a rectangle

    with respect to firstElemIndex and lastElemIndex.

    :param filename: string, path to image file (fullpath if ` dirname`=None)
    :param offset: integer, nb of file header bytes
    :param framedim: iterable of 2 integers, shape of expected 2D data
    :param formatdata: string, key for numpy dtype to decode binary file

    :return: dataimage : 1D array image data pixel intensity
    """
    (framedim, _, _, fliprot, offsetheader, formatdata, _, _) = DictLT.dict_CCD[CCDLabel]
    if verbose > 0:
        print("framedim read from DictLT.dict_CCD in readoneimage_crop_fast()", framedim)
        print("formatdata", formatdata)
        print("offsetheader", offsetheader)

    if dirname is None:
        dirname = os.curdir

    # if formatdata in ("uint16",):
    #     nbBytesPerElement = 2
    # if formatdata in ("uint32",):
    #     nbBytesPerElement = 4

    dataimage2D = np.zeros(framedim)

    # colFirstElemIndex = firstElemIndex % framedim[1]
    lineFirstElemIndex = firstElemIndex // framedim[1]
    # colLastElemIndex = lastElemIndex % framedim[1]
    lineLastElemIndex = lastElemIndex // framedim[1]

    band1D = readoneimage_band(filename, framedim=framedim, dirname=dirname, offset=offsetheader,
                                                            line_startindex=lineFirstElemIndex,
                                                            line_finalindex=lineLastElemIndex,
                                                            formatdata=formatdata)

    #     print "band1D.shape",band1D.shape
    #     print "(lineLastElemIndex-lineFirstElemIndex,framedim[1])",(lineLastElemIndex-lineFirstElemIndex,framedim[1])
    #     print "((lineLastElemIndex-lineFirstElemIndex)*framedim[1])",((lineLastElemIndex-lineFirstElemIndex)*framedim[1])

    band2D = band1D.reshape((lineLastElemIndex - lineFirstElemIndex + 1, framedim[1]))

    dataimage2D[lineFirstElemIndex : lineLastElemIndex + 1, :] = band2D

    return dataimage2D, framedim, fliprot


def readrectangle_in_image(filename, pixx, pixy, halfboxx, halfboxy, dirname=None,
                                                                            CCDLabel="MARCCD165",
                                                                            verbose=0):
    r"""
    returns a 2d array of integers from a binary image file. Data are taken only from a rectangle
    centered on pixx, pixy

    :return: dataimage : 2D array, image data pixel intensity
    """
    (framedim, _, _, fliprot, offsetheader, formatdata, _, _) = DictLT.dict_CCD[CCDLabel]

    if verbose > 0:
        print("framedim read from DictLT.dict_CCD in readrectangle_in_image()", framedim)
        print("formatdata", formatdata)
        print("offsetheader", offsetheader)
    # recompute headersize
    if dirname is not None:
        fullpathfilename = os.path.join(dirname, filename)
    else:
        dirname = os.curdir
        fullpathfilename = filename

    if formatdata in ("uint16",):
        nbBytesPerElement = 2
    if formatdata in ("uint32",):
        nbBytesPerElement = 4

    if verbose > 0:
        print("fullpathfilename", fullpathfilename)
    try:
        filesize = os.path.getsize(fullpathfilename)
    except OSError:
        print("missing file {}\n".format(fullpathfilename))
        return None

    # uint16
    offsetheader = filesize - (framedim[0] * framedim[1]) * nbBytesPerElement

    if verbose > 0:
        print("calculated offset of header from file size...", offsetheader)

    x = int(pixx)
    y = int(pixy)

    if fliprot in ("sCMOS_fliplr",):
        x = framedim[1] - x

    boxx = int(halfboxx)
    boxy = int(halfboxy)

    xpixmin = x - boxx
    xpixmax = x + boxx

    ypixmin = y - boxy
    ypixmax = y + boxy

    #     ymin= (y-boxy)*framedim[1]
    #     ymax= (y+boxy)*framedim[1]

    lineFirstElemIndex = ypixmin
    lineLastElemIndex = ypixmax

    if verbose > 0:
        print("lineFirstElemIndex", lineFirstElemIndex)
        print("lineLastElemIndex", lineLastElemIndex)

    band = readoneimage_band(fullpathfilename,
                                framedim=framedim,
                                dirname=None,
                                offset=offsetheader,
                                line_startindex=lineFirstElemIndex,
                                line_finalindex=lineLastElemIndex,
                                formatdata=formatdata)

    nblines = lineLastElemIndex - lineFirstElemIndex + 1

    band2D = np.reshape(band, (nblines, framedim[1]))

    rectangle2D = band2D[:, xpixmin : xpixmax + 1]

    if verbose > 0:
        print("band2D.shape", band2D.shape)
        print("rectangle2D.shape", rectangle2D.shape)

    return rectangle2D


def readoneimage_crop(filename, center, halfboxsize, CCDLabel="PRINCETON", dirname=None):
    r"""
    return a cropped array of data read in an image file

    :param filename: string, path to image file (fullpath if ` dirname`=None)
    :param center: iterable of 2 integers, (x,y) pixel coordinates
    :param halfboxsize: integer or iterable of 2 integers, ROI half size in both directions

    :return: dataimage : 1D array, image data pixel intensity

    .. todo:: useless?
    """
    if dirname is None:
        dirname = os.curdir
    else:
        filename = os.path.join(dirname, filename)

    if isinstance(halfboxsize, int):
        boxsizex, boxsizey = halfboxsize, halfboxsize
    elif len(halfboxsize) == 2:
        boxsizex, boxsizey = halfboxsize

    xpic, ypic = center

    dataimage, framedim, _ = readCCDimage(filename, CCDLabel=CCDLabel, dirname=None)

    x1 = np.maximum(0, xpic - boxsizex)
    x2 = np.minimum(framedim[1], xpic + boxsizex + 1)  # framedim[0]
    y1 = np.maximum(0, ypic - boxsizey)
    y2 = np.minimum(framedim[0], ypic + boxsizey + 1)  # framedim[1]

    return None, dataimage[y1:y2, x1:x2]


def readoneimage_manycrops(filename, centers, boxsize, stackimageindex=-1, CCDLabel="MARCCD165",
                                                                        addImax=False,
                                                                        use_data_corrected=None,
                                                                        verbose=0):
    r"""
    reads 1 image and extract many regions
    centered on center_pixel with xyboxsize dimensions in pixel unit

    :param filename: string,fullpath to image file
    :param centers: list or array of [int,int] centers (x,y) pixel coordinates
    :param use_data_corrected: enter data instead of reading data from file
                         must be a tuple of 3 elements:
                         fulldata, framedim, fliprot
                         where fulldata is a numpy.ndarray
                         as output by :func:`readCCDimage`
    :param boxsize: iterable 2 elements or integer
              boxsizes [in x, in y] direction or integer to set a square ROI

    :return: Data, list of 2D array pixel intensity or Data and Imax
    """
    # use alternate data  (for instance for data from filename without background)
    if use_data_corrected is not None:
        if isinstance(use_data_corrected, tuple):
            if len(use_data_corrected) == 3:
                fulldata, framedim, _ = use_data_corrected
    # use data by reading file
    else:
        fulldata, framedim, _ = readCCDimage(filename, stackimageindex=stackimageindex,
                                                            CCDLabel=CCDLabel,
                                                            dirname=None)

    if isinstance(boxsize, int):
        boxsizex, boxsizey = boxsize, boxsize
    elif len(boxsize) == 2:
        boxsizex, boxsizey = boxsize

    # xpic, ypic = np.array(centers).T

    #    x1 = np.array(np.maximum(0, xpic - boxsizex), dtype=np.int)
    #    x2 = np.array(np.minimum(framedim[0], xpic + boxsizex), dtype=np.int)
    #    y1 = np.array(np.maximum(0, ypic - boxsizey), dtype=np.int)
    #    y2 = np.array(np.minimum(framedim[1], ypic + boxsizey), dtype=np.int)

    Data = []

    Imax = []
        
    if verbose > 0:
        print("framedim in readoneimage_manycrops", framedim)
    framedim = framedim[1], framedim[0]

    for center in centers:
        i1, i2, j1, j2 = ImProc.getindices2cropArray(center, (boxsizex, boxsizey), framedim)
        #        print "i1, i2, j1, j2-----", i1, i2, j1, j2
        cropdata = fulldata[i1:i2, j1:j2]
        #         # print "cropdata.shape", cropdata.shape
        #         print "i2-i1",i2-i1
        #         print "boxsizey*2+1",boxsizey*2+1
        #         print "j2-j1",j2-j1
        #         print "boxsizex*2+1",boxsizex*2+1

        # for spot near border, replace by zeros array
        if i2 - i1 != boxsizey * 2 or j2 - j1 != boxsizex * 2:
            cropdata = np.zeros((boxsizey * 2 + 1, boxsizex * 2 + 1))

        Data.append(cropdata)
        if addImax:
            Imax.append(np.amax(cropdata))

    #        print "max in cropped data", np.amax(cropdata)
    if addImax:
        return Data, Imax
    else:
        return Data

def writeimage(outputname, _header, data, dataformat=np.uint16, verbose=0):
    r"""
    from data 1d array of integers
    with header coming from a f.open('imagefile'); f.read(headersize);f.close()
    .. warning:: header contain dimensions for subsequent data. Check before the compatibility of
    data with header infos(nb of byte per pixel and array dimensions
    """
    newfile = open(outputname, "wb")
    newfile.write(_header)
    data = np.array(data, dtype=dataformat)
    data.tofile(newfile)
    newfile.close()
    if verbose > 0: print("image written in ", outputname)


def write_rawbinary(outputname, data, dataformat=np.uint16, verbose=0):
    r"""
    write a binary file without header of a 2D array

    used ?
    """
    newfile = open(outputname, "wb")
    data = np.array(data, dtype=dataformat)
    data.tofile(newfile)

    newfile.close()
    if verbose > 0: print("image written in ", outputname)


def SumImages(prefixname, suffixname, ind_start, ind_end, dirname=None,
                                                            plot=0,
                                                            output_filename=None,
                                                            CCDLabel=None,
                                                            nbdigits=0):
    r"""
    sum images and write image with 32 bits per pixel format (4 bytes)

    used?
    """
    #     prefixname = 'HN08_'
    #     suffixname = '.tif'
    #     CCDLabel = 'ImageStar_raw'
    #     dirname = '/home/micha/LaueProjects/Vita'

    output_filename = "mean_{}_{:04d}_{}{}".format(prefixname, ind_start, ind_end, suffixname)

    filename = "{}{:04d}{}".format(prefixname, ind_start, suffixname)

    data, shape, _ = readCCDimage(filename, CCDLabel=CCDLabel, dirname=dirname)

    if CCDLabel == "ImageStar_raw":
        # Add addition of 32 bits image => replace 2 by 4  nb of bytes per pixel
        filesize = os.path.getsize(os.path.join(dirname, filename))
        offsetheader = filesize - 1500 * 1500 * 2
    if CCDLabel == "EIGER_1M":
        filesize = os.path.getsize(os.path.join(dirname, filename))
        offsetheader = filesize - 1065 * 1030 * 4
    else:
        # Add addition of 32 bits image => replace 2 by 4  nb of bytes per pixel
        filesize = os.path.getsize(os.path.join(dirname, filename))
        offsetheader = filesize - 2048 * 2048 * 2
    # print "shape = ", np.shape(datastart)
    datasum = np.zeros(shape, dtype=np.uint32)

    #     nb_images = ind_end - ind_start + 1

    indexscanlist = list(range(ind_start, ind_end + 1, 1))
    for k in indexscanlist:
        # print k
        filename = "{}{:04d}{}".format(prefixname, k, suffixname)
        # print filename1
        data, shape, _ = readCCDimage(filename, CCDLabel=CCDLabel, dirname=dirname)
        # print max(data1), np.argmax(data1)
        datasum = data + datasum

    if output_filename:
        outputfilename = output_filename
        header = readheader(os.path.join(dirname, filename), offset=offsetheader)
        writeimage(os.path.join(dirname, outputfilename), header, datasum, dataformat=np.uint32)

        print("Added images with prefix {} from {} to {} written in {}".format(
                prefixname, ind_start, ind_end, outputfilename))
    if plot:
        print("later")

    return datasum


def Add_Images2(prefixname,
                ind_start,
                ind_end,
                plot=0,
                writefilename=None,
                CCDLabel="MARCCD165",
                average=True):
    """
    in dev
    """
    suffixname = "." + DictLT.dict_CCD[CCDLabel][-1]

    filename = prefixname + stringint(ind_start, 4) + suffixname
    datastart, _, _ = readCCDimage(filename, CCDLabel)

    # print "shape = ", np.shape(datastart)
    datastart = np.array(datastart, dtype=float)

    indexscanlist = list(range(ind_start + 1, ind_end + 1, 1))
    for k in indexscanlist:
        # print k
        filename1 = prefixname + stringint(k, 4) + suffixname
        # print filename1
        data1, _, _ = readCCDimage(filename1, CCDLabel)
        # print max(data1), np.argmax(data1)
        datastart = np.array(data1, dtype=float) + datastart
    # datastart= datastart/((len(indexscanlist)+1)*1.)
    # print "final"
    # print max(datastart), np.argmax(datastart)
    if average:
        datastart = datastart / (float(len(indexscanlist)) + 1.0)
    # print max(datastart), np.argmax(datastart)
    datastart = np.array(datastart, dtype=np.uint16)
    # print max(datastart), np.argmax(datastart)
    if writefilename:
        outputfilename = writefilename
        header = readheader(filename1, CCDLabel=CCDLabel)
        writeimage(outputfilename, header, datastart)
        print("written in ", outputfilename)
    if plot:
        print("later")

    return datastart


def Add_Images(prefixname, ind_start, ind_end, plot=0, writefilename=None):
    r"""
    Add continuous sequence of images

    .. note::
        Add_Images2 exists

    :param prefixname: string, prefix common part of name of files

    :param ind_start: int, starting image index

    :param ind_end: int, final image index

    :param writefilename: string, new image filename where to write datastart (with last image file header read)

    :return:  datastart, array accumulation of 2D data from each image
    """
    suffixname = ".mccd"

    datastart = readoneimage(prefixname + stringint(ind_start, 4) + suffixname)
    # print "shape = ", np.shape(datastart)
    datastart = np.array(datastart, dtype=float)

    indexscanlist = list(range(ind_start + 1, ind_end + 1, 1))
    for k in indexscanlist:
        # print k
        filename1 = prefixname + stringint(k, 4) + suffixname
        # print filename1
        data1 = readoneimage(filename1)
        # print max(data1), np.argmax(data1)
        datastart = np.array(data1, dtype=float) + datastart
    # datastart= datastart/((len(indexscanlist)+1)*1.)
    # print "final"
    # print max(datastart), np.argmax(datastart)
    datastart = datastart / (float(len(indexscanlist)) + 1.0)
    # print max(datastart), np.argmax(datastart)
    datastart = np.array(datastart, dtype=np.uint16)
    # print max(datastart), np.argmax(datastart)
    if writefilename:
        outputfilename = writefilename
        header = readheader(filename1)
        writeimage(outputfilename, header, datastart)
        print("written in ", outputfilename)
    if plot:
        print("later")

    return datastart

def get_imagesize(framedim, nbbits_per_pixel, headersize_bytes):
    r"""
    return size of image in byte (= 1 octet = 8 bits)
    """
    return (framedim[0] * framedim[1] * nbbits_per_pixel + headersize_bytes * 8) // 8
