Exemple #1
0
def integrate_spectra(obj, **kwargs):
    """
    This function takes a set of spectra and calculates the integration for the
    primary axis. If the integration range for a spectrum cannot be found, an
    error report will be generated with the following information:

    Range not found: pixel ID, start bin, end bin, length of data array
       
    A failing pixel will have the integration tuple set to C{(nan, nan)}.

    @param obj: Object containing spectra that will have the integration
                calculated from them.
    @type obj: C{SOM.SOM} or C{SOM.SO}
    
    @param kwargs: A list of keyword arguments that the function accepts:
    
    @keyword start: The start range for the integration.
    @type start: C{int}
    
    @keyword end: The end range for the integration. 
                  function.
    @type end: C{int}
    
    @keyword axis_pos: This is position of the axis in the axis array. If no
                       argument is given, the default value is I{0}.
    @type axis_pos: C{int}

    @keyword norm: This is a flag to turn on the division of the individual
                   spectrum integrations by the solid angle of the
                   corresponding pixel. This also activates the multiplication
                   of the individual spectrum bin values by their
                   corresponding bin width via the I{width} flag in
                   L{integrate_axis}. The default value of the flag is
                   I{False}.
    @type norm: C{boolean}

    @keyword total: This is a flag to turn on the summation of all individual
                    spectrum integrations. The default value of the flag is
                    I{False}.
    @type total: C{boolean}

    @keyword width: This is a flag to turn on the removal of the individual bin
                    width in the L{integrate_axis} function while doing the
                    integrations. The default value of the flag is I{False}. 
    @type width: C{boolean}
    
    
    @return: Object containing the integration and the uncertainty squared
             associated with the integration
    @rtype: C{SOM.SOM} or C{SOM.SO}
    """

    # import the helper functions
    import hlr_utils

    if obj is None:
        return obj

    # set up for working through data
    (result, res_descr) = hlr_utils.empty_result(obj)

    o_descr = hlr_utils.get_descr(obj)
    result = hlr_utils.copy_som_attr(result, res_descr, obj, o_descr)

    # Check for axis_pos keyword argument
    try:
        axis_pos = kwargs["axis_pos"]
    except KeyError:
        axis_pos = 0

    # Check for norm keyword argument
    try:
        norm = kwargs["norm"]
        if norm:
            if o_descr == "SO":
                raise RuntimeError("Cannot use norm keyword with SO!")

            width = True
            inst = obj.attr_list.instrument
        else:
            width = False
    except KeyError:
        norm = False
        width = False

    # Check for total keyword argument
    try:
        total = kwargs["total"]
    except KeyError:
        total = False

    # Check for width keyword argument only if norm isn't present
    if not norm:
        try:
            width = kwargs["width"]
        except KeyError:
            width = False

    # If the integration start bound is not given, set to infinity
    try:
        i_start = kwargs["start"]
    except KeyError:
        i_start = float("inf")

    # If the integration end bound is not given, set to infinity
    try:
        i_end = kwargs["end"]
    except KeyError:
        i_end = float("inf")

    # iterate through the values
    import dr_lib

    len_obj = hlr_utils.get_length(obj)
    for i in xrange(len_obj):
        obj1 = hlr_utils.get_value(obj, i, o_descr, "all")

        # If there's a NaN at the front and back, there are NaN's everywhere
        if str(obj1.axis[axis_pos].val[0]) == "nan" and \
               str(obj1.axis[axis_pos].val[-1]) == "nan":
            print "Range not found:", obj1.id, i_start, i_end, len(obj1)
            value = (float('nan'), float('nan'))
        else:
            value = dr_lib.integrate_axis(obj1,
                                          start=i_start,
                                          end=i_end,
                                          width=width)
        if norm:
            if inst.get_name() == "BSS":
                map_so = hlr_utils.get_map_so(obj, None, i)
                dOmega = dr_lib.calc_BSS_solid_angle(map_so, inst)

                value1 = (value[0] / dOmega, value[1] / (dOmega * dOmega))
            else:
                raise RuntimeError("Do not know how to get solid angle from "\
                                   +"%s" % inst.get_name())
        else:
            value1 = value

        hlr_utils.result_insert(result, res_descr, value1, obj1, "yonly")

    if not total:
        return result
    else:
        # Sum all integration counts
        total_counts = 0
        total_err2 = 0

        for j in xrange(hlr_utils.get_length(result)):
            total_counts += hlr_utils.get_value(result, j, res_descr, "y")
            total_err2 += hlr_utils.get_err2(result, j, res_descr, "y")

        # Create new result object
        (result2, res2_descr) = hlr_utils.empty_result(result)

        result2 = hlr_utils.copy_som_attr(result2, res2_descr, result,
                                          res_descr)

        res1 = hlr_utils.get_value(result, 0, res_descr, "all")
        hlr_utils.result_insert(result2, res2_descr,
                                (total_counts, total_err2), res1, "yonly")
        return result2
def create_E_vs_Q_igs(som, *args, **kwargs):
    """
    This function starts with the initial IGS wavelength axis and turns this
    into a 2D spectra with E and Q axes.

    @param som: The input object with initial IGS wavelength axis
    @type som: C{SOM.SOM}

    @param args: A mandatory list of axes for rebinning. There is a particular
                 order to them. They should be present in the following order:

                 Without errors
                   1. Energy transfer
                   2. Momentum transfer
                 With errors
                   1. Energy transfer
                   2. Energy transfer error^2
                   3. Momentum transfer
                   4. Momentum transfer error ^2
    @type args: C{nessi_list.NessiList}s
       
    @param kwargs: A list of keyword arguments that the function accepts:

    @keyword withXVar: Flag for whether the function should be expecting the
                       associated axes to have errors. The default value will
                       be I{False}.
    @type withXVar: C{boolean}

    @keyword data_type: Name of the data type which can be either I{histogram},
                        I{density} or I{coordinate}. The default value will be
                        I{histogram}
    @type data_type: C{string}
    
    @keyword Q_filter: Flag to turn on or off Q filtering. The default behavior
                       is I{True}.
    @type Q_filter: C{boolean}
    
    @keyword so_id: The identifier represents a number, string, tuple or other
                    object that describes the resulting C{SO}
    @type so_id: C{int}, C{string}, C{tuple}, C{pixel ID}
    
    @keyword y_label: The y axis label
    @type y_label: C{string}
    
    @keyword y_units: The y axis units
    @type y_units: C{string}
    
    @keyword x_labels: This is a list of names that sets the individual x axis
    labels
    @type x_labels: C{list} of C{string}s
    
    @keyword x_units: This is a list of names that sets the individual x axis
    units
    @type x_units: C{list} of C{string}s

    @keyword split: This flag causes the counts and the fractional area to
                    be written out into separate files.
    @type split: C{boolean}

    @keyword configure: This is the object containing the driver configuration.
    @type configure: C{Configure}


    @return: Object containing a 2D C{SO} with E and Q axes
    @rtype: C{SOM.SOM}


    @raise RuntimeError: Anything other than a C{SOM} is passed to the function
    
    @raise RuntimeError: An instrument is not contained in the C{SOM}
    """
    import nessi_list

    # Setup some variables 
    dim = 2
    N_y = []
    N_tot = 1
    N_args = len(args)

    # Get T0 slope in order to calculate dT = dT_i + dT_0
    try:
        t_0_slope = som.attr_list["Time_zero_slope"][0]
        t_0_slope_err2 = som.attr_list["Time_zero_slope"][1]
    except KeyError:
        t_0_slope = float(0.0)
        t_0_slope_err2 = float(0.0)

    # Check withXVar keyword argument and also check number of given args.
    # Set xvar to the appropriate value
    try:
        value = kwargs["withXVar"]
        if value.lower() == "true":
            if N_args != 4:
                raise RuntimeError("Since you have requested x errors, 4 x "\
                                   +"axes must be provided.")
            else:
                xvar = True
        elif value.lower() == "false":
            if N_args != 2:
                raise RuntimeError("Since you did not requested x errors, 2 "\
                                   +"x axes must be provided.")
            else:
                xvar = False
        else:
            raise RuntimeError("Do not understand given parameter %s" % \
                               value)
    except KeyError:
        if N_args != 2:
            raise RuntimeError("Since you did not requested x errors, 2 "\
                               +"x axes must be provided.")
        else:
            xvar = False

    # Check dataType keyword argument. An offset will be set to 1 for the
    # histogram type and 0 for either density or coordinate
    try:
        data_type = kwargs["data_type"]
        if data_type.lower() == "histogram":
            offset = 1
        elif data_type.lower() == "density" or \
                 data_type.lower() == "coordinate":
            offset = 0
        else:
            raise RuntimeError("Do not understand data type given: %s" % \
                               data_type)
    # Default is offset for histogram
    except KeyError:
        offset = 1

    try:
        Q_filter = kwargs["Q_filter"]
    except KeyError:
        Q_filter = True

    # Check for split keyword
    try:
        split = kwargs["split"]
    except KeyError:
        split = False

    # Check for configure keyword
    try:
        configure = kwargs["configure"]
    except KeyError:
        configure = None

    so_dim = SOM.SO(dim)

    for i in range(dim):
        # Set the x-axis arguments from the *args list into the new SO
        if not xvar:
            # Axis positions are 1 (Q) and 0 (E)
            position = dim - i - 1
            so_dim.axis[i].val = args[position]
        else:
            # Axis positions are 2 (Q), 3 (eQ), 0 (E), 1 (eE)
            position = dim - 2 * i
            so_dim.axis[i].val = args[position]
            so_dim.axis[i].var = args[position + 1]

        # Set individual value axis sizes (not x-axis size)
        N_y.append(len(args[position]) - offset)

        # Calculate total 2D array size
        N_tot = N_tot * N_y[-1]

    # Create y and var_y lists from total 2D size
    so_dim.y = nessi_list.NessiList(N_tot)
    so_dim.var_y = nessi_list.NessiList(N_tot)
    
    # Create area sum and errors for the area sum lists from total 2D size
    area_sum = nessi_list.NessiList(N_tot)
    area_sum_err2 = nessi_list.NessiList(N_tot)

    # Create area sum and errors for the area sum lists from total 2D size
    bin_count = nessi_list.NessiList(N_tot)
    bin_count_err2 = nessi_list.NessiList(N_tot)
    
    inst = som.attr_list.instrument
    lambda_final = som.attr_list["Wavelength_final"]
    inst_name = inst.get_name()

    import bisect
    import math

    import dr_lib
    import utils

    arr_len = 0
    #: Vector of zeros for function calculations
    zero_vec = None
    
    for j in xrange(hlr_utils.get_length(som)):
        # Get counts
        counts = hlr_utils.get_value(som, j, "SOM", "y")
        counts_err2 = hlr_utils.get_err2(som, j, "SOM", "y")

        arr_len = len(counts)
        zero_vec = nessi_list.NessiList(arr_len)

        # Get mapping SO
        map_so = hlr_utils.get_map_so(som, None, j)

        # Get lambda_i
        l_i = hlr_utils.get_value(som, j, "SOM", "x")
        l_i_err2 = hlr_utils.get_err2(som, j, "SOM", "x")
        
        # Get lambda_f from instrument information
        l_f_tuple = hlr_utils.get_special(lambda_final, map_so)
        l_f = l_f_tuple[0]
        l_f_err2 = l_f_tuple[1]
        
        # Get source to sample distance
        (L_s, L_s_err2) = hlr_utils.get_parameter("primary", map_so, inst)

        # Get sample to detector distance
        L_d_tuple = hlr_utils.get_parameter("secondary", map_so, inst)
        L_d = L_d_tuple[0]

        # Get polar angle from instrument information
        (angle, angle_err2) = hlr_utils.get_parameter("polar", map_so, inst)

        # Get the detector pixel height
        dh_tuple = hlr_utils.get_parameter("dh", map_so, inst)
        dh = dh_tuple[0]
        # Need dh in units of Angstrom
        dh *= 1e10

        # Calculate T_i
        (T_i, T_i_err2) = axis_manip.wavelength_to_tof(l_i, l_i_err2, 
                                                       L_s, L_s_err2)

        # Scale counts by lambda_f / lambda_i
        (l_i_bc, l_i_bc_err2) = utils.calc_bin_centers(l_i, l_i_err2)

        (ratio, ratio_err2) = array_manip.div_ncerr(l_f, l_f_err2,
                                                    l_i_bc, l_i_bc_err2)

        (counts, counts_err2) = array_manip.mult_ncerr(counts, counts_err2,
                                                       ratio, ratio_err2)

        # Calculate E_i
        (E_i, E_i_err2) = axis_manip.wavelength_to_energy(l_i, l_i_err2)

        # Calculate E_f
        (E_f, E_f_err2) = axis_manip.wavelength_to_energy(l_f, l_f_err2)

        # Calculate E_t
        (E_t, E_t_err2) = array_manip.sub_ncerr(E_i, E_i_err2, E_f, E_f_err2)

        if inst_name == "BSS":
            # Convert E_t from meV to ueV
            (E_t, E_t_err2) = array_manip.mult_ncerr(E_t, E_t_err2,
                                                     1000.0, 0.0)
            (counts, counts_err2) = array_manip.mult_ncerr(counts, counts_err2,
                                                           1.0/1000.0, 0.0)

        # Convert lambda_i to k_i
        (k_i, k_i_err2) = axis_manip.wavelength_to_scalar_k(l_i, l_i_err2)

        # Convert lambda_f to k_f
        (k_f, k_f_err2) = axis_manip.wavelength_to_scalar_k(l_f, l_f_err2)

        # Convert k_i and k_f to Q
        (Q, Q_err2) = axis_manip.init_scatt_wavevector_to_scalar_Q(k_i,
                                                                   k_i_err2,
                                                                   k_f,
                                                                   k_f_err2,
                                                                   angle,
                                                                   angle_err2)

        # Calculate dT = dT_0 + dT_i
        dT_i = utils.calc_bin_widths(T_i, T_i_err2)

        (l_i_bw, l_i_bw_err2) = utils.calc_bin_widths(l_i, l_i_err2)
        dT_0 = array_manip.mult_ncerr(l_i_bw, l_i_bw_err2,
                                      t_0_slope, t_0_slope_err2)

        dT_tuple = array_manip.add_ncerr(dT_i[0], dT_i[1], dT_0[0], dT_0[1])
        dT = dT_tuple[0]

        # Calculate Jacobian
        if inst_name == "BSS":
            (x_1, x_2,
             x_3, x_4) = dr_lib.calc_BSS_coeffs(map_so, inst, (E_i, E_i_err2),
                                                (Q, Q_err2), (k_i, k_i_err2),
                                                (T_i, T_i_err2), dh, angle,
                                                E_f, k_f, l_f, L_s, L_d,
                                                t_0_slope, zero_vec)
        else:
            raise RuntimeError("Do not know how to calculate x_i "\
                               +"coefficients for instrument %s" % inst_name)

        (A, A_err2) = dr_lib.calc_EQ_Jacobian(x_1, x_2, x_3, x_4, dT, dh,
                                              zero_vec)
        
        # Apply Jacobian: C/dlam * dlam / A(EQ) = C/EQ
        (jac_ratio, jac_ratio_err2) = array_manip.div_ncerr(l_i_bw,
                                                            l_i_bw_err2,
                                                            A, A_err2)
        (counts, counts_err2) = array_manip.mult_ncerr(counts, counts_err2,
                                                       jac_ratio,
                                                       jac_ratio_err2)
        
        # Reverse counts, E_t, k_i and Q
        E_t = axis_manip.reverse_array_cp(E_t)
        E_t_err2 = axis_manip.reverse_array_cp(E_t_err2)
        Q = axis_manip.reverse_array_cp(Q)
        Q_err2 = axis_manip.reverse_array_cp(Q_err2)        
        counts = axis_manip.reverse_array_cp(counts)
        counts_err2 = axis_manip.reverse_array_cp(counts_err2)
        k_i = axis_manip.reverse_array_cp(k_i)
        x_1 = axis_manip.reverse_array_cp(x_1)
        x_2 = axis_manip.reverse_array_cp(x_2)
        x_3 = axis_manip.reverse_array_cp(x_3)
        x_4 = axis_manip.reverse_array_cp(x_4)
        dT = axis_manip.reverse_array_cp(dT)        

        # Filter for duplicate Q values
        if Q_filter:
            k_i_cutoff = k_f * math.cos(angle)
            k_i_cutbin = bisect.bisect(k_i, k_i_cutoff)
            
            counts.__delslice__(0, k_i_cutbin)
            counts_err2.__delslice__(0, k_i_cutbin)
            Q.__delslice__(0, k_i_cutbin)
            Q_err2.__delslice__(0, k_i_cutbin)
            E_t.__delslice__(0, k_i_cutbin)
            E_t_err2.__delslice__(0, k_i_cutbin)
            x_1.__delslice__(0, k_i_cutbin)
            x_2.__delslice__(0, k_i_cutbin)
            x_3.__delslice__(0, k_i_cutbin)
            x_4.__delslice__(0, k_i_cutbin)            
            dT.__delslice__(0, k_i_cutbin)
            zero_vec.__delslice__(0, k_i_cutbin)

        try:
            if inst_name == "BSS":
                ((Q_1, E_t_1),
                 (Q_2, E_t_2),
                 (Q_3, E_t_3),
                 (Q_4, E_t_4)) = dr_lib.calc_BSS_EQ_verticies((E_t, E_t_err2),
                                                              (Q, Q_err2), x_1,
                                                              x_2, x_3, x_4,
                                                              dT, dh, zero_vec)
            else:
                raise RuntimeError("Do not know how to calculate (Q_i, "\
                                   +"E_t_i) verticies for instrument %s" \
                                   % inst_name)

        except IndexError:
            # All the data got Q filtered, move on
            continue

        try:
            (y_2d, y_2d_err2,
             area_new,
             bin_count_new) = axis_manip.rebin_2D_quad_to_rectlin(Q_1, E_t_1,
                                                           Q_2, E_t_2,
                                                           Q_3, E_t_3,
                                                           Q_4, E_t_4,
                                                           counts,
                                                           counts_err2,
                                                           so_dim.axis[0].val,
                                                           so_dim.axis[1].val)
        except IndexError, e:
            # Get the offending index from the error message
            index = int(str(e).split()[1].split('index')[-1].strip('[]'))
            print "Id:", map_so.id
            print "Index:", index
            print "Verticies: %f, %f, %f, %f, %f, %f, %f, %f" % (Q_1[index],
                                                                 E_t_1[index],
                                                                 Q_2[index],
                                                                 E_t_2[index],
                                                                 Q_3[index],
                                                                 E_t_3[index],
                                                                 Q_4[index],
                                                                 E_t_4[index])
            raise IndexError(str(e))

        # Add in together with previous results
        (so_dim.y, so_dim.var_y) = array_manip.add_ncerr(so_dim.y,
                                                         so_dim.var_y,
                                                         y_2d, y_2d_err2)
        
        (area_sum, area_sum_err2) = array_manip.add_ncerr(area_sum,
                                                          area_sum_err2,
                                                          area_new,
                                                          area_sum_err2)

        if configure.dump_pix_contrib or configure.scale_sqe:
            if inst_name == "BSS":
                dOmega = dr_lib.calc_BSS_solid_angle(map_so, inst)
                (bin_count_new,
                 bin_count_err2) = array_manip.mult_ncerr(bin_count_new,
                                                          bin_count_err2,
                                                          dOmega, 0.0)
                
                (bin_count,
                 bin_count_err2) = array_manip.add_ncerr(bin_count,
                                                         bin_count_err2,
                                                         bin_count_new,
                                                         bin_count_err2)
        else:
            del bin_count_new
def integrate_spectra(obj, **kwargs):
    """
    This function takes a set of spectra and calculates the integration for the
    primary axis. If the integration range for a spectrum cannot be found, an
    error report will be generated with the following information:

    Range not found: pixel ID, start bin, end bin, length of data array
       
    A failing pixel will have the integration tuple set to C{(nan, nan)}.

    @param obj: Object containing spectra that will have the integration
                calculated from them.
    @type obj: C{SOM.SOM} or C{SOM.SO}
    
    @param kwargs: A list of keyword arguments that the function accepts:
    
    @keyword start: The start range for the integration.
    @type start: C{int}
    
    @keyword end: The end range for the integration. 
                  function.
    @type end: C{int}
    
    @keyword axis_pos: This is position of the axis in the axis array. If no
                       argument is given, the default value is I{0}.
    @type axis_pos: C{int}

    @keyword norm: This is a flag to turn on the division of the individual
                   spectrum integrations by the solid angle of the
                   corresponding pixel. This also activates the multiplication
                   of the individual spectrum bin values by their
                   corresponding bin width via the I{width} flag in
                   L{integrate_axis}. The default value of the flag is
                   I{False}.
    @type norm: C{boolean}

    @keyword total: This is a flag to turn on the summation of all individual
                    spectrum integrations. The default value of the flag is
                    I{False}.
    @type total: C{boolean}

    @keyword width: This is a flag to turn on the removal of the individual bin
                    width in the L{integrate_axis} function while doing the
                    integrations. The default value of the flag is I{False}. 
    @type width: C{boolean}
    
    
    @return: Object containing the integration and the uncertainty squared
             associated with the integration
    @rtype: C{SOM.SOM} or C{SOM.SO}
    """

    # import the helper functions
    import hlr_utils

    if obj is None:
        return obj

    # set up for working through data
    (result, res_descr) = hlr_utils.empty_result(obj)

    o_descr = hlr_utils.get_descr(obj)
    result = hlr_utils.copy_som_attr(result, res_descr, obj, o_descr)

    # Check for axis_pos keyword argument
    try:
        axis_pos = kwargs["axis_pos"]
    except KeyError:
        axis_pos = 0

    # Check for norm keyword argument
    try:
        norm = kwargs["norm"]
        if norm:
            if o_descr == "SO":
                raise RuntimeError("Cannot use norm keyword with SO!")
            
            width = True
            inst = obj.attr_list.instrument
        else:
            width = False
    except KeyError:
        norm = False
        width = False

    # Check for total keyword argument
    try:
        total = kwargs["total"]
    except KeyError:
        total = False

    # Check for width keyword argument only if norm isn't present
    if not norm:
        try:
            width = kwargs["width"]
        except KeyError:
            width = False

    # If the integration start bound is not given, set to infinity
    try:
        i_start = kwargs["start"]
    except KeyError:
        i_start = float("inf")

    # If the integration end bound is not given, set to infinity
    try:
        i_end = kwargs["end"]
    except KeyError:
        i_end = float("inf")
    
    # iterate through the values
    import dr_lib

    len_obj = hlr_utils.get_length(obj)
    for i in xrange(len_obj):
        obj1 = hlr_utils.get_value(obj, i, o_descr, "all")

        # If there's a NaN at the front and back, there are NaN's everywhere
        if str(obj1.axis[axis_pos].val[0]) == "nan" and \
               str(obj1.axis[axis_pos].val[-1]) == "nan":
            print "Range not found:", obj1.id, i_start, i_end, len(obj1)
            value = (float('nan'), float('nan'))
        else:
            value = dr_lib.integrate_axis(obj1, start=i_start, end=i_end,
                                          width=width)
        if norm:
            if inst.get_name() == "BSS":
                map_so = hlr_utils.get_map_so(obj, None, i)
                dOmega = dr_lib.calc_BSS_solid_angle(map_so, inst)
        
                value1 = (value[0] / dOmega, value[1] / (dOmega * dOmega))
            else:
                raise RuntimeError("Do not know how to get solid angle from "\
                                   +"%s" % inst.get_name())
        else:
            value1 = value

        hlr_utils.result_insert(result, res_descr, value1, obj1, "yonly")

    if not total:
        return result
    else:
        # Sum all integration counts
        total_counts = 0
        total_err2 = 0

        for j in xrange(hlr_utils.get_length(result)):
            total_counts += hlr_utils.get_value(result, j, res_descr, "y")
            total_err2 += hlr_utils.get_err2(result, j, res_descr, "y")

        # Create new result object
        (result2, res2_descr) = hlr_utils.empty_result(result)

        result2 = hlr_utils.copy_som_attr(result2, res2_descr,
                                          result, res_descr)

        res1 = hlr_utils.get_value(result, 0, res_descr, "all")
        hlr_utils.result_insert(result2, res2_descr,
                                (total_counts, total_err2),
                                res1, "yonly")
        return result2
def create_E_vs_Q_igs(som, *args, **kwargs):
    """
    This function starts with the initial IGS wavelength axis and turns this
    into a 2D spectra with E and Q axes.

    @param som: The input object with initial IGS wavelength axis
    @type som: C{SOM.SOM}

    @param args: A mandatory list of axes for rebinning. There is a particular
                 order to them. They should be present in the following order:

                 Without errors
                   1. Energy transfer
                   2. Momentum transfer
                 With errors
                   1. Energy transfer
                   2. Energy transfer error^2
                   3. Momentum transfer
                   4. Momentum transfer error ^2
    @type args: C{nessi_list.NessiList}s
       
    @param kwargs: A list of keyword arguments that the function accepts:

    @keyword withXVar: Flag for whether the function should be expecting the
                       associated axes to have errors. The default value will
                       be I{False}.
    @type withXVar: C{boolean}

    @keyword data_type: Name of the data type which can be either I{histogram},
                        I{density} or I{coordinate}. The default value will be
                        I{histogram}
    @type data_type: C{string}
    
    @keyword Q_filter: Flag to turn on or off Q filtering. The default behavior
                       is I{True}.
    @type Q_filter: C{boolean}
    
    @keyword so_id: The identifier represents a number, string, tuple or other
                    object that describes the resulting C{SO}
    @type so_id: C{int}, C{string}, C{tuple}, C{pixel ID}
    
    @keyword y_label: The y axis label
    @type y_label: C{string}
    
    @keyword y_units: The y axis units
    @type y_units: C{string}
    
    @keyword x_labels: This is a list of names that sets the individual x axis
    labels
    @type x_labels: C{list} of C{string}s
    
    @keyword x_units: This is a list of names that sets the individual x axis
    units
    @type x_units: C{list} of C{string}s

    @keyword split: This flag causes the counts and the fractional area to
                    be written out into separate files.
    @type split: C{boolean}

    @keyword configure: This is the object containing the driver configuration.
    @type configure: C{Configure}


    @return: Object containing a 2D C{SO} with E and Q axes
    @rtype: C{SOM.SOM}


    @raise RuntimeError: Anything other than a C{SOM} is passed to the function
    
    @raise RuntimeError: An instrument is not contained in the C{SOM}
    """
    import nessi_list

    # Setup some variables
    dim = 2
    N_y = []
    N_tot = 1
    N_args = len(args)

    # Get T0 slope in order to calculate dT = dT_i + dT_0
    try:
        t_0_slope = som.attr_list["Time_zero_slope"][0]
        t_0_slope_err2 = som.attr_list["Time_zero_slope"][1]
    except KeyError:
        t_0_slope = float(0.0)
        t_0_slope_err2 = float(0.0)

    # Check withXVar keyword argument and also check number of given args.
    # Set xvar to the appropriate value
    try:
        value = kwargs["withXVar"]
        if value.lower() == "true":
            if N_args != 4:
                raise RuntimeError("Since you have requested x errors, 4 x "\
                                   +"axes must be provided.")
            else:
                xvar = True
        elif value.lower() == "false":
            if N_args != 2:
                raise RuntimeError("Since you did not requested x errors, 2 "\
                                   +"x axes must be provided.")
            else:
                xvar = False
        else:
            raise RuntimeError("Do not understand given parameter %s" % \
                               value)
    except KeyError:
        if N_args != 2:
            raise RuntimeError("Since you did not requested x errors, 2 "\
                               +"x axes must be provided.")
        else:
            xvar = False

    # Check dataType keyword argument. An offset will be set to 1 for the
    # histogram type and 0 for either density or coordinate
    try:
        data_type = kwargs["data_type"]
        if data_type.lower() == "histogram":
            offset = 1
        elif data_type.lower() == "density" or \
                 data_type.lower() == "coordinate":
            offset = 0
        else:
            raise RuntimeError("Do not understand data type given: %s" % \
                               data_type)
    # Default is offset for histogram
    except KeyError:
        offset = 1

    try:
        Q_filter = kwargs["Q_filter"]
    except KeyError:
        Q_filter = True

    # Check for split keyword
    try:
        split = kwargs["split"]
    except KeyError:
        split = False

    # Check for configure keyword
    try:
        configure = kwargs["configure"]
    except KeyError:
        configure = None

    so_dim = SOM.SO(dim)

    for i in range(dim):
        # Set the x-axis arguments from the *args list into the new SO
        if not xvar:
            # Axis positions are 1 (Q) and 0 (E)
            position = dim - i - 1
            so_dim.axis[i].val = args[position]
        else:
            # Axis positions are 2 (Q), 3 (eQ), 0 (E), 1 (eE)
            position = dim - 2 * i
            so_dim.axis[i].val = args[position]
            so_dim.axis[i].var = args[position + 1]

        # Set individual value axis sizes (not x-axis size)
        N_y.append(len(args[position]) - offset)

        # Calculate total 2D array size
        N_tot = N_tot * N_y[-1]

    # Create y and var_y lists from total 2D size
    so_dim.y = nessi_list.NessiList(N_tot)
    so_dim.var_y = nessi_list.NessiList(N_tot)

    # Create area sum and errors for the area sum lists from total 2D size
    area_sum = nessi_list.NessiList(N_tot)
    area_sum_err2 = nessi_list.NessiList(N_tot)

    # Create area sum and errors for the area sum lists from total 2D size
    bin_count = nessi_list.NessiList(N_tot)
    bin_count_err2 = nessi_list.NessiList(N_tot)

    inst = som.attr_list.instrument
    lambda_final = som.attr_list["Wavelength_final"]
    inst_name = inst.get_name()

    import bisect
    import math

    import dr_lib
    import utils

    arr_len = 0
    #: Vector of zeros for function calculations
    zero_vec = None

    for j in xrange(hlr_utils.get_length(som)):
        # Get counts
        counts = hlr_utils.get_value(som, j, "SOM", "y")
        counts_err2 = hlr_utils.get_err2(som, j, "SOM", "y")

        arr_len = len(counts)
        zero_vec = nessi_list.NessiList(arr_len)

        # Get mapping SO
        map_so = hlr_utils.get_map_so(som, None, j)

        # Get lambda_i
        l_i = hlr_utils.get_value(som, j, "SOM", "x")
        l_i_err2 = hlr_utils.get_err2(som, j, "SOM", "x")

        # Get lambda_f from instrument information
        l_f_tuple = hlr_utils.get_special(lambda_final, map_so)
        l_f = l_f_tuple[0]
        l_f_err2 = l_f_tuple[1]

        # Get source to sample distance
        (L_s, L_s_err2) = hlr_utils.get_parameter("primary", map_so, inst)

        # Get sample to detector distance
        L_d_tuple = hlr_utils.get_parameter("secondary", map_so, inst)
        L_d = L_d_tuple[0]

        # Get polar angle from instrument information
        (angle, angle_err2) = hlr_utils.get_parameter("polar", map_so, inst)

        # Get the detector pixel height
        dh_tuple = hlr_utils.get_parameter("dh", map_so, inst)
        dh = dh_tuple[0]
        # Need dh in units of Angstrom
        dh *= 1e10

        # Calculate T_i
        (T_i, T_i_err2) = axis_manip.wavelength_to_tof(l_i, l_i_err2, L_s,
                                                       L_s_err2)

        # Scale counts by lambda_f / lambda_i
        (l_i_bc, l_i_bc_err2) = utils.calc_bin_centers(l_i, l_i_err2)

        (ratio, ratio_err2) = array_manip.div_ncerr(l_f, l_f_err2, l_i_bc,
                                                    l_i_bc_err2)

        (counts, counts_err2) = array_manip.mult_ncerr(counts, counts_err2,
                                                       ratio, ratio_err2)

        # Calculate E_i
        (E_i, E_i_err2) = axis_manip.wavelength_to_energy(l_i, l_i_err2)

        # Calculate E_f
        (E_f, E_f_err2) = axis_manip.wavelength_to_energy(l_f, l_f_err2)

        # Calculate E_t
        (E_t, E_t_err2) = array_manip.sub_ncerr(E_i, E_i_err2, E_f, E_f_err2)

        if inst_name == "BSS":
            # Convert E_t from meV to ueV
            (E_t, E_t_err2) = array_manip.mult_ncerr(E_t, E_t_err2, 1000.0,
                                                     0.0)
            (counts,
             counts_err2) = array_manip.mult_ncerr(counts, counts_err2,
                                                   1.0 / 1000.0, 0.0)

        # Convert lambda_i to k_i
        (k_i, k_i_err2) = axis_manip.wavelength_to_scalar_k(l_i, l_i_err2)

        # Convert lambda_f to k_f
        (k_f, k_f_err2) = axis_manip.wavelength_to_scalar_k(l_f, l_f_err2)

        # Convert k_i and k_f to Q
        (Q, Q_err2) = axis_manip.init_scatt_wavevector_to_scalar_Q(
            k_i, k_i_err2, k_f, k_f_err2, angle, angle_err2)

        # Calculate dT = dT_0 + dT_i
        dT_i = utils.calc_bin_widths(T_i, T_i_err2)

        (l_i_bw, l_i_bw_err2) = utils.calc_bin_widths(l_i, l_i_err2)
        dT_0 = array_manip.mult_ncerr(l_i_bw, l_i_bw_err2, t_0_slope,
                                      t_0_slope_err2)

        dT_tuple = array_manip.add_ncerr(dT_i[0], dT_i[1], dT_0[0], dT_0[1])
        dT = dT_tuple[0]

        # Calculate Jacobian
        if inst_name == "BSS":
            (x_1, x_2, x_3, x_4) = dr_lib.calc_BSS_coeffs(
                map_so, inst, (E_i, E_i_err2), (Q, Q_err2), (k_i, k_i_err2),
                (T_i, T_i_err2), dh, angle, E_f, k_f, l_f, L_s, L_d, t_0_slope,
                zero_vec)
        else:
            raise RuntimeError("Do not know how to calculate x_i "\
                               +"coefficients for instrument %s" % inst_name)

        (A, A_err2) = dr_lib.calc_EQ_Jacobian(x_1, x_2, x_3, x_4, dT, dh,
                                              zero_vec)

        # Apply Jacobian: C/dlam * dlam / A(EQ) = C/EQ
        (jac_ratio,
         jac_ratio_err2) = array_manip.div_ncerr(l_i_bw, l_i_bw_err2, A,
                                                 A_err2)
        (counts, counts_err2) = array_manip.mult_ncerr(counts, counts_err2,
                                                       jac_ratio,
                                                       jac_ratio_err2)

        # Reverse counts, E_t, k_i and Q
        E_t = axis_manip.reverse_array_cp(E_t)
        E_t_err2 = axis_manip.reverse_array_cp(E_t_err2)
        Q = axis_manip.reverse_array_cp(Q)
        Q_err2 = axis_manip.reverse_array_cp(Q_err2)
        counts = axis_manip.reverse_array_cp(counts)
        counts_err2 = axis_manip.reverse_array_cp(counts_err2)
        k_i = axis_manip.reverse_array_cp(k_i)
        x_1 = axis_manip.reverse_array_cp(x_1)
        x_2 = axis_manip.reverse_array_cp(x_2)
        x_3 = axis_manip.reverse_array_cp(x_3)
        x_4 = axis_manip.reverse_array_cp(x_4)
        dT = axis_manip.reverse_array_cp(dT)

        # Filter for duplicate Q values
        if Q_filter:
            k_i_cutoff = k_f * math.cos(angle)
            k_i_cutbin = bisect.bisect(k_i, k_i_cutoff)

            counts.__delslice__(0, k_i_cutbin)
            counts_err2.__delslice__(0, k_i_cutbin)
            Q.__delslice__(0, k_i_cutbin)
            Q_err2.__delslice__(0, k_i_cutbin)
            E_t.__delslice__(0, k_i_cutbin)
            E_t_err2.__delslice__(0, k_i_cutbin)
            x_1.__delslice__(0, k_i_cutbin)
            x_2.__delslice__(0, k_i_cutbin)
            x_3.__delslice__(0, k_i_cutbin)
            x_4.__delslice__(0, k_i_cutbin)
            dT.__delslice__(0, k_i_cutbin)
            zero_vec.__delslice__(0, k_i_cutbin)

        try:
            if inst_name == "BSS":
                ((Q_1, E_t_1), (Q_2, E_t_2), (Q_3, E_t_3),
                 (Q_4, E_t_4)) = dr_lib.calc_BSS_EQ_verticies(
                     (E_t, E_t_err2), (Q, Q_err2), x_1, x_2, x_3, x_4, dT, dh,
                     zero_vec)
            else:
                raise RuntimeError("Do not know how to calculate (Q_i, "\
                                   +"E_t_i) verticies for instrument %s" \
                                   % inst_name)

        except IndexError:
            # All the data got Q filtered, move on
            continue

        try:
            (y_2d, y_2d_err2, area_new,
             bin_count_new) = axis_manip.rebin_2D_quad_to_rectlin(
                 Q_1, E_t_1, Q_2, E_t_2, Q_3, E_t_3, Q_4, E_t_4, counts,
                 counts_err2, so_dim.axis[0].val, so_dim.axis[1].val)
        except IndexError, e:
            # Get the offending index from the error message
            index = int(str(e).split()[1].split('index')[-1].strip('[]'))
            print "Id:", map_so.id
            print "Index:", index
            print "Verticies: %f, %f, %f, %f, %f, %f, %f, %f" % (
                Q_1[index], E_t_1[index], Q_2[index], E_t_2[index], Q_3[index],
                E_t_3[index], Q_4[index], E_t_4[index])
            raise IndexError(str(e))

        # Add in together with previous results
        (so_dim.y,
         so_dim.var_y) = array_manip.add_ncerr(so_dim.y, so_dim.var_y, y_2d,
                                               y_2d_err2)

        (area_sum,
         area_sum_err2) = array_manip.add_ncerr(area_sum, area_sum_err2,
                                                area_new, area_sum_err2)

        if configure.dump_pix_contrib or configure.scale_sqe:
            if inst_name == "BSS":
                dOmega = dr_lib.calc_BSS_solid_angle(map_so, inst)
                (bin_count_new, bin_count_err2) = array_manip.mult_ncerr(
                    bin_count_new, bin_count_err2, dOmega, 0.0)

                (bin_count, bin_count_err2) = array_manip.add_ncerr(
                    bin_count, bin_count_err2, bin_count_new, bin_count_err2)
        else:
            del bin_count_new
def energy_transfer(obj, itype, axis_const, **kwargs):
    """
    This function takes a SOM with a wavelength axis (initial for IGS and
    final for DGS) and calculates the energy transfer.  

    @param obj: The object containing the wavelength axis
    @type obj: C{SOM.SOM}

    @param itype: The instrument class type. The choices are either I{IGS} or
                  I{DGS}.
    @type itype: C{string}

    @param axis_const: The attribute name for the axis constant which is the 
                         final wavelength for I{IGS} and the initial energy for
                         I{DGS}.
    @type axis_const: C{string}

    @param kwargs: A list of keyword arguments that the function accepts:

    @keyword units: The units for the incoming axis. The default is
                    I{Angstroms}.
    @type units: C{string}

    @keyword change_units: A flag that signals the function to convert from
                           I{meV} to I{ueV}. The default is I{False}.
    @type change_units: C{boolean}

    @keyword scale: A flag to scale the y-axis by lambda_f/lambda_i for I{IGS}
                    and lambda_i/lambda_f for I{DGS}. The default is I{False}.
    @type scale: C{boolean}

    @keyword lojac: A flag that turns on the calculation and application of
                    the linear-order Jacobian. The default is I{False}.
    @type lojac: C{boolean}

    @keyword sa_norm: A flag to turn on solid angle normlaization.
    @type sa_norm: C{boolean}

    @return: Object with the energy transfer calculated in units of I{meV} or
             I{ueV}. The default is I{meV}.
    @rtype: C{SOM.SOM}


    @raise RuntimeError: The instrument class type is not recognized
    @raise RuntimeError: The x-axis units are not Angstroms
    @raise RuntimeError: A SOM is not given to the function
    """
    # Check the instrument class type to make sure its allowed
    allowed_types = ["DGS", "IGS"]

    if itype not in allowed_types:
        raise RuntimeError("The instrument class type %s is not known. "\
                           +"Please use DGS or IGS" % itype)

    # import the helper functions
    import hlr_utils

    # set up for working through data
    (result, res_descr) = hlr_utils.empty_result(obj)
    o_descr = hlr_utils.get_descr(obj)

    if o_descr != "SOM":
        raise RuntimeError("Must provide a SOM to the function.")
    # Go on
    else:
        pass

    # Setup keyword arguments
    try:
        units = kwargs["units"]
    except KeyError:
        units = "Angstroms"

    try:
        change_units = kwargs["change_units"]
    except KeyError:
        change_units = False       

    try:
        scale = kwargs["scale"]
    except KeyError:
        scale = False

    try:
        sa_norm = kwargs["sa_norm"]
    except KeyError:
        sa_norm = False

    if sa_norm:
        inst = obj.attr_list.instrument

    try:
        lojac = kwargs["lojac"]
    except KeyError:
        lojac = False
    
    # Primary axis for transformation. 
    axis = hlr_utils.one_d_units(obj, units)

    # Get the subtraction constant
    try:
        axis_c = obj.attr_list[axis_const]
    except KeyError:
        raise RuntimeError("Must provide a final wavelength (IGS) or initial "\
                           +"energy (DGS) via the incoming SOM")
    
    result = hlr_utils.copy_som_attr(result, res_descr, obj, o_descr)
    if change_units:
        unit_str = "ueV"
    else:
        unit_str = "meV"
    result = hlr_utils.force_units(result, unit_str, axis)
    result.setAxisLabel(axis, "energy_transfer")
    result.setYUnits("Counts/" + unit_str)
    result.setYLabel("Intensity")

    # iterate through the values
    import array_manip
    import axis_manip
    import dr_lib
    import utils

    for i in xrange(hlr_utils.get_length(obj)):
        if itype == "IGS":
            l_i = hlr_utils.get_value(obj, i, o_descr, "x", axis)
            l_i_err2 = hlr_utils.get_err2(obj, i, o_descr, "x", axis)
        else:
            l_f = hlr_utils.get_value(obj, i, o_descr, "x", axis)
            l_f_err2 = hlr_utils.get_err2(obj, i, o_descr, "x", axis)
            
        y_val = hlr_utils.get_value(obj, i, o_descr, "y", axis)
        y_err2 = hlr_utils.get_err2(obj, i, o_descr, "y", axis)
        
        map_so = hlr_utils.get_map_so(obj, None, i)

        if itype == "IGS":
            (E_i, E_i_err2) = axis_manip.wavelength_to_energy(l_i, l_i_err2)
            l_f = hlr_utils.get_special(axis_c, map_so)[:2]
            (E_f, E_f_err2) = axis_manip.wavelength_to_energy(l_f[0], l_f[1])
            if lojac:
                (y_val, y_err2) = utils.linear_order_jacobian(l_i, E_i, 
                                                              y_val, y_err2)  
        else:
            (E_i, E_i_err2) = axis_c.toValErrTuple()
            (E_f, E_f_err2) = axis_manip.wavelength_to_energy(l_f, l_f_err2)
            if lojac:
                (y_val, y_err2) = utils.linear_order_jacobian(l_f, E_f, 
                                                              y_val, y_err2)

        if scale:
            # Scale counts by lambda_f / lambda_i
            if itype == "IGS":
                (l_n, l_n_err2) = l_f
                (l_d, l_d_err2) = utils.calc_bin_centers(l_i, l_i_err2)
            else:
                (l_n, l_n_err2) = utils.calc_bin_centers(l_f, l_f_err2)
                (l_d, l_d_err2) = axis_manip.energy_to_wavelength(E_i,
                                                                  E_i_err2)
                
            ratio = array_manip.div_ncerr(l_n, l_n_err2, l_d, l_d_err2)
            scale_y = array_manip.mult_ncerr(y_val, y_err2, ratio[0], ratio[1])
        else:
            scale_y = (y_val, y_err2)

        value = array_manip.sub_ncerr(E_i, E_i_err2, E_f, E_f_err2)

        if change_units:
            # Convert from meV to ueV
            value2 = array_manip.mult_ncerr(value[0], value[1], 1000.0, 0.0)
            scale_y = array_manip.mult_ncerr(scale_y[0], scale_y[1],
                                             1.0/1000.0, 0.0)
        else:
            value2 = value

        if sa_norm:
            if inst.get_name() == "BSS":
                dOmega = dr_lib.calc_BSS_solid_angle(map_so, inst)
                scale_y = array_manip.div_ncerr(scale_y[0], scale_y[1],
                                                dOmega, 0.0)
            else:
                raise RuntimeError("Do not know how to get solid angle from "\
                                   +"%s" % inst.get_name())
            
        if itype == "IGS":
            # Reverse the values due to the conversion
            value_y = axis_manip.reverse_array_cp(scale_y[0])
            value_var_y = axis_manip.reverse_array_cp(scale_y[1])
            value_x = axis_manip.reverse_array_cp(value2[0])
        else:
            value_y = scale_y[0]
            value_var_y = scale_y[1]
            value_x = value2[0]

        hlr_utils.result_insert(result, res_descr, (value_y, value_var_y),
                                map_so, "all", 0, [value_x])

    return result
def energy_transfer(obj, itype, axis_const, **kwargs):
    """
    This function takes a SOM with a wavelength axis (initial for IGS and
    final for DGS) and calculates the energy transfer.  

    @param obj: The object containing the wavelength axis
    @type obj: C{SOM.SOM}

    @param itype: The instrument class type. The choices are either I{IGS} or
                  I{DGS}.
    @type itype: C{string}

    @param axis_const: The attribute name for the axis constant which is the 
                         final wavelength for I{IGS} and the initial energy for
                         I{DGS}.
    @type axis_const: C{string}

    @param kwargs: A list of keyword arguments that the function accepts:

    @keyword units: The units for the incoming axis. The default is
                    I{Angstroms}.
    @type units: C{string}

    @keyword change_units: A flag that signals the function to convert from
                           I{meV} to I{ueV}. The default is I{False}.
    @type change_units: C{boolean}

    @keyword scale: A flag to scale the y-axis by lambda_f/lambda_i for I{IGS}
                    and lambda_i/lambda_f for I{DGS}. The default is I{False}.
    @type scale: C{boolean}

    @keyword lojac: A flag that turns on the calculation and application of
                    the linear-order Jacobian. The default is I{False}.
    @type lojac: C{boolean}

    @keyword sa_norm: A flag to turn on solid angle normlaization.
    @type sa_norm: C{boolean}

    @return: Object with the energy transfer calculated in units of I{meV} or
             I{ueV}. The default is I{meV}.
    @rtype: C{SOM.SOM}


    @raise RuntimeError: The instrument class type is not recognized
    @raise RuntimeError: The x-axis units are not Angstroms
    @raise RuntimeError: A SOM is not given to the function
    """
    # Check the instrument class type to make sure its allowed
    allowed_types = ["DGS", "IGS"]

    if itype not in allowed_types:
        raise RuntimeError("The instrument class type %s is not known. "\
                           +"Please use DGS or IGS" % itype)

    # import the helper functions
    import hlr_utils

    # set up for working through data
    (result, res_descr) = hlr_utils.empty_result(obj)
    o_descr = hlr_utils.get_descr(obj)

    if o_descr != "SOM":
        raise RuntimeError("Must provide a SOM to the function.")
    # Go on
    else:
        pass

    # Setup keyword arguments
    try:
        units = kwargs["units"]
    except KeyError:
        units = "Angstroms"

    try:
        change_units = kwargs["change_units"]
    except KeyError:
        change_units = False

    try:
        scale = kwargs["scale"]
    except KeyError:
        scale = False

    try:
        sa_norm = kwargs["sa_norm"]
    except KeyError:
        sa_norm = False

    if sa_norm:
        inst = obj.attr_list.instrument

    try:
        lojac = kwargs["lojac"]
    except KeyError:
        lojac = False

    # Primary axis for transformation.
    axis = hlr_utils.one_d_units(obj, units)

    # Get the subtraction constant
    try:
        axis_c = obj.attr_list[axis_const]
    except KeyError:
        raise RuntimeError("Must provide a final wavelength (IGS) or initial "\
                           +"energy (DGS) via the incoming SOM")

    result = hlr_utils.copy_som_attr(result, res_descr, obj, o_descr)
    if change_units:
        unit_str = "ueV"
    else:
        unit_str = "meV"
    result = hlr_utils.force_units(result, unit_str, axis)
    result.setAxisLabel(axis, "energy_transfer")
    result.setYUnits("Counts/" + unit_str)
    result.setYLabel("Intensity")

    # iterate through the values
    import array_manip
    import axis_manip
    import dr_lib
    import utils

    for i in xrange(hlr_utils.get_length(obj)):
        if itype == "IGS":
            l_i = hlr_utils.get_value(obj, i, o_descr, "x", axis)
            l_i_err2 = hlr_utils.get_err2(obj, i, o_descr, "x", axis)
        else:
            l_f = hlr_utils.get_value(obj, i, o_descr, "x", axis)
            l_f_err2 = hlr_utils.get_err2(obj, i, o_descr, "x", axis)

        y_val = hlr_utils.get_value(obj, i, o_descr, "y", axis)
        y_err2 = hlr_utils.get_err2(obj, i, o_descr, "y", axis)

        map_so = hlr_utils.get_map_so(obj, None, i)

        if itype == "IGS":
            (E_i, E_i_err2) = axis_manip.wavelength_to_energy(l_i, l_i_err2)
            l_f = hlr_utils.get_special(axis_c, map_so)[:2]
            (E_f, E_f_err2) = axis_manip.wavelength_to_energy(l_f[0], l_f[1])
            if lojac:
                (y_val,
                 y_err2) = utils.linear_order_jacobian(l_i, E_i, y_val, y_err2)
        else:
            (E_i, E_i_err2) = axis_c.toValErrTuple()
            (E_f, E_f_err2) = axis_manip.wavelength_to_energy(l_f, l_f_err2)
            if lojac:
                (y_val,
                 y_err2) = utils.linear_order_jacobian(l_f, E_f, y_val, y_err2)

        if scale:
            # Scale counts by lambda_f / lambda_i
            if itype == "IGS":
                (l_n, l_n_err2) = l_f
                (l_d, l_d_err2) = utils.calc_bin_centers(l_i, l_i_err2)
            else:
                (l_n, l_n_err2) = utils.calc_bin_centers(l_f, l_f_err2)
                (l_d,
                 l_d_err2) = axis_manip.energy_to_wavelength(E_i, E_i_err2)

            ratio = array_manip.div_ncerr(l_n, l_n_err2, l_d, l_d_err2)
            scale_y = array_manip.mult_ncerr(y_val, y_err2, ratio[0], ratio[1])
        else:
            scale_y = (y_val, y_err2)

        value = array_manip.sub_ncerr(E_i, E_i_err2, E_f, E_f_err2)

        if change_units:
            # Convert from meV to ueV
            value2 = array_manip.mult_ncerr(value[0], value[1], 1000.0, 0.0)
            scale_y = array_manip.mult_ncerr(scale_y[0], scale_y[1],
                                             1.0 / 1000.0, 0.0)
        else:
            value2 = value

        if sa_norm:
            if inst.get_name() == "BSS":
                dOmega = dr_lib.calc_BSS_solid_angle(map_so, inst)
                scale_y = array_manip.div_ncerr(scale_y[0], scale_y[1], dOmega,
                                                0.0)
            else:
                raise RuntimeError("Do not know how to get solid angle from "\
                                   +"%s" % inst.get_name())

        if itype == "IGS":
            # Reverse the values due to the conversion
            value_y = axis_manip.reverse_array_cp(scale_y[0])
            value_var_y = axis_manip.reverse_array_cp(scale_y[1])
            value_x = axis_manip.reverse_array_cp(value2[0])
        else:
            value_y = scale_y[0]
            value_var_y = scale_y[1]
            value_x = value2[0]

        hlr_utils.result_insert(result, res_descr, (value_y, value_var_y),
                                map_so, "all", 0, [value_x])

    return result