#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-
#
# Extracting script for Image Reduction application
#
# Author: Stéphane POIRIER, Raphaël GIRARDOT, Sahbi THAIRI
# date  : 2019/08/01
#
# version: 1.9
#
# usage: python extractor.py <path/to/nexus_file> <destination/path>
#

# importing h5py module
import h5py

# generic modules
import sys
import os
import os.path
import re

#import pylibtiff 
from tifffile import *
import numpy as np
#import numpy for extracting tiff

# global variables
from __builtin__ import list

#>>> # to open a tiff file for writing:
#>>> tif = TIFF.open('filename.tif', mode='w')
#>>> # to write a image to tiff file
#>>> tif.write_image(image)

g_macro_names_translation = {'Ave':'data-average', 'Avv':'data-average', 'Avw':'data-average', 'Avo':'data-average', 'Av1':'clever-average-1', 'Av2':'clever-average-2', 'Av3':'clever-average-3', 'Av4':'clever-average-4', 'Av5':'clever-average-5', 'AvHPLCSample':'clever-average-hplc-sample', 'AvHPLCBuffer':'clever-average-hplc-buffer', 'AvBatchSample':'clever-average-batch-sample', 'AvBatchBuffer':'clever-average-batch-buffer', 'Sum':'sum-data', 'Sub':'sub-data', 'Merg':'merged-data(stat)', 'MergS':'merged-data(stat)', 'MergV':'merged-data(var)', 'Lin':'linear-data', 'Sca':'scaled-data', 'Div':'divide' }
g_macro_names_translation_2 = {'Average':'Ave', 'CleverAverage1':'Av1', 'CleverAverage2':'Av2', 'CleverAverage3':'Av3', 'CleverAverage4':'Av4', 'CleverAverage5':'Av5', 'CleverAverageHPLCSample':'AvHPLCSample', 'CleverAverageHPLCBuffer':'AvHPLCBuffer', 'CleverAverageBatchSample':'AvBatchSample', 'CleverAverageBatchBuffer':'AvBatchBuffer', 'Subtract':'Sub', 'Substract':'Sub', 'LinearFunction':'Lin', 'Linear':'Lin', 'Divide':'Div', 'Spectra':'' }
g_result_names = ['RadialIntensity', 'AzimuthalIntensity', 'projection', 'meanProjection', 'Z']
g_macro_short_names_translation_map = {'Average':'Ave', 'Average_Variance_Intensity':'Avv', 'Average_Weighted':'Avw', 'Average_Old':'Avo', 'AverageSpectra':'Ave', 'CleverAverage1':'Av1', 'CleverAverage2':'Av2', 'CleverAverage3':'Av3', 'CleverAverage4':'Av4', 'CleverAverage5':'Av5', 'CleverAverageHPLCSample':'AvHPLCSample', 'CleverAverageHPLCBuffer':'AvHPLCBuffer', 'CleverAverageBatchSample':'AvBatchSample', 'CleverAverageBatchBuffer':'AvBatchBuffer', 'Merg':'Merg', 'extractZ':'exZ'}
beamlines = ['AILES', 'ANATOMIX', 'ANTARES', 'CASSIOPÉE', 'CASSIOPEE', 'CRISTAL', 'DEIMOS', 'DESIRS', 'DIFFABS', 'DISCO', 'GALAXIES', 'HERMES', 'LUCIA', 'MARS', 'MÉTROLOGIE ', 'METROLOGIE ', 'NANOSCOPIUM', 'ODE', 'PLÉIADES', 'PLEIADES', 'PROXIMA-1', 'PROXIMA-2A', 'PSICHÉ', 'PSICHE', 'PUMA', 'ROCK', 'SAMBA', 'SEXTANTS', 'SIRIUS', 'SIXS', 'SMIS', 'SWING', 'TEMPO']
g_map_operation_result_param=['a','b','c','Imax','SigmaPsi','Psi0',u'\u03c3Q',u'\u03930','Gamma0','q0','SigmaQ','IntL','monitor','integration']
g_map_operation_result=['AzProfileFitLorentzianSum (LMA)','AzProfileFitGaussianSum (LMA)','azIntFitLorentzianSum (LMA)','azIntFitGaussianSum (LMA)','AzIntFitAzProfFit','monitorIntensity','integral2D']
g_file_ext = '.dat'
g_header_separator = '#####################################################################################################\n'
NODE_NAME_USER_MACROS = "userMacros"
ATTR_TARGET = 'target'
TYPE_CLEVER_AVERAGE = 'CleverAverage'
SLASH = '/'
BACKSLASH = '\\'
UNDERSCORE = '_'
PARAMETER_O_ = 'parameter_0_'

# ------------------------------------------------------------------------------


class TargetMacroParameter:
    def __init__(self):  # constructor
        self.target = None
        self.index = None
        self.macro = None
        self.ind_param = None

    def __init__(self, macro, ind_param , index):  # constructor
        self.macro = macro
        self.ind_param = ind_param
        self.index = index
        self.target = None

    def get_target_macro(self):
        if (self.macro is not None) and (self.ind_param is not None) and (self.index is not None):
            parameter = self.ind_param + str(self.index)
            self.target = get_string( self.macro[parameter].attrs[ATTR_TARGET])
        return self.target

    def get_macro(self):
        return self.macro

    def get_index(self):
        return self.index

    def get_ind_param(self):
        return self.ind_param
# ------------------------------------------------------------------------------


def try_to_print(str):
    try:
        try:
            print str
        except:
            # there was probably an encoding error
            print unicode(str).encode("utf-8")
    except IOError:
        # we just don't care for print errors
        return
# ------------------------------------------------------------------------------


def get_string(attr):
    if isinstance(attr, basestring):
        return attr
    else:
        return attr[0]
# ------------------------------------------------------------------------------


def is_number_int(var):
    try:
        z = int(var)
        return z
    except ValueError:
        return -1
# ------------------------------------------------------------------------------


def get_macro_short_name_to_list(name_find):
    macro_short_name = None
    for key in g_macro_short_names_translation_map:
        tmp = g_macro_short_names_translation_map[key]
        if name_find == key:
            macro_short_name = tmp
            break
    return macro_short_name
# ------------------------------------------------------------------------------


def get_macro_short_name(macro_name):
    macro_short_name = macro_name[:3]
    if macro_name.find("Merge") == 0:
        macro_short_name = macro_name[:4]
        if macro_name.find("Statistical_Error") > -1:
            macro_short_name += 'S'
        elif macro_name.find("Variance_Intensity") > -1:
            macro_short_name += 'V'
    elif macro_name.find("CleverAverage") == 0:
        index = macro_name.find(" ")
        name_find = macro_name[:index]
        macro_short_name = get_macro_short_name_to_list(name_find)
    elif macro_name.startswith('Average'):
        index = macro_name.rfind(UNDERSCORE)
        name_find = macro_name[:index]
        macro_short_name = get_macro_short_name_to_list(name_find)
    return macro_short_name
# ------------------------------------------------------------------------------


def is_macro_short_name(name_ref, macro_short_name):
    is_macro_short_name = False
    if (len(macro_short_name) == 3) and (macro_short_name[:2] == 'Av') :
        ind = macro_short_name[2:3]
        var = is_number_int(ind)
        if var >= 0 :
            name_key = name_ref + str(var)
            tmp = get_macro_short_name_to_list(name_key)
            if (tmp is not None) and (tmp == macro_short_name):
                is_macro_short_name = True

    elif (len(macro_short_name) > 3) and (macro_short_name[:2] == 'Av') :
        name_key = name_ref + macro_short_name[2:]
        tmp = get_macro_short_name_to_list(name_key)
        if (tmp is not None) and (tmp == macro_short_name):
            is_macro_short_name = True

    return is_macro_short_name
# ------------------------------------------------------------------------------


def write_output_results(f, intensity, sig, Q, two_theta, psi, concat_string):
    # comment next 2 lines to activate two_theta
    if Q is not None:
        two_theta = None

    if intensity is not None:
        q_name = "q(A-1)"
        two_theta_name = "2Theta(degrees)"
        index_name = "Idx"
        psi_name = "Psi(degrees)"
        i_name = "I(q)" + concat_string
        sig_name = "Sig(q)" + concat_string
        # Compute best output format (100 characters max)
        length = max(len(i_name), len(sig_name))
        x_format = '%-35s'
        y_format = '%-100s'
        for i in range(10):
            mult = (i + 1) * 10 + 5
            if length < mult and mult > 30:
                y_format = '%-' + str(mult) + 's'
                break
        if psi is not None and len(psi) == len(intensity):
            if sig is None:
                # Result titles
                format = x_format + y_format + '\n'
                f.write(format %  (psi_name, i_name))
                # Result values
                for i in range(len(psi)):
                    f.write(x_format % psi[i])
                    f.write(y_format % intensity[i])
                    f.write('\n')
            else:
                # Result titles
                format = x_format + y_format + y_format + '\n'
                f.write(format %  (psi_name, i_name, sig_name))
                # Result values
                for i in range(len(psi)):
                    f.write(x_format % psi[i])
                    f.write(y_format % intensity[i])
                    f.write(y_format % sig[i])
                    f.write('\n')
        elif Q is None:
            if two_theta is not None:
                if sig is None:
                    # Result titles
                    format = x_format + y_format + '\n'
                    f.write(format %  (two_theta_name, i_name))
                    # Result values
                    for i in range(len(two_theta)):
                        f.write(x_format % two_theta[i])
                        f.write(y_format % intensity[i])
                        f.write('\n')
                else:
                    # Result titles
                    format = x_format + y_format + y_format + '\n'
                    f.write(format %  (two_theta_name, i_name, sig_name))
                    # Result values
                    for i in range(len(two_theta)):
                        f.write(x_format % two_theta[i])
                        f.write(y_format % intensity[i])
                        f.write(y_format % sig[i])
                        f.write('\n')
            else:
                if sig is None:
                    # Result titles
                    format = x_format + y_format + '\n'
                    f.write(format %  (index_name, i_name))
                    # Result values
                    for i in range(len(intensity)):
                        f.write(x_format % i)
                        f.write(y_format % intensity[i])
                        f.write('\n')
                else:
                    # Result titles
                    format = x_format + y_format + y_format + '\n'
                    f.write(format %  (index_name, i_name, sig_name))
                    # Result values
                    for i in range(len(intensity)):
                        f.write(x_format % i)
                        f.write(y_format % intensity[i])
                        f.write(y_format % sig[i])
                        f.write('\n')
        elif two_theta is None:
            if sig is None:
                # Result titles
                format = x_format + y_format +'\n'
                f.write(format %  (q_name, i_name))
                # Result values
                for i in range(len(Q)):
                    f.write(x_format % Q[i])
                    f.write(y_format % intensity[i])
                    f.write('\n')
            else:
                # Result titles
                format = x_format + y_format + y_format + '\n'
                f.write(format %  (q_name, i_name, sig_name))
                # Result values
                for i in range(len(Q)):
                    f.write(x_format % Q[i])
                    f.write(y_format % intensity[i])
                    f.write(y_format % sig[i])
                    f.write('\n')
        else:
            if sig is None:
                # Result titles
                format = x_format + y_format + x_format + '\n'
                f.write(format %  (q_name, i_name, two_theta_name))
                # Result values
                for i in range(len(Q)):
                    f.write(x_format % Q[i])
                    f.write(y_format % intensity[i])
                    f.write(x_format % two_theta[i])
                    f.write('\n')

# ------------------------------------------------------------------------------


def write_polar_data(f, polar_data, psi, two_theta, concat_string):

    title = "Psi (degrees) / 2 Theta(degrees)"
    psi_format = '%-35s'
    # value_format = '%-25s'
    value_format = '%-35s'

    if psi is not None:
        if two_theta is not None:
            if polar_data is not None:
                f.write(psi_format % title)
                for i in range(len(two_theta)):
                    f.write(valueFormat % two_theta[i])
                f.write('\n')
                for i in range(len(psi)):
                    f.write(psi_format % psi[i])
                    for j in range(len(two_theta)):
                        f.write(value_format % polar_data[i][j])
                    f.write('\n')
# ------------------------------------------------------------------------------


def adapt_name(name):
    """
        Transforms a nexus written name into user readable one
    """
    name = name.replace('_#','').replace('#_','#').replace('#-','(').replace('-#',')').replace('___',',').replace('--__--',' ')
    return name
# ------------------------------------------------------------------------------


def adapt_name2(name):
    """
        Transforms a nexus written name into user readable one
    """
    name = name.replace('_#','}').replace('#_','{').replace('#-','(').replace('-#',')').replace('___',',').replace('--__--',' ').replace('azimuthalIntegration', 'AzInt')
    return name
# ------------------------------------------------------------------------------


def adapt_name_in_macro(name):
    """
        Transforms a name written in a macro node into a nexus name
    """
    name = name.replace('}','_#').replace('{','#_').replace('(','#-').replace(')','-#')
    return name
# ------------------------------------------------------------------------------


def adapt_acquisition_name(acqui_name):
    """
        Adapts acquisition name to file name
    """
    if acqui_name.find(SLASH) == 0:
        acqui_name = acqui_name[1:]
    if acqui_name.find(SLASH) > -1:
        acqui_name = acqui_name[:acqui_name.rfind(SLASH)]
        if acqui_name.find(SLASH) > -1:
            acqui_name = UNDERSCORE.join(acqui_name.split(SLASH))
    if acqui_name.find('_img'):
        acqui_name = acqui_name.replace('_img', '')
    if acqui_name.find('{') > -1:
        startIndex = acqui_name.find('{')
        separator2D = acqui_name.find(',')
        endIndex = acqui_name.find('}')
        if separator2D > -1:
            firstNumberImage = acqui_name[startIndex+1:separator2D]
            firstNumberImageWith5Digit = format(int(firstNumberImage),'05d')
            secondNumberImage = acqui_name[separator2D+1:endIndex]
            secondNumberImageWith5Digit = format(int(secondNumberImage),'05d')
            acqui_name = acqui_name[:startIndex+1]+firstNumberImageWith5Digit+','+secondNumberImageWith5Digit+acqui_name[endIndex:]
        else : 
            numberImage = acqui_name[startIndex+1:endIndex]
            numberImageWith5Digit = format(int(numberImage),'05d')
            acqui_name = acqui_name[:startIndex+1]+numberImageWith5Digit+acqui_name[endIndex:]
        
    return adapt_name2(acqui_name)
# ------------------------------------------------------------------------------

def adapt_acquisition_name2(acqui_name):
    """
        Adapts acquisition name to file name
    """
    if acqui_name.find(SLASH) == 0:
        acqui_name = acqui_name[1:]
    if acqui_name.find(SLASH) > -1:
        acqui_name = acqui_name[:acqui_name.rfind(SLASH)]
        if acqui_name.find(SLASH) > -1:
            acqui_name = UNDERSCORE.join(acqui_name.split(SLASH))
    if acqui_name.find('img') != -1:
        acqui_name = acqui_name.replace('img', '')
    return adapt_name2(acqui_name)
# ------------------------------------------------------------------------------


def is_effective(name):
    if name.endswith(SLASH):
        name = name[:name.rfind(SLASH)]
    if name.find(SLASH) > -1:
        tmp = name.split(SLASH)
        name = tmp[len(tmp) - 1]
    return 'Intensity' in name or 'projection' in name.lower() or 'Z' == name
# ------------------------------------------------------------------------------


def __is_path_user_macro(macro):
    is_macro = -1
    if (macro is not None):
        macro_name = macro.name.split(SLASH)[1]
        if macro_name.find(NODE_NAME_USER_MACROS) == 0 :
            is_macro = 0

    return is_macro
# ------------------------------------------------------------------------------


def __get_parent_node(node):
    node_parent = None
    if (node is not None) and (isinstance(node, h5py.Group)):
        node_parent = node.parent

    return node_parent
# ------------------------------------------------------------------------------


def __get_name_user_macro(macro):
    name = None
    if macro is not None:
        name_tmp = macro.name
        if name_tmp.find(NODE_NAME_USER_MACROS) >= 0:
            name = name_tmp.split(SLASH)[-1]

    return name
# ------------------------------------------------------------------------------


def __get_node_racine(node):
    find = -1
    node_racine = None
    if (node is not None) and (isinstance(node, h5py.Group)):
        if node.name == SLASH:
            node_racine = node
        else:
            node_find = node
            while find < 0:
                node_parent = __get_parent_node(node_find)
                if (node_parent is not None) and (isinstance(node_parent,h5py.Group)):
                    if node_parent.name == SLASH:
                        node_racine = node_parent
                        find = 0
                    else:
                        node_find = node_parent
                else:
                    find = 0

    return node_racine
# ------------------------------------------------------------------------------


def __get_target_macro_from_parameter(macro, param, index_param, str_find):
    target_macro_parameter = None
    parameter = param + str(index_param)
    target = get_string(macro[parameter].attrs[ATTR_TARGET])
    if target.find(str_find) != -1:
        index_result = None
        # from the beginning
        target_ref = target[:target.rfind(str_find)]
        # until the end
        result_ref = target[target.find(str_find):]
        if result_ref.find(str_find) != -1:
            ind = result_ref.rfind(str_find)
            index_result = result_ref[ind + len(str_find):]
        grp_parent = macro.parent
        for tmp in grp_parent.values():
            if tmp.name == target_ref:
                target_macro_parameter = TargetMacroParameter(tmp, param, index_result)
                break;

    return target_macro_parameter
# ------------------------------------------------------------------------------


def __get_all_macros_parameter_from_macro(macro_ref, param, index, str_find, with_ref):
    find_param = -1
    parameter = param + str(index)
    list_macros_param = None
    index_param = index
    if (macro_ref is not None) and (parameter is not None) and (str_find is not None) and (__is_path_user_macro(macro_ref) != -1 ):
        if with_ref:
            list_macros_param = list()
            target_macro_parameter = TargetMacroParameter(macro_ref, param, index_param)
            if target_macro_parameter is not None:
                list_macros_param.append(target_macro_parameter)
        while find_param < 0:
            target_macro_parameter = __get_target_macro_from_parameter(macro_ref, param, index_param, str_find)
            if target_macro_parameter is not None:
                macro_result = target_macro_parameter.get_macro()
                if macro_result is not None:
                    if isinstance(macro_result, h5py.Group):
                        if (list_macros_param is None):
                            list_macros_param = list()
                        list_macros_param.append(target_macro_parameter)
                        macro_ref = macro_result
                        index_param = target_macro_parameter.get_index()
            else:
                find_param = 0

    return list_macros_param

# ------------------------------------------------------------------------------


def __get_parameter_operation_from_macro(macro, parameter):
    ope_find = None
    if (macro is not None) and (parameter is not None):
        target_items = get_string(macro[parameter].attrs[ATTR_TARGET]).split(SLASH)
        acqui = target_items[1]
        image = target_items[2]
        if image.find('img') != -1 :
            full_path = SLASH + acqui + SLASH + image
            racine = __get_node_racine(macro)
            if isinstance(racine, h5py.Group):
                tmp = racine.get(full_path)
                if isinstance(tmp, h5py.Group):
                    ope_find = tmp

    return ope_find

# ------------------------------------------------------------------------------


def    __adapt_name_file_from_param_node(macro_ref, param, index, with_full_operation_path):
    name_file = ''
    ope_result = None
    parameter = param + str(index)
    result = '/result_0_'

    list_macros_param = __get_all_macros_parameter_from_macro(macro_ref, param, index, result, True)
    if (list_macros_param is not None) and (len(list_macros_param) > 0):
        last_macro_parameter = list_macros_param[-1]
        if isinstance(last_macro_parameter, TargetMacroParameter):
            lastMacro = last_macro_parameter.get_macro()
            ind_param = last_macro_parameter.get_ind_param()
            index_param = last_macro_parameter.get_index()
            if len(list_macros_param) > 1:
                ope_result = __get_parameter_operation_from_macro(lastMacro, ind_param + str(index_param))
            else:
                ope_result = __get_parameter_operation_from_macro(lastMacro, parameter)
    else:
        list_macros_param = list()
        target_macro_parameter = TargetMacroParameter(macro_ref, param, index)
        list_macros_param.append(target_macro_parameter)
        ope_result = __get_parameter_operation_from_macro(macro_ref, param + str(index))

    if ope_result is not None:
        if isinstance(ope_result, h5py.Group):
            targetItems = get_string(ope_result.name).split(SLASH)
            acqui = targetItems[1]
            image = adapt_acquisition_name2(targetItems[2])
            if(with_full_operation_path is False):
                image = image[image.find('_')+1:image.rfind('_')]

    if (list_macros_param is not None) and (len(list_macros_param) > 0):
        for i in range(0, len(list_macros_param)):
            macro_parameter = list_macros_param[i]
            if isinstance(macro_parameter, TargetMacroParameter) and (macro_parameter is not None):
                nodeTmp = macro_parameter.get_macro()
                if isinstance(nodeTmp, h5py.Group):
                    nameMacro = __get_name_user_macro(nodeTmp)
                    if nameMacro is not None:
                        macroShortName = get_macro_short_name(nameMacro)
                        if i == 0 :
                            #indiceMacro...
                            if macroShortName is not None:
                                name_file = macroShortName + nameMacro[nameMacro.rfind(UNDERSCORE):] + UNDERSCORE
                            else:
                                name_file = nameMacro[nameMacro.rfind(UNDERSCORE):] + UNDERSCORE
                        else:
                            name_file = name_file + macroShortName + nameMacro[nameMacro.rfind(UNDERSCORE):] + UNDERSCORE

        name_file = name_file + acqui + image

    return name_file

# ------------------------------------------------------------------------------


def write_multi_results_user_macro(macro, path, file_name, version, full_name, index, intensity, sigma, Q, two_theta, psi, result_name, persisting,scalar_parameters):
    """
        Writes macro results and parameters in a file
    """
    macro_name = macro.name.split(SLASH)[-1]
    if (intensity is not None) and (file_name is not None):
        # opening output file
        f = open(path + file_name + g_file_ext, 'w')
        try_to_print("Writing " + file_name + g_file_ext)
        f.write(g_header_separator)
        f.write('# %-50s%s\n' % ("_diffrn_sofware", version))
        f.write('# %s\n' % adapt_name2(full_name))
        f.write('# %s\n' % g_macro_names_translation[get_macro_short_name(macro_name)])

        # Write persisting data
        if persisting != "":
            f.write(persisting)

        # write scalar parameters if any
        if scalar_parameters != "":
            f.write(scalar_parameters)
        if result_name is not None:
            f.write('# %s\n' % 'Result name:')
            f.write('# %s\n' % result_name)
            f.write(g_header_separator)
        write_output_results(f, intensity, sigma, Q, two_theta, psi, file_name)

# ------------------------------------------------------------------------------


def process_multi_results_user_macro(file_handle, macro, path, version, first_scalar_parameter_index):
    """
        The subtract macro consists of subtracting one image results
        (which may represent a dark signal) from one (or more) other image results

        parameters_0_x refer to results to be corrected
        parameters_1_y refer to results to be subtracted

        The result triplet of this macro is:
            parameters_0_<3i>   - parameter_1_0
            parameters_0_<3i+1> - parameter_1_1
            parameters_0_<3i+2> - parameter_1_2
        with i in range [0, number of images to be corrected[
    """
    try_to_print("Processing user macro '%s'" % macro.name)
    persisting = get_persisting_data(macro)
    scalar_parameters = get_scalar_parameters(macro,first_scalar_parameter_index)

    # op_name = None
    full_name = None
    file_name = None
    intensity = None
    sigma = None
    Q = None
    two_theta = None
    psi = None
    save_result_name = None
    macro_name = macro.name.split(SLASH)[-1]

    # one output file for each result triplet or quadruplet
    for result_index in range(macro['result_0_count'][0]):
        if check_dataset(macro, 'result_0_' + str(result_index)):
            node = macro['result_0_' + str(result_index)]
            if 'name' in node.attrs:
                result_name = get_string(node.attrs['name'])
            elif check_dataset(macro, 'result_0_' + str(result_index) + '_name'):
                result_name = macro['result_0_' + str(result_index) + '_name'][0]
            else:
                result_name = ''
            index = result_name.index('(')
            if index > -1:
                typeName = result_name[:index]

            if is_effective(typeName):
                # a new intensity value is found --> we have all necessary values to write previous file
                write_multi_results_user_macro(macro, path, file_name, version, full_name, first_scalar_parameter_index, intensity, sigma, Q, two_theta, psi, save_result_name, persisting, scalar_parameters)
                intensity = node
                # JIRA (DATAREDUC-563)
                file_name = __adapt_name_file_from_param_node(macro, PARAMETER_O_, result_index, True)
                full_name = file_name
                save_result_name = result_name
                sigma = None
                Q = None
                two_theta = None
            elif typeName.endswith('twoTheta'):
                two_theta = node
            elif typeName.endswith('Sigma'):
                sigma = node
            elif typeName.endswith('psi'):
                psi = node
            elif typeName.endswith('Q'):
                Q = node

    write_multi_results_user_macro(macro, path, file_name, version, full_name, first_scalar_parameter_index, intensity, sigma, Q, two_theta, psi, save_result_name, persisting, scalar_parameters)

# ------------------------------------------------------------------------------


def write_single_result_user_macro_file(file_handle, path, macro, macro_short_same, res_name, intensity, sigma, Q, two_theta, psi, version, persisting):
    param_count = macro['parameter_0_count'][0]

    #JIRA (DATAREDUC-563)
    file_name = __adapt_name_file_from_param_node(macro, PARAMETER_O_, 0, False)

    try_to_print("Writing " + file_name + g_file_ext)

    if file_name is not None:
        # opening output file
        f = open(path + file_name + g_file_ext, 'w')

        f.write(g_header_separator)
        f.write('# %-50s%s\n' % ("_diffrn_sofware", version))
        f.write('# %s\n' % g_macro_names_translation[macro_short_same])

        # Write persisting data
        if persisting != "":
            f.write(persisting)

        # Write source images names
        # get total number of input parameters
        param_count = macro['parameter_0_count'][0]
        f.write('# Input parameters:\n')

        # writes input image names
        for i in range(param_count):
            param_name = PARAMETER_O_ + str(i)
            param_item = macro[param_name]
            item_path = get_string(param_item.attrs['path'])
            check_res = False
            if item_path.endswith(res_name):
                item_path = item_path.split(SLASH)[2]
                f.write('# %s\n' % adapt_name2(item_path))
            elif 'name' in param_item.attrs:
                item_path = get_string(param_item.attrs['name'])
                check_res = True
            elif 'path' in param_item.attrs:
                tmp = get_string(param_item.attrs['path'])
                if check_dataset(file_handle, tmp + '_name'):
                    item_path = file_handle[tmp + '_name'][0]
                    check_res = True
            if check_res:
                if item_path.startswith(res_name):
                    item_path = item_path.replace(res_name, '')
                    f.write('# %s\n' % adapt_name2(item_path))

        # write parameter_0 name attribute splitting each line
        if 'name' in macro['parameter_0'].attrs:
            to_split = get_string(macro['parameter_0'].attrs['name'])
        elif 'path' in macro['parameter_0'].attrs:
            tmp = get_string(macro['parameter_0'].attrs['path'])
            if check_dataset(file_handle, tmp + '_name'):
                to_split = file_handle[tmp + '_name'][0]
            else:
                to_split = ''
        else:
            to_split = ''
        for l in to_split.split('\n'):
            if l.startswith(res_name):
                f.write('# %s\n' % l)
        f.write('#\n')

        # write filtered input if any
        if is_macro_short_name(TYPE_CLEVER_AVERAGE, macro_short_same):
            if check_dataset(macro, 'result_1_count'):
                result_count = macro['result_1_count'][0]
                f.write('# Filtered Input:\n')
                for i in range(result_count):
                    param_name = 'result_1_' + str(i)
                    param_item = macro[param_name]
                    item_path = get_string(param_item.attrs['path'])
                    check_res = False
                    if item_path.endswith(res_name):
                        item_path = item_path.split(SLASH)[2]
                        f.write('# %s\n' % adapt_name2(item_path))
                    elif 'name' in param_item.attrs:
                        item_path = get_string(param_item.attrs['name'])
                        check_res = True
                    elif 'path' in param_item.attrs:
                        tmp = get_string(param_item.attrs['path'])
                        if check_dataset(file_handle, tmp + '_name'):
                            item_path = file_handle[tmp + '_name'][0]
                            check_res = True
                    if check_res:
                        if item_path.startswith(res_name):
                            item_path = item_path.replace(res_name,'')
                            f.write('# %s\n' % adapt_name2(item_path))
                f.write('#\n')

        # write scalar parameters if any
        count = 0
        if check_dataset(macro, 'parameter_count'):
            count = macro['parameter_count'][0]
        for i in range(1, count):
            param_name = 'parameter_' + str(i)
            param_short_name = param_name + '_displayShortName'
            if check_dataset(macro, param_name) and check_dataset(macro, param_short_name):
                param_item = macro[param_name]
                readName = ''.join(macro[param_short_name])
                f.write('# %-50s%s\n' % (readName, str(param_item[0])))
        f.write(g_header_separator)

        # Write results data
        write_output_results(f, intensity, sigma, Q, two_theta, psi, file_name)

# ------------------------------------------------------------------------------


def process_single_result_user_macro(file_handle, macro, path, version):

    # Calculates file name
    macro_name = macro.name.split(SLASH)[-1]
    try_to_print("Processing user macro '%s'" % macro_name)
    macro_short_same = get_macro_short_name(macro_name)
    # Result data
    result_count = macro['result_0_count'][0]
    res_name = None
    intensity = None
    sigma = None
    Q = None
    two_theta = None
    psi = None
    persisting = get_persisting_data(macro)

    # Check results to find how many files to write
    for resIndex in range(result_count):
        currentResult = macro['result_0_' + str(resIndex)]
        if 'name' in currentResult.attrs:
            node_name = get_string(currentResult.attrs['name'])
        elif check_dataset(macro, 'result_0_' + str(resIndex) + '_name'):
            node_name = macro['result_0_' + str(resIndex) + '_name'][0]
        else:
            node_name = ''
        try:
            index = node_name.index('(')
        except:
            index = -1
        if index > -1:
            node_name = node_name[:index]
        if node_name.startswith('Q'):
            Q = currentResult
        elif node_name.startswith('psi'):
            psi = currentResult
        elif node_name.startswith('Sigma'):
            sigma = currentResult
        elif node_name.startswith('twoTheta'):
            two_theta = currentResult
        else:
            for tmpName in g_result_names:
                if node_name.startswith(tmpName):
                    if res_name is not None:
                        write_single_result_user_macro_file(file_handle, path, macro, macro_short_same, res_name, intensity, sigma, Q, two_theta, psi, version, persisting)
                    res_name = tmpName
                    intensity = currentResult
                    sigma = None
                    Q = None
                    two_theta = None
                    psi = None
                    break

    if res_name is not None:
        write_single_result_user_macro_file(file_handle, path, macro, macro_short_same, res_name, intensity, sigma, Q, two_theta, psi, version, persisting)

# ------------------------------------------------------------------------------


def process_single_user_macro(file_handle, macro, path, version):
    macro_name = macro.name.split(SLASH)[-1]
    # macro with 1 spectrum list input
    if macro_name.find("Sum") == 0 or macro_name.find("Average") == 0 or macro_name.find("CleverAverage") == 0  or macro_name.find("Merge") == 0  or macro_name.find("Divide") == 0:
        process_single_result_user_macro(file_handle, macro, path, version)

    # macro with 2 spectrum lists input
    elif macro_name.find("Subtract") == 0 or macro_name.find("Substract") == 0 or macro_name.find("Linear") == 0:
        process_multi_results_user_macro(file_handle, macro, path, version, 0)
    elif macro_name.find("Scaling") == 0:
        process_multi_results_user_macro(file_handle, macro, path, version, 2)

# ------------------------------------------------------------------------------


def process_user_macros(file_handle, macros_group, path, version):
    try_to_print("Processing " + macros_group.name)
    try_to_print(macros_group)
    # iterate other macros
    for macro in macros_group.values():
        if isinstance(macro, h5py.Group):
            process_single_user_macro(file_handle, macro, path, version)

# ------------------------------------------------------------------------------


def check_dataset(op_group, dataset_name):
    return dataset_name in op_group
    
def extractMapImage(op_group,f,dimX,dimZ):
    print("start extract map image")
    isImage = False
    for mapOp in g_map_operation_result:
        for mapParam in g_map_operation_result_param:
            mapName = 'map_'+mapOp+'_'+mapParam
            if check_dataset(op_group,mapName):
                map_data = get_dataset(op_group,mapName)
                if map_data is not None:
                    isImage = True
                    for j in range(dimZ):
                        f.write('\n')
                        for i in range(dimX-1):
                            f.write('%s,'     % (format(map_data[j][i], '.5E')))
                        f.write('%s'     % (format(map_data[j][dimX-1], '.5E')))
    return isImage
 

def saveAsTiff(op_group,path,nexus_file_name,op_name,ijmeta):
    for mapOp in g_map_operation_result:
        for mapParam in g_map_operation_result_param:
            mapName = 'map_'+mapOp+'_'+mapParam
            if check_dataset(op_group,mapName):
                # Intensity value range
                val_range = np.arange(256, dtype=np.uint8)
                # Gray LUT
                lut_gray = np.stack([val_range, val_range, val_range])
                # Red LUT
                lut_red = np.zeros((3, 256), dtype=np.uint8)
                lut_red[0, :] = val_range
                # Green LUT
                lut_green = np.zeros((3, 256), dtype=np.uint8)
                lut_green[1, :] = val_range
                # Blue LUT
                lut_blue = np.zeros((3, 256), dtype=np.uint8)
                lut_blue[2, :] = val_range
                ijmeta = {'LUTs': [lut_gray, lut_red, lut_green, lut_blue], 'unit':'mm'}
                
                ijmeta.update({'NeXus source file' : nexus_file_name})
                ijmeta.update({'NxData' : op_name})
                #imsave('map-psi.tif',im_3frame,resolution = (1./10, 1./10),metadata={'mode':'composite'},imagej=True,ijmetadata=ijmeta,)
                map_data = get_dataset(op_group,mapName)
                grid_x = get_dataset(op_group,'gridX')
                origin_x = grid_x[0]
                ij_origin_x = origin_x/10
                nbPixelX = np.array(grid_x).size
                grid_y = get_dataset(op_group,'gridZ')
                origin_y = grid_y[0]
                ij_origin_y = origin_y/10
                nbPixelY = np.array(grid_y).size
                dataset_steps = get_dataset(op_group,'steps')
                steps = np.array(dataset_steps,dtype='<f')
                value = np.array(map_data,dtype='<f')
                #(256,'d',1,nbPixelX),(257,'d',1,nbPixelY)
                op_name = op_group.name.split(SLASH)[-1]
                fileName = path+op_name[1:]+ '.tif'
                # Save tiff file
                imsave(fileName,value,metadata={'mode':'composite'},resolution = (10/steps[0], 10/steps[1]),imagej=True,ijmetadata=ijmeta,extratags=[(296,'d',1,3,True),(286,'d',1,ij_origin_x,True),(287,'d',1,ij_origin_y,True)],)
                
# ------------------------------------------------------------------------------


def get_persisting_data(group):
    persisting = ""
    if group is not None:
        if isinstance(group, h5py.Group):
            try_to_print("writing persisting data for '" + group.name + "'")
            for node in group.values():
                try:
                    if 'persisting' in node.attrs or 'persistingHeader' in node.attrs:
                        persisting = persisting + ('# %-50s%s\n'     % (node.name.split(SLASH)[-1], node[0]))
                except:
                    continue
    return persisting
# ------------------------------------------------------------------------------

def get_scalar_parameters(macro,index):
    scalar_parameters = ""
    if macro is not None:
        count = 0
        if check_dataset(macro, 'parameter_count'):
            count = macro['parameter_count'][0]
        for i in range(index, count):
            param_name = 'parameter_' + str(i)
            param_display_name = param_name + '_displayName'
            if check_dataset(macro, param_name) and check_dataset(macro, param_display_name):
                param_item = macro[param_name]
                readName = ''.join(macro[param_display_name])
                if (readName != 'Sample') and (readName != 'Buffer'):
                    scalar_parameters = scalar_parameters + ('# %-50s%s\n' % (readName, str(param_item[0])))

    return scalar_parameters
# ------------------------------------------------------------------------------


def process_persisting_data(group, f):
    persisting = get_persisting_data(group)
    if persisting != "":
        f.write(persisting)

# ------------------------------------------------------------------------------


def process_single_operation(acq, op, path, nexus_file_name, version):
    try_to_print("Processing operation " + op.name)
    op_name = op.name.split(SLASH)[-1]
    acq_name = acq.name.split(SLASH)[-1]
    ijmeta = {}
    gridX = []
    gridZ = []

    # look for operation names containing the string '_rad_', '_circles_', '_tiltedCircles_' or 'Polar2ThetaPsi'
    polar_2_theta_psi = False
    mapOperation = False
    if op_name.find('_rad_') > 0:
        sub_string = op_name
    # compatibility with circle gathering
    elif op_name.find('_circles_') > 0:
        sub_string = op_name
    # compatibility with azimuthal integration
    elif op_name.find('_azimuthalIntegration_') > 0:
        sub_string = op_name.replace('azimuthalIntegration', 'AzInt')
    # compatibility with tilted circle gathering (former name of azimuthal integration)
    elif op_name.find('_tiltedCircles_') > 0:
        sub_string = op_name
    # compatibility with azimuthal profile
    elif op_name.find('_azimAt') > 0:
        sub_string = op_name
    # compatibility with mean projection
    elif op_name.find('_meanProjection') > 0:
        sub_string = op_name
    # compatibility with projection
    elif op_name.find('_projection') > 0:
        sub_string = op_name
    # compatibility with polar extractZ psi
    elif op_name.find('extractZ') > -1:
        sub_string = op_name
    # compatibility with polar 2theta psi
    elif op_name.find('Polar2ThetaPsi') > -1:
        sub_string = op_name
        polar_2_theta_psi = True
    elif op_name.find('map') >-1:
        sub_string = op_name
        mapOperation = True
    else:
        sub_string = ''


    detector = "null"
    if check_dataset(acq, 'detector'):
        detector = ''.join(acq['detector'])
    if detector == "null":
        detector = "not specified"

    origin = "Not specified"
    if check_dataset(acq, 'origin'):
        origin = ''.join(acq['origin'])
        for tmpName in beamlines:
            if origin == tmpName:
                origin = "Storage Ring SOLEIL, beamline " + tmpName
                break

    comment = ""
    if check_dataset(acq, 'comment'):
        comment = ''.join(acq['comment'])

    if sub_string != '':
        # compute output file name
        if polar_2_theta_psi:
            file_name = acq_name + UNDERSCORE + sub_string.replace('#img', '')
            file_name = UNDERSCORE.join(file_name.split(UNDERSCORE)[:-1])          
        elif mapOperation:
            file_name = sub_string
        else:
            file_name = adapt_acquisition_name(acq_name + UNDERSCORE + sub_string)
        #            file_name = acq_name + UNDERSCORE + op_name[:re.compile(sub_string).search(op_name).end()]
        #            file_name = file_name.replace('img', sub_string)
        #            file_name = file_name[:re.compile(UNDERSCORE + sub_string+ '$').search(file_name).start()].replace('#_', '{').replace('_#', '}')

        # opening output file
        f = open(path + file_name + g_file_ext, 'w')
        try_to_print("Writing " + file_name + g_file_ext)
        
        f.write(g_header_separator)
        f.write('# NeXus source file: %s\n' % nexus_file_name)
        op_name = op_name.encode('utf-8')
        f.write('# NXdata: name = %-50s\n#\n#\n' % op_name)
        ijmeta.update({'NeXus source file' : nexus_file_name})
        ijmeta.update({'NxData' : op_name})

        if check_dataset(op, 'twoThetaValue') and check_dataset(op, 'twoThetaDelta'):
            f.write('# %-50s%s +- %s\n' % ("2Theta (degrees)", op['twoThetaValue'][0], op['twoThetaDelta'][0]))
        if check_dataset(op, 'qValue') and check_dataset(op, 'qDelta'):
            f.write('# %-50s%s +- %s\n' % ("Q (A-1)", op['qValue'][0], op['qDelta'][0]))

        if check_dataset(op, 'exposureTime'):
            f.write('# %-50s%s + %s\n' % ("Exposure_msec", op['exposureTime'][0], op['shutterCloseDelay'][0]))
            ijmeta.update({'Exposure_msec': op['exposureTime'][0] + op['shutterCloseDelay'][0]})
        f.write('# %-50s%s\n'     % ("_diffrn_source.source", origin))
        ijmeta.update({'_diffrn_source.source':origin})
        f.write('# %-50s%s\n'     % ("_diffrn_sofware", version))
        ijmeta.update({'_diffrn_sofware':version})
        f.write('# %-50s%s\n'     % ("_diffrn_detector.detector", detector))
        ijmeta.update({'_diffrn_detector.detector':detector})
        f.write('# %-50s%s\n'     % ("_diffrn_comment.comment", comment))
        ijmeta.update({'_diffrn_comment.comment':comment})
        if check_dataset(op, 'image'):
            f.write('# %-50s%s\n'     % ("_sas_detc.pixnum_ax", op['image'].attrs['dimX']))
            f.write('# %-50s%s\n'     % ("_sas_detc.pixnum_eq", op['image'].attrs['dimY']))
            ijmeta.update({'_sas_detc.pixnum_ax':op['image'].attrs['dimX']})
            ijmeta.update({'_sas_detc.pixnum_eq':op['image'].attrs['dimY']})
        if check_dataset(op, 'pixelSize'):
            f.write('# %-50s%s\n'     % ("_sas_detc.pixsize", op['pixelSize'][0]))
            ijmeta.update({'_sas_detc.pixsize':op['pixelSize'][0]})
        if check_dataset(op, 'bin'):
            f.write('# %-50s%s\n'     % ("_sas_detc.binning", op['bin'][0]))
            ijmeta.update({'_sas_detc.binning':op['bin'][0]})
        if check_dataset(op, 'x0'):
            f.write('# %-50s%s\n'     % ("_sas_detc.center_ax", op['x0'][0]))
            ijmeta.update({'_sas_detc.center_ax':op['x0'][0]})
        if check_dataset(op, 'z0'):
            f.write('# %-50s%s\n'     % ("_sas_detc.center_eq", op['z0'][0]))
            ijmeta.update({'_sas_detc.center_eq':op['z0'][0]})
        if check_dataset(op, 'distance'):
            f.write('# %-50s%s\n'     % ("_sas_detc.dist_spec/detc", op['distance'][0]))
            ijmeta.update({'_sas_detc.dist_spec/detc':op['distance'][0]})
        if check_dataset(acq, 'energy'):
            f.write('# %-50s%s\n'     % ("_sas_detc.energy", acq['energy'][0]))
            ijmeta.update({'_sas_detc.energy':acq['energy'][0]})
        if check_dataset(op, 'gain'):
            f.write('# %-50s%s\n'     % ("_sas_detc.gain", op['gain'][0]))
            ijmeta.update({'_sas_detc.gain':op['gain'][0]})
        if check_dataset(op, 'detectorBasedUncertainty'):
            f.write('# %-50s%s\n'     % ("_sas_detc.detector_based_uncertainty", op['detectorBasedUncertainty'][0]))
            ijmeta.update({'_sas_detc.detector_based_uncertainty':op['detectorBasedUncertainty'][0]})
        if check_dataset(op, 'encoderDelta'):
            f.write('# %-50s%s\n'     % ("_sas_detc.encoder_delta", op['encoderDelta'][0]))
            ijmeta.update({'_sas_detc.encoder_delta':op['encoderDelta'][0]})
        if check_dataset(op, 'delta0'):
            f.write('# %-50s%s\n'     % ("_sas_detc.delta0", op['delta0'][0]))
            ijmeta.update({'_sas_detc.delta0':op['delta0'][0]})
        if check_dataset(op, 'gamma0'):
            f.write('# %-50s%s\n'     % ("_sas_detc.gamma_offset", op['gamma0'][0]))
            ijmeta.update({'_sas_detc.gamma_offset':op['gamma0'][0]})
        if check_dataset(op, 'lambda'):
            f.write('# %-50s%s\n'     % ("_sas_detc.wave_length", op['lambda'][0]))
            ijmeta.update({'_sas_detc.wave_length':op['lambda'][0]})
        if check_dataset(op, 'useBias'):
            use_bias = 'false'
            if op['useBias'][0] == 1:
                use_bias = 'true'
            f.write('# %-50s%s\n'     % ("_sas_detc.bias.sub", use_bias))
            ijmeta.update({'_sas_detc.bias.sub':use_bias})
        if check_dataset(op, 'bias'):
            f.write('# %-50s%s\n'     % ("_sas_detc.bias", op['bias'][0]))
            ijmeta.update({'_sas_detc.bias':op['bias'][0]})
        if check_dataset(op, 'sector'):
            sector = op['sector'][0]
            # Extractor is only compatible with AngularSector
            if sector.find('AngularSector') > -1:
                indexX0 = sector.find('x0')
                indexSeparatorX0 = sector.find(';',indexX0)
                indexZ0 = sector.find('z0')
                indexSeparatorZ0 = sector.find(';',indexZ0)
                indexMeanPsi = sector.find('Mean_Psi')
                indexSeparatorMeanPsi = sector.find(';',indexMeanPsi)
                indexAngle = sector.find('Delta_Psi')
                f.write('# %-50s%s\n'     % ("_sas_detc.sector_type", "angular"))
                f.write('# %-50s%s\n'     % ("_sas_detc.sector_x", sector[indexX0+3:indexSeparatorX0]))
                f.write('# %-50s%s\n'     % ("_sas_detc.sector_z", sector[indexZ0+3:indexSeparatorZ0]))
                f.write('# %-50s%s\n'     % ("_sas_detc.sector_angle", sector[indexMeanPsi+9:indexSeparatorMeanPsi]))
                f.write('# %-50s%s\n'     % ("_sas_detc.sector_width", sector[indexAngle+10:]))
                ijmeta.update({'_sas_detc.sector_type':"angular"})
                ijmeta.update({'_sas_detc.sector_x':sector[indexX0+3:indexSeparatorX0]})
                ijmeta.update({'_sas_detc.sector_z':sector[indexZ0+3:indexSeparatorZ0]})
                ijmeta.update({'_sas_detc.sector_angle':sector[indexMeanPsi+9:indexSeparatorMeanPsi]})
                ijmeta.update({'_sas_detc.sector_width':sector[indexAngle+10:]})
        if check_dataset(op, 'mask'):
            if 'name' in op['mask'].attrs:
                f.write('# %-50s%s\n'     % ("_sas_detc.mask", get_string(op['mask'].attrs['name'])))
                ijmeta.update({'_sas_detc.mask':get_string(op['mask'].attrs['name'])})
            elif check_dataset(op, 'mask_name'):
                f.write('# %-50s%s\n'     % ("_sas_detc.mask", op['mask_name'][0]))
                ijmeta.update({'_sas_detc.mask':op['mask_name'][0]})
        is_normalized = 'false'
        if check_dataset(op, 'normalize') and op['normalize'][0] == 1:
            is_normalized = 'true'
        if check_dataset(op, 'isNormalized') and  op['isNormalized'][0] == 1:
            is_normalized = 'true'
        if not polar_2_theta_psi:
            f.write('# %-50s%s\n'     % ("Normalization", is_normalized))
            ijmeta.update({'Normalization':is_normalized})
        if check_dataset(op, 'normalizationCount'):
            count = op['normalizationCount'][0]
        else:
            count = 0
        global_coeff = check_dataset(op, 'coefficient')
        if count > 0:
            f.write('# %-50s%s\n'     % ("Normalization Count", count))
            f.write('# %-50s%s\n'     % ("Normalization Factor", op['normalizationFactor'][0]))
            ijmeta.update({'Normalization':count})
            ijmeta.update({'Normalization Factor':op['normalizationFactor'][0]})
            if global_coeff:
                f.write('# %-50s%s\n'     % ("GlobalNormalization", op['coefficient'][0]))
                ijmeta.update({'GlobalNormalization':op['coefficient'][0]})
            for i in range (count):
                f.write('# %-50s%s\n'     % ("Normalization Monitor " + str(i), op['mi' + str(i)][0]))
                ijmeta.update({'Normalization Monitor'+ str(i):op['mi' + str(i)][0]})
                if check_dataset(op, "miIntensity" + str(i)):
                    f.write('# %-50s%s\n'     % ("Normalization Intensity " + str(i), op['miIntensity' + str(i)][0]))
                    ijmeta.update({'Normalization Intensity'+ str(i):op['miIntensity' + str(i)][0]})
        else:
            if check_dataset(op, 'normalizationFactor'):
                f.write('# %-50s%s\n'     % ("Normalization Factor", op['normalizationFactor'][0]))
                ijmeta.update({'Normalization Factor':op['normalizationFactor'][0]})
            if check_dataset(op, 'mi'):
                f.write('# %-50s%s\n'     % ("Normalization Monitor", op['mi'][0]))
                ijmeta.update({'Normalization Monitor':op['mi'][0]})
            if check_dataset(op, 'miIntensity'):
                f.write('# %-50s%s\n'     % ("Normalization Intensity", op['miIntensity'][0]))
                ijmeta.update({'Normalization Intensity':op['miIntensity'][0]})
            if global_coeff:
                f.write('# %-50s%s\n'     % ("GlobalNormalization", op['coefficient'][0]))
                ijmeta.update({'GlobalNormalization':op['coefficient'][0]})

        if check_dataset(op, 'roi'):
            # roi : may be on several lines
            roi = op['roi'][0]
            roi_list = roi.split('\n')
            f.write('# %-50s%s\n'     % ("Roi=", roi_list[0]))
            for i in range (1, len(roi_list)):
                f.write('# %-50s%s\n'     % ("", roi_list[i]))
        
        if check_dataset(op,'gridX'):
        # exporting grid X of map operations
            gridX = get_dataset(op,'gridX')
            f.write('# %-50s'     % ("Grid X"))
            for i in range(len(gridX)):
                f.write('%s;'     % ((format(gridX[i], '.5f'))))  
            f.write('\n')
        
        if check_dataset(op,'gridZ'):
        # exporting grid Z of map operations
            gridZ = get_dataset(op,'gridZ')
            f.write('# %-50s'     % ("Grid Z"))
            for i in range(len(gridZ)):
                f.write('%s;'     % ((format(gridZ[i], '.5f'))))  
            f.write('\n') 
            
            
            
        saveAsTiff(op,path,nexus_file_name,op_name,ijmeta)

        # Write persisting data
        process_persisting_data(op, f)

        f.write(g_header_separator)
        two_theta = get_dataset(op, 'twoTheta')
        psi = get_dataset(op, 'psi')
        
        if polar_2_theta_psi:
            polar_data = op['polarData'];
            write_polar_data(f, polar_data, psi, two_theta, adapt_name(file_name))
        else:
            # Result data
            intensity = None
            for node in op.values():
                if isinstance(node, h5py.Dataset):
                    if 'name' in node.attrs:
                        node_name = get_string(node.attrs['name'])
                    elif check_dataset(op, node.name + '_name'):
                        node_name = op[node.name + '_name'][0]
                    else:
                        node_name = ''
                    if is_effective(node_name):
                        intensity = node
                        break
            sigma = get_dataset(op, 'Sigma')
            Q = get_dataset(op, 'Q')
            # Write results data
            isImage = extractMapImage(op, f,len(gridX),len(gridZ))
            if not isImage:
                write_output_results(f, intensity, sigma, Q, two_theta, psi, adapt_name(file_name))
# ------------------------------------------------------------------------------


def get_dataset(node, dataset_name, default=None):
    try:
        return node[dataset_name]
    except KeyError:
        return default


# ------------------------------------------------------------------------------


def process_operations(op_group, path, nexus_file_name, version):
    try_to_print("Processing {}".format(op_group))

    # iterate other operations
    for op in op_group.values():
        if isinstance(op, h5py.Group):
            process_single_operation(op_group, op, path, nexus_file_name, version)
# ------------------------------------------------------------------------------


def process_file(nexus_file, path, path_to_group=None):
    try_to_print("Processing file " + nexus_file)

    # creating destination path if needed
    #nexus_file_name = os.path.splitext(nexus_file)[0]
    nexus_complete_file_name = os.path.split(nexus_file)[1]
    nexus_file_name = os.path.splitext(nexus_complete_file_name)[0]

    if os.sep == BACKSLASH:
        path = path.replace(BACKSLASH, SLASH)
        try_to_print("os path is " + BACKSLASH)

    full_path = path.rstrip('/\\') + SLASH + nexus_file_name + SLASH
    try_to_print("full path: " + full_path)
    if not os.path.exists (full_path):
        os.makedirs (full_path)

    try:
        file_handle = h5py.File(nexus_file, 'r')
    except IOError as e:
        try_to_print("cannot open " + path + SLASH + nexus_complete_file_name + ":\n" + str(e))
        return
    except:
        try_to_print('wrong hdf5 file:\n' + str(sys.exc_info()[0]))
        return

    try:
        version = get_string(file_handle.attrs['creator'])
    except KeyError:
        version = "FOXTROT"

    if path_to_group is None:
        # iterate other NXentry groups
        for name, grp in file_handle.items():
            if isinstance(grp, h5py.Group):
                if name == 'userMacros':
                    process_user_macros(file_handle, grp, full_path, version)
                else:
                    # operation group (first level macro)
                    process_operations(grp, full_path, nexus_file_name, version)

    else:
        # Third parameter gives the operation to process
        acq_name = path_to_group[:path_to_group.rfind(SLASH)].lstrip(SLASH)
        try:
            op = file_handle[SLASH + path_to_group]
            acq = file_handle[SLASH + acq_name]
        except Exception:
            try_to_print("Node %s not found" % acq_name)
        else:
            if acq_name == 'userMacros':
                process_single_user_macro(file_handle, op, full_path, version)
            else:
                # operation group (first level macro)
                process_single_operation(acq, op, full_path, nexus_file_name, version)

# ------------------------------------------------------------------------------
# Entry point
# ------------------------------------------------------------------------------


if __name__ == "__main__":
    if len(sys.argv) < 3:
        try_to_print("Input file name and destination directory are required.")
    else:
        if len(sys.argv) == 3:
            process_file(sys.argv[1], sys.argv[2])
        else:
            process_file(sys.argv[1], sys.argv[2], sys.argv[3])