Example #1
0
    def test_convert(self):
        ws = Load(Filename='INTER00013460')
        converter = ConvertToWavelength(ws)

        monitor_ws, detector_ws = converter.convert(
            wavelength_min=0.0,
            wavelength_max=10.0,
            detector_workspace_indexes=(2, 4),
            monitor_workspace_index=0,
            correct_monitor=True,
            bg_min=2.0,
            bg_max=8.0)

        self.assertEqual(1, monitor_ws.getNumberHistograms(),
                         "Wrong number of spectra in monitor workspace")
        self.assertEqual(3, detector_ws.getNumberHistograms(),
                         "Wrong number of spectra in detector workspace")
        self.assertEqual("Wavelength",
                         detector_ws.getAxis(0).getUnit().unitID())
        self.assertEqual("Wavelength",
                         monitor_ws.getAxis(0).getUnit().unitID())
        x_min, x_max = ConvertToWavelengthTest.cropped_x_range(detector_ws, 0)

        self.assertTrue(x_min >= 0.0)
        self.assertTrue(x_max <= 10.0)
    def test_convert(self):
        ws = Load(Filename='INTER00013460')
        converter = ConvertToWavelength(ws)

        monitor_ws, detector_ws = converter.convert(wavelength_min=0.0, wavelength_max=10.0, detector_workspace_indexes = (2,4), monitor_workspace_index=0, correct_monitor=True, bg_min=2.0, bg_max=8.0)

        self.assertEqual(1, monitor_ws.getNumberHistograms(), "Wrong number of spectra in monitor workspace")
        self.assertEqual(3, detector_ws.getNumberHistograms(), "Wrong number of spectra in detector workspace")
        self.assertEqual("Wavelength", detector_ws.getAxis(0).getUnit().unitID())
        self.assertEqual("Wavelength", monitor_ws.getAxis(0).getUnit().unitID())
        x_min, x_max = ConvertToWavelengthTest.cropped_x_range(detector_ws, 0)

        self.assertTrue(x_min >= 0.0)
        self.assertTrue(x_max <= 10.0)
Example #3
0
def make_trans_corr(transrun, stitch_start_overlap, stitch_end_overlap, stitch_params,
                    lambda_min=None, lambda_max=None, background_min=None,
                    background_max=None, int_min=None, int_max=None, detector_index_ranges=None,
                    i0_monitor_index=None):
    '''
    Make the transmission correction workspace.
    '''


    # Check to see whether all optional inputs have been provide. If not we have to get them from the IDF.
    if not all((lambda_min, lambda_max, background_min, background_max, int_min, int_max, detector_index_ranges, i0_monitor_index)):
        logger.notice("make_trans_corr: Fetching missing arguments from the IDF")
        instrument_source = transrun
        if isinstance(transrun, str):
            instrument_source = transrun.split(',')[0]
        trans_ws = ConvertToWavelength.to_workspace(instrument_source)
        idf_defaults = get_defaults(trans_ws)

        # Fetch defaults for anything not specified
        if not i0_monitor_index:
            i0_monitor_index = idf_defaults['I0MonitorIndex']
        if not lambda_min:
            lambda_min = idf_defaults['LambdaMin']
        if not lambda_max:
            lambda_max = idf_defaults['LambdaMax']
        if not detector_index_ranges:
            point_detector_start = idf_defaults['PointDetectorStart']
            point_detector_stop =  idf_defaults['PointDetectorStop']
            detector_index_ranges = (point_detector_start, point_detector_stop)
        if not background_min:
            background_min = idf_defaults['MonitorBackgroundMin']
        if not background_max:
            background_max = idf_defaults['MonitorBackgroundMax']
        if not int_min:
            int_min = idf_defaults['MonitorIntegralMin']
        if not int_max:
            int_max = idf_defaults['MonitorIntegralMax']


    transWS = None
    if isinstance(transrun, str) and (',' in transrun):
        slam = transrun.split(',')[0]
        llam = transrun.split(',')[1]
        print "Transmission runs: ", transrun

        to_lam = ConvertToWavelength(slam)
        _monitor_ws_slam, _detector_ws_slam = to_lam.convert(wavelength_min=lambda_min, wavelength_max=lambda_max, detector_workspace_indexes=detector_index_ranges, monitor_workspace_index=i0_monitor_index, correct_monitor=True, bg_min=background_min, bg_max=background_max )

        _i0p_slam = RebinToWorkspace(WorkspaceToRebin=_monitor_ws_slam, WorkspaceToMatch=_detector_ws_slam)
        _mon_int_trans = Integration(InputWorkspace=_i0p_slam, RangeLower=int_min, RangeUpper=int_max)
        _detector_ws_slam = Divide(LHSWorkspace=_detector_ws_slam, RHSWorkspace=_mon_int_trans)

        to_lam = ConvertToWavelength(llam)
        _monitor_ws_llam, _detector_ws_llam = to_lam.convert(wavelength_min=lambda_min, wavelength_max=lambda_max, detector_workspace_indexes=detector_index_ranges, monitor_workspace_index=i0_monitor_index, correct_monitor=True, bg_min=background_min, bg_max=background_max )

        _i0p_llam = RebinToWorkspace(WorkspaceToRebin=_monitor_ws_llam, WorkspaceToMatch=_detector_ws_llam)
        _mon_int_trans = Integration(InputWorkspace=_i0p_llam, RangeLower=int_min,RangeUpper=int_max)
        _detector_ws_llam = Divide(LHSWorkspace=_detector_ws_llam, RHSWorkspace=_mon_int_trans)

        print stitch_start_overlap, stitch_end_overlap, stitch_params
        transWS, outputScaling = Stitch1D(LHSWorkspace=_detector_ws_slam, RHSWorkspace=_detector_ws_llam, StartOverlap=stitch_start_overlap,\
                                           EndOverlap=stitch_end_overlap,  Params=stitch_params)

        transWS = RenameWorkspace(InputWorkspace=transWS, OutputWorkspace="TRANS_" + slam + "_" + llam)
    else:

        to_lam = ConvertToWavelength(transrun)
        _monitor_ws_trans, _detector_ws_trans = to_lam.convert(wavelength_min=lambda_min, wavelength_max=lambda_max, detector_workspace_indexes=detector_index_ranges, monitor_workspace_index=i0_monitor_index, correct_monitor=True, bg_min=background_min, bg_max=background_max )
        _i0p_trans = RebinToWorkspace(WorkspaceToRebin=_monitor_ws_trans, WorkspaceToMatch=_detector_ws_trans)

        _mon_int_trans = Integration( InputWorkspace=_i0p_trans, RangeLower=int_min, RangeUpper=int_max )
        transWS = Divide( LHSWorkspace=_detector_ws_trans, RHSWorkspace=_mon_int_trans )

        transWS = RenameWorkspace(InputWorkspace=transWS, OutputWorkspace="TRANS_" + transrun)
    return transWS
Example #4
0
def quick_explicit(run, i0_monitor_index, lambda_min, lambda_max,  background_min, background_max, int_min, int_max,
                   point_detector_start=0, point_detector_stop=0, multi_detector_start=0, theta=None,
                   pointdet=True,roi=[0,0], db=[0,0], trans='', debug=False, correction_strategy=NullCorrectionStrategy(),
                   stitch_start_overlap=None, stitch_end_overlap=None, stitch_params=None,
                   polcorr=False, crho=None, calpha=None, cAp=None, cPp=None, detector_component_name='point-detector',
                   sample_component_name='some-surface-holder', correct_positions=True ):

    '''
    Version of quick where all parameters are explicitly provided.
    '''

    _sample_ws = ConvertToWavelength.to_single_workspace(run)
    nHist =  _sample_ws.getNumberHistograms()
    to_lam = ConvertToWavelength(run)

    if pointdet:
        detector_index_ranges = (point_detector_start, point_detector_stop)
    else:
        detector_index_ranges = (multi_detector_start, nHist-1)


    _monitor_ws, _detector_ws = to_lam.convert(wavelength_min=lambda_min, wavelength_max=lambda_max,
                                               detector_workspace_indexes=detector_index_ranges,
                                               monitor_workspace_index=i0_monitor_index, correct_monitor=True,
                                               bg_min=background_min, bg_max=background_max )

    inst = _sample_ws.getInstrument()
    # Some beamline constants from IDF

    print i0_monitor_index
    print nHist

    if run=='0':
        RunNumber = '0'
    else:
        RunNumber = groupGet(_sample_ws.getName(),'samp','run_number')

    if not pointdet:
        # Proccess Multi-Detector; assume MD goes to the end:
        # if roi or db are given in the function then sum over the apropriate channels
        print "This is a multidetector run."

        _I0M = RebinToWorkspace(WorkspaceToRebin=_monitor_ws,WorkspaceToMatch=_detector_ws)
        IvsLam = _detector_ws / _I0M
        if (roi != [0,0]) :
            ReflectedBeam = SumSpectra(InputWorkspace=IvsLam, StartWorkspaceIndex=roi[0], EndWorkspaceIndex=roi[1])
        if (db != [0,0]) :
            DirectBeam = SumSpectra(InputWorkspace=_detector_ws, StartWorkspaceIndex=db[0], EndWorkspaceIndex=db[1])
            ReflectedBeam = ReflectedBeam / DirectBeam
        polCorr(polcorr, IvsLam, crho, calpha, cAp, cPp)
        if theta and correct_positions:
            IvsQ = l2q(ReflectedBeam, detector_component_name, theta, sample_component_name)
        else:
            IvsQ = ConvertUnits(InputWorkspace=ReflectedBeam, Target="MomentumTransfer")


    # Single Detector processing-------------------------------------------------------------
    else:
        print "This is a Point-Detector run."
        # handle transmission runs
        # process the point detector reflectivity
        _I0P = RebinToWorkspace(WorkspaceToRebin=_monitor_ws,WorkspaceToMatch=_detector_ws)
        IvsLam = Scale(InputWorkspace=_detector_ws,Factor=1)

        if not trans:
            print "No transmission file. Trying default exponential/polynomial correction..."
            IvsLam = correction_strategy.apply(_detector_ws)
            IvsLam = Divide(LHSWorkspace=IvsLam, RHSWorkspace=_I0P)
        else: # we have a transmission run
            _monInt = Integration(InputWorkspace=_I0P,RangeLower=int_min,RangeUpper=int_max)
            IvsLam = Divide(LHSWorkspace=_detector_ws,RHSWorkspace=_monInt)
            names = mtd.getObjectNames()

            IvsLam = transCorr(trans, IvsLam, lambda_min, lambda_max, background_min, background_max,
                               int_min, int_max, detector_index_ranges, i0_monitor_index, stitch_start_overlap,
                               stitch_end_overlap, stitch_params )


        IvsLam = polCorr(polcorr, IvsLam, crho, calpha, cAp, cPp)



        # Convert to I vs Q
        # check if detector in direct beam
        if theta == None or theta == 0 or theta == '':
            inst = groupGet('IvsLam','inst')
            detLocation=inst.getComponentByName(detector_component_name).getPos()
            sampleLocation=inst.getComponentByName(sample_component_name).getPos()
            detLocation=inst.getComponentByName(detector_component_name).getPos()
            source=inst.getSource()
            beamPos = sampleLocation - source.getPos()
            theta = groupGet(str(_sample_ws),'samp','theta')
            if not theta:
                theta = inst.getComponentByName(detector_component_name).getTwoTheta(sampleLocation, beamPos)*180.0/math.pi/2.0
            print "Det location: ", detLocation, "Calculated theta = ",theta
            if correct_positions:  # detector is not in correct place
                # Get detector angle theta from NeXuS
                logger.information('The detectorlocation is not at Y=0')
                print 'Nexus file theta =', theta
                IvsQ = l2q(IvsLam, detector_component_name, theta, sample_component_name)
            else:
                IvsQ = ConvertUnits(InputWorkspace=IvsLam,OutputWorkspace="IvsQ",Target="MomentumTransfer")

        else:
            if correct_positions:
                theta = float(theta)
                try:
                    IvsQ = l2q(IvsLam, detector_component_name, theta, sample_component_name)
                except AttributeError:
                    logger.warning("detector_component_name " + detector_component_name + " is unknown")
                    IvsQ = ConvertUnits(InputWorkspace=IvsLam,OutputWorkspace="IvsQ",Target="MomentumTransfer")
            else:
                IvsQ = ConvertUnits(InputWorkspace=IvsLam,OutputWorkspace="IvsQ",Target="MomentumTransfer")

    RenameWorkspace(InputWorkspace=IvsLam,OutputWorkspace=RunNumber+'_IvsLam')
    if isinstance(IvsLam, WorkspaceGroup):
        counter = 0
        for ws in IvsLam:
            RenameWorkspace(ws, OutputWorkspace=RunNumber+'_IvsLam_'+str(counter))
            counter += 1
    RenameWorkspace(InputWorkspace=IvsQ,OutputWorkspace=RunNumber+'_IvsQ')

    # delete all temporary workspaces unless in debug mode (debug=1)

    if not debug:
        cleanup()
        if mtd.doesExist('IvsLam'):
            DeleteWorkspace('IvsLam')
    return  mtd[RunNumber+'_IvsLam'], mtd[RunNumber+'_IvsQ'], theta
Example #5
0
def make_trans_corr(transrun,
                    stitch_start_overlap,
                    stitch_end_overlap,
                    stitch_params,
                    lambda_min=None,
                    lambda_max=None,
                    background_min=None,
                    background_max=None,
                    int_min=None,
                    int_max=None,
                    detector_index_ranges=None,
                    i0_monitor_index=None):
    '''
    Make the transmission correction workspace.
    '''

    # Check to see whether all optional inputs have been provide. If not we have to get them from the IDF.
    if not all((lambda_min, lambda_max, background_min, background_max,
                int_min, int_max, detector_index_ranges, i0_monitor_index)):
        logger.notice(
            "make_trans_corr: Fetching missing arguments from the IDF")
        instrument_source = transrun
        if isinstance(transrun, str):
            instrument_source = transrun.split(',')[0]
        trans_ws = ConvertToWavelength.to_workspace(instrument_source)
        idf_defaults = get_defaults(trans_ws)

        # Fetch defaults for anything not specified
        if not i0_monitor_index:
            i0_monitor_index = idf_defaults['I0MonitorIndex']
        if not lambda_min:
            lambda_min = idf_defaults['LambdaMin']
        if not lambda_max:
            lambda_max = idf_defaults['LambdaMax']
        if not detector_index_ranges:
            point_detector_start = idf_defaults['PointDetectorStart']
            point_detector_stop = idf_defaults['PointDetectorStop']
            detector_index_ranges = (point_detector_start, point_detector_stop)
        if not background_min:
            background_min = idf_defaults['MonitorBackgroundMin']
        if not background_max:
            background_max = idf_defaults['MonitorBackgroundMax']
        if not int_min:
            int_min = idf_defaults['MonitorIntegralMin']
        if not int_max:
            int_max = idf_defaults['MonitorIntegralMax']

    transWS = None
    if isinstance(transrun, str) and (',' in transrun):
        slam = transrun.split(',')[0]
        llam = transrun.split(',')[1]
        print("Transmission runs: ", transrun)

        to_lam = ConvertToWavelength(slam)
        _monitor_ws_slam, _detector_ws_slam = to_lam.convert(
            wavelength_min=lambda_min,
            wavelength_max=lambda_max,
            detector_workspace_indexes=detector_index_ranges,
            monitor_workspace_index=i0_monitor_index,
            correct_monitor=True,
            bg_min=background_min,
            bg_max=background_max)

        _i0p_slam = RebinToWorkspace(WorkspaceToRebin=_monitor_ws_slam,
                                     WorkspaceToMatch=_detector_ws_slam)
        _mon_int_trans = Integration(InputWorkspace=_i0p_slam,
                                     RangeLower=int_min,
                                     RangeUpper=int_max)
        _detector_ws_slam = Divide(LHSWorkspace=_detector_ws_slam,
                                   RHSWorkspace=_mon_int_trans)

        to_lam = ConvertToWavelength(llam)
        _monitor_ws_llam, _detector_ws_llam = to_lam.convert(
            wavelength_min=lambda_min,
            wavelength_max=lambda_max,
            detector_workspace_indexes=detector_index_ranges,
            monitor_workspace_index=i0_monitor_index,
            correct_monitor=True,
            bg_min=background_min,
            bg_max=background_max)

        _i0p_llam = RebinToWorkspace(WorkspaceToRebin=_monitor_ws_llam,
                                     WorkspaceToMatch=_detector_ws_llam)
        _mon_int_trans = Integration(InputWorkspace=_i0p_llam,
                                     RangeLower=int_min,
                                     RangeUpper=int_max)
        _detector_ws_llam = Divide(LHSWorkspace=_detector_ws_llam,
                                   RHSWorkspace=_mon_int_trans)

        print(stitch_start_overlap, stitch_end_overlap, stitch_params)
        transWS, _outputScaling = Stitch1D(LHSWorkspace=_detector_ws_slam,
                                           RHSWorkspace=_detector_ws_llam,
                                           StartOverlap=stitch_start_overlap,
                                           EndOverlap=stitch_end_overlap,
                                           Params=stitch_params)

        transWS = RenameWorkspace(InputWorkspace=transWS,
                                  OutputWorkspace="TRANS_" + slam + "_" + llam)
    else:

        to_lam = ConvertToWavelength(transrun)
        _monitor_ws_trans, _detector_ws_trans = to_lam.convert(
            wavelength_min=lambda_min,
            wavelength_max=lambda_max,
            detector_workspace_indexes=detector_index_ranges,
            monitor_workspace_index=i0_monitor_index,
            correct_monitor=True,
            bg_min=background_min,
            bg_max=background_max)
        _i0p_trans = RebinToWorkspace(WorkspaceToRebin=_monitor_ws_trans,
                                      WorkspaceToMatch=_detector_ws_trans)

        _mon_int_trans = Integration(InputWorkspace=_i0p_trans,
                                     RangeLower=int_min,
                                     RangeUpper=int_max)
        transWS = Divide(LHSWorkspace=_detector_ws_trans,
                         RHSWorkspace=_mon_int_trans)

        transWS = RenameWorkspace(InputWorkspace=transWS,
                                  OutputWorkspace="TRANS_" + transrun)
    return transWS
Example #6
0
def quick_explicit(run,
                   i0_monitor_index,
                   lambda_min,
                   lambda_max,
                   background_min,
                   background_max,
                   int_min,
                   int_max,
                   point_detector_start=0,
                   point_detector_stop=0,
                   multi_detector_start=0,
                   theta=None,
                   pointdet=True,
                   roi=[0, 0],
                   db=[0, 0],
                   trans='',
                   debug=False,
                   correction_strategy=NullCorrectionStrategy(),
                   stitch_start_overlap=None,
                   stitch_end_overlap=None,
                   stitch_params=None,
                   polcorr=False,
                   crho=None,
                   calpha=None,
                   cAp=None,
                   cPp=None,
                   detector_component_name='point-detector',
                   sample_component_name='some-surface-holder',
                   correct_positions=True):
    '''
    Version of quick where all parameters are explicitly provided.
    '''

    _sample_ws = ConvertToWavelength.to_single_workspace(run)
    nHist = _sample_ws.getNumberHistograms()
    to_lam = ConvertToWavelength(run)

    if pointdet:
        detector_index_ranges = (point_detector_start, point_detector_stop)
    else:
        detector_index_ranges = (multi_detector_start, nHist - 1)

    _monitor_ws, _detector_ws = to_lam.convert(
        wavelength_min=lambda_min,
        wavelength_max=lambda_max,
        detector_workspace_indexes=detector_index_ranges,
        monitor_workspace_index=i0_monitor_index,
        correct_monitor=True,
        bg_min=background_min,
        bg_max=background_max)

    inst = _sample_ws.getInstrument()
    # Some beamline constants from IDF

    print(i0_monitor_index)
    print(nHist)

    if run == '0':
        RunNumber = '0'
    else:
        RunNumber = groupGet(_sample_ws.name(), 'samp', 'run_number')

    if not pointdet:
        # Proccess Multi-Detector; assume MD goes to the end:
        # if roi or db are given in the function then sum over the apropriate channels
        print("This is a multidetector run.")

        _I0M = RebinToWorkspace(WorkspaceToRebin=_monitor_ws,
                                WorkspaceToMatch=_detector_ws)
        IvsLam = _detector_ws / _I0M
        if (roi != [0, 0]):
            ReflectedBeam = SumSpectra(InputWorkspace=IvsLam,
                                       StartWorkspaceIndex=roi[0],
                                       EndWorkspaceIndex=roi[1])
        if (db != [0, 0]):
            DirectBeam = SumSpectra(InputWorkspace=_detector_ws,
                                    StartWorkspaceIndex=db[0],
                                    EndWorkspaceIndex=db[1])
            ReflectedBeam = ReflectedBeam / DirectBeam
        polCorr(polcorr, IvsLam, crho, calpha, cAp, cPp)
        if theta and correct_positions:
            IvsQ = l2q(ReflectedBeam, detector_component_name, theta,
                       sample_component_name)
        else:
            IvsQ = ConvertUnits(InputWorkspace=ReflectedBeam,
                                Target="MomentumTransfer")

    # Single Detector processing-------------------------------------------------------------
    else:
        print("This is a Point-Detector run.")
        # handle transmission runs
        # process the point detector reflectivity
        _I0P = RebinToWorkspace(WorkspaceToRebin=_monitor_ws,
                                WorkspaceToMatch=_detector_ws)
        IvsLam = Scale(InputWorkspace=_detector_ws, Factor=1)

        if not trans:
            print(
                "No transmission file. Trying default exponential/polynomial correction..."
            )
            IvsLam = correction_strategy.apply(_detector_ws)
            IvsLam = Divide(LHSWorkspace=IvsLam, RHSWorkspace=_I0P)
        else:  # we have a transmission run
            _monInt = Integration(InputWorkspace=_I0P,
                                  RangeLower=int_min,
                                  RangeUpper=int_max)
            IvsLam = Divide(LHSWorkspace=_detector_ws, RHSWorkspace=_monInt)

            IvsLam = transCorr(trans, IvsLam, lambda_min, lambda_max,
                               background_min, background_max, int_min,
                               int_max, detector_index_ranges,
                               i0_monitor_index, stitch_start_overlap,
                               stitch_end_overlap, stitch_params)

        IvsLam = polCorr(polcorr, IvsLam, crho, calpha, cAp, cPp)

        # Convert to I vs Q
        # check if detector in direct beam
        if theta is None or theta == 0 or theta == '':
            inst = groupGet('IvsLam', 'inst')
            detLocation = inst.getComponentByName(
                detector_component_name).getPos()
            sampleLocation = inst.getComponentByName(
                sample_component_name).getPos()
            detLocation = inst.getComponentByName(
                detector_component_name).getPos()
            source = inst.getSource()
            beamPos = sampleLocation - source.getPos()
            theta = groupGet(str(_sample_ws), 'samp', 'theta')
            if not theta:
                theta = inst.getComponentByName(
                    detector_component_name).getTwoTheta(
                        sampleLocation, beamPos) * 180.0 / math.pi / 2.0
            print("Det location: ", detLocation, "Calculated theta = ", theta)
            if correct_positions:  # detector is not in correct place
                # Get detector angle theta from NeXuS
                logger.information('The detectorlocation is not at Y=0')
                print('Nexus file theta =', theta)
                IvsQ = l2q(IvsLam, detector_component_name, theta,
                           sample_component_name)
            else:
                IvsQ = ConvertUnits(InputWorkspace=IvsLam,
                                    OutputWorkspace="IvsQ",
                                    Target="MomentumTransfer")

        else:
            if correct_positions:
                theta = float(theta)
                try:
                    IvsQ = l2q(IvsLam, detector_component_name, theta,
                               sample_component_name)
                except AttributeError:
                    logger.warning("detector_component_name " +
                                   detector_component_name + " is unknown")
                    IvsQ = ConvertUnits(InputWorkspace=IvsLam,
                                        OutputWorkspace="IvsQ",
                                        Target="MomentumTransfer")
            else:
                IvsQ = ConvertUnits(InputWorkspace=IvsLam,
                                    OutputWorkspace="IvsQ",
                                    Target="MomentumTransfer")

    RenameWorkspace(InputWorkspace=IvsLam,
                    OutputWorkspace=RunNumber + '_IvsLam')
    if isinstance(IvsLam, WorkspaceGroup):
        counter = 0
        for ws in IvsLam:
            RenameWorkspace(ws,
                            OutputWorkspace=RunNumber + '_IvsLam_' +
                            str(counter))
            counter += 1
    RenameWorkspace(InputWorkspace=IvsQ, OutputWorkspace=RunNumber + '_IvsQ')

    # delete all temporary workspaces unless in debug mode (debug=1)

    if not debug:
        cleanup()
        if mtd.doesExist('IvsLam'):
            DeleteWorkspace('IvsLam')
    return mtd[RunNumber + '_IvsLam'], mtd[RunNumber + '_IvsQ'], theta