Ticket #4843: inelastic_indirect_reduction_steps.py

File inelastic_indirect_reduction_steps.py, 39.5 KB (added by Karl Palmen, 8 years ago)

Merge Conflict 5 Apr 2013

Line 
1from reduction.reducer import ReductionStep
2import mantid
3from mantid import config
4from mantid.simpleapi import *
5import string
6import os
7
8class LoadData(ReductionStep):
9    """Handles the loading of the data for Indirect instruments. The summing
10    of input workspaces is handled in this routine, as well as the identifying
11    of detectors that require masking.
12   
13    This step will use the following parameters from the Instrument's parameter
14    file:
15   
16    * Workflow.Masking - identifies the method (if any) on which detectors that
17        are to be masked should be identified.
18    * Workflow.ChopDataIfGreaterThan - if this parameter is specified on the
19        instrument, then the raw data will be split into multiple frames if
20        the largest TOF (X) value in the workspace is greater than the provided
21        value.
22    """
23
24    _multiple_frames = False
25    _sum = False
26    _load_logs = False
27    _monitor_index = None
28    _detector_range_start = None
29    _detector_range_end = None
30    _masking_detectors = []
31    _parameter_file = None
32    _data_files = {}
33    _extra_load_opts = {}
34
35    def __init__(self):
36        """Initialise the ReductionStep. Constructor should set the initial
37        parameters for the step.
38        """
39        super(LoadData, self).__init__()
40        self._sum = False
41        self._load_logs = False
42        self._multiple_frames = False
43        self._monitor_index = None
44        self._detector_range_start = None
45        self._detector_range_end = None
46        self._parameter_file = None
47        self._data_files = {}
48
49    def execute(self, reducer, file_ws):
50        """Loads the data.
51        """
52        wsname = ''
53
54        for output_ws, filename in self._data_files.iteritems():
55            try:
56                self._load_single_file(filename,output_ws)
57                if wsname == "":
58                    wsname = output_ws
59            except RuntimeError, exc:
60                logger.warning("Error loading '%s': %s. File skipped" % (filename, str(exc)))
61                continue
62
63        if ( self._sum ) and ( len(self._data_files) > 1 ):
64            ## Sum files
65            merges = []
66            if ( self._multiple_frames ):
67                self._sum_chopped(wsname)
68            else:
69                self._sum_regular(wsname)
70            ## Need to adjust the reducer's list of workspaces
71            self._data_files = {}
72            self._data_files[wsname] = wsname
73
74    def set_load_logs(self, value):
75        self._load_logs = value
76
77    def set_sum(self, value):
78        self._sum = value
79
80    def set_parameter_file(self, value):
81        self._parameter_file = value
82
83    def set_monitor_index(self, index):
84        self._monitor_index = index
85
86    def set_detector_range(self, start, end):
87        self._detector_range_start = start
88        self._detector_range_end = end
89
90    def set_extra_load_opts(self, opts):
91        self._extra_load_opts = opts
92
93    def get_mask_list(self):
94        return self._masking_detectors
95
96    def set_ws_list(self, value):
97        self._data_files = value
98
99    def get_ws_list(self):
100        return self._data_files
101
102    def _load_single_file(self, filename, output_ws):
103        logger.notice("Loading file %s" % filename)
104
105        loader_name = self._load_data(filename, output_ws)
106
107        inst_name = mtd[output_ws].getInstrument().getName()
108        if inst_name == 'BASIS':
109            ModeratorTzero(InputWorkspace=output_ws,OutputWorkspace= output_ws)
110            basis_mask = mtd[output_ws].getInstrument().getStringParameter(
111                'Workflow.MaskFile')[0]
112            # Quick hack for older BASIS files that only have one side
113            #if (mtd[file].getRun()['run_number'] < 16693):
114            #        basis_mask = "BASIS_Mask_before_16693.xml"
115            basis_mask_filename = os.path.join(config.getString('maskFiles.directory')
116                    , basis_mask)
117            if os.path.isfile(basis_mask_filename):
118                    LoadMask(Instrument="BASIS", OutputWorkspace="__basis_mask", 
119                             InputFile=basis_mask_filename)
120<<<<<<< HEAD
121                    MaskDetectors(Workspace=output_ws, MaskedWorkspace="__basis_mask")
122=======
123                    MaskDetectors(Workspace=filename, MaskedWorkspace="__basis_mask")
124>>>>>>> origin/feature/4843_load_logs
125            else:
126                    logger.notice("Couldn't find specified mask file : " + str(basis_mask_filename))
127
128        if self._parameter_file != None:
129            LoadParameterFile(Workspace=output_ws,Filename= self._parameter_file)
130
131
132        if self._require_chop_data(output_ws):
133            ChopData(InputWorkspace=output_ws,OutputWorkspace= output_ws,Step= 20000.0,NChops= 5, IntegrationRangeLower=5000.0,
134                IntegrationRangeUpper=10000.0, 
135                MonitorWorkspaceIndex=self._monitor_index)
136            self._multiple_frames = True
137        else:
138            self._multiple_frames = False
139
140        if ( self._multiple_frames ):
141            workspaces = mtd[output_ws].getNames()
142        else:
143            workspaces = [output_ws]
144
145
146        logger.debug('self._monitor_index = ' + str(self._monitor_index))
147
148        for ws in workspaces:
149<<<<<<< HEAD
150            if isinstance(mtd[ws],mantid.api.IEventWorkspace):
151                LoadNexusMonitors(Filename=self._data_files[output_ws],
152                                  OutputWorkspace= ws+'_mon')
153=======
154            if (loader_name.endswith('Nexus')):
155                LoadNexusMonitors(Filename=self._data_files[output_ws],OutputWorkspace= ws+'_mon')
156>>>>>>> origin/feature/4843_load_logs
157            else:
158                ## Extract Monitor Spectrum
159                ExtractSingleSpectrum(InputWorkspace=ws,OutputWorkspace= ws+'_mon',WorkspaceIndex= self._monitor_index)
160                ## Crop the workspace to remove uninteresting detectors
161                CropWorkspace(InputWorkspace=ws,OutputWorkspace= ws,
162                    StartWorkspaceIndex=self._detector_range_start,
163                    EndWorkspaceIndex=self._detector_range_end)
164
165        try:
166            msk = mtd[workspaces[0]].getInstrument().getStringParameter('Workflow.Masking')[0]
167        except IndexError:
168            msk = 'None'
169        if ( msk == 'IdentifyNoisyDetectors' ):
170            self._identify_bad_detectors(workspaces[0])
171
172    def _load_data(self, filename, output_ws):
173        if self._parameter_file is not None and "VESUVIO" in self._parameter_file:
174            loaded_ws = LoadVesuvio(Filename=filename, OutputWorkspace=output_ws, SpectrumList="1-198", **self._extra_load_opts)
175            loader_name = "LoadVesuvio"
176        else:
177<<<<<<< HEAD
178            loaded_ws = Load(Filename=filename, OutputWorkspace=output_ws, LoadLogFiles=False, **self._extra_load_opts)
179=======
180            if self._load_logs == True:
181                loaded_ws = Load(Filename=filename, OutputWorkspace=output_ws, LoadLogFiles=True, **self._extra_load_opts)
182                logger.notice("Loaded logs")
183            else:
184                loaded_ws = Load(Filename=filename, OutputWorkspace=output_ws, LoadLogFiles=False, **self._extra_load_opts)
185>>>>>>> origin/feature/4843_load_logs
186            loader_handle = loaded_ws.getHistory().lastAlgorithm()
187            loader_name = loader_handle.getPropertyValue("LoaderName")
188        return loader_name
189
190    def _sum_regular(self, wsname):
191        merges = [[], []]
192        for ws in self._data_files:
193            merges[0].append(ws)
194            merges[1].append(ws+'_mon')
195        MergeRuns(InputWorkspaces=','.join(merges[0]),OutputWorkspace= wsname)
196        MergeRuns(InputWorkspaces=','.join(merges[1]),OutputWorkspace= wsname+'_mon')
197        for n in range(1, len(merges[0])):
198            DeleteWorkspace(Workspace=merges[0][n])
199            DeleteWorkspace(Workspace=merges[1][n])
200        factor = 1.0 / len(self._data_files)
201        Scale(InputWorkspace=wsname,OutputWorkspace= wsname,Factor= factor)
202        Scale(InputWorkspace=wsname+'_mon',OutputWorkspace= wsname+'_mon',Factor= factor)
203
204    def _sum_chopped(self, wsname):
205        merges = []
206        nmerges = len(mtd[wsname].getNames())
207        for n in range(0, nmerges):
208            merges.append([])
209            merges.append([])
210            for file in self._data_files:
211                try:
212                    merges[2*n].append(mtd[file].getNames()[n])
213                    merges[2*n+1].append(mtd[file].getNames()[n]+'_mon')
214                except AttributeError:
215                    if n == 0:
216                        merges[0].append(file)
217                        merges[1].append(file+'_mon')
218        for merge in merges:
219            MergeRuns(InputWorkspaces=','.join(merge),OutputWorkspace= merge[0])
220            factor = 1.0 / len(merge)
221            Scale(InputWorkspace=merge[0],OutputWorkspace= merge[0],Factor= factor)
222            for n in range(1, len(merge)):
223                DeleteWorkspace(Workspace=merge[n])
224
225    def _identify_bad_detectors(self, workspace):
226        IdentifyNoisyDetectors(InputWorkspace=workspace,OutputWorkspace= '__temp_tsc_noise')
227        ws = mtd['__temp_tsc_noise']
228        nhist = ws.getNumberHistograms()
229        self._masking_detectors = []
230        for i in range(0, nhist):
231            if ( ws.readY(i)[0] == 0.0 ):
232                self._masking_detectors.append(i)
233        DeleteWorkspace(Workspace='__temp_tsc_noise')
234        return self._masking_detectors
235
236    def _require_chop_data(self, ws):
237        try:
238            cdigt = mtd[ws].getInstrument().getNumberParameter(
239                'Workflow.ChopDataIfGreaterThan')[0]
240        except IndexError:
241            return False
242        if ( mtd[ws].readX(0)[mtd[ws].blocksize()] > cdigt ):
243            return True
244        else:
245            return False
246
247    def is_multiple_frames(self):
248        return self._multiple_frames
249
250#--------------------------------------------------------------------------------------------------
251
252class BackgroundOperations(ReductionStep):
253    """Removes, if requested, a background from the detectors data in TOF
254    units. Currently only uses the FlatBackground algorithm, more options
255    to cover SNS use to be added at a later point.
256    """
257    _multiple_frames = False
258    _background_start = None
259    _background_end = None
260
261    def __init__(self, MultipleFrames=False):
262        super(BackgroundOperations, self).__init__()
263        self._multiple_frames = MultipleFrames
264        self._background_start = None
265        self._background_end = None
266
267    def execute(self, reducer, file_ws):
268        if ( self._multiple_frames ):
269            try:
270                workspaces = mtd[file_ws].getNames()
271            except AttributeError:
272                workspaces = [file_ws]
273        else:
274            workspaces = [file_ws]
275
276        for ws in workspaces:
277            ConvertToDistribution(Workspace=ws)
278            FlatBackground(InputWorkspace=ws,OutputWorkspace= ws,StartX= self._background_start,
279                           EndX=self._background_end, Mode='Mean')
280            ConvertFromDistribution(Workspace=ws)
281
282    def set_range(self, start, end):
283        self._background_start = start
284        self._background_end = end
285
286class CreateCalibrationWorkspace(ReductionStep):
287    """Creates a calibration workspace from a White-Beam Vanadium run.
288    """
289
290    _back_min = None
291    _back_max = None
292    _peak_min = None
293    _peak_max = None
294    _detector_range_start = None
295    _detector_range_end = None
296    _calib_raw_files = []
297    _calib_workspace = None
298    _analyser = None
299    _reflection = None
300
301    def __init__(self):
302        super(CreateCalibrationWorkspace, self).__init__()
303        self._back_min = None
304        self._back_max = None
305        self._peak_min = None
306        self._peak_max = None
307        self._detector_range_start = None
308        self._detector_range_end = None
309        self._calib_raw_files = []
310        self._calib_workspace = None
311        self._analyser = None
312        self._reflection = None
313
314    def execute(self, reducer, file_ws):
315        """The information we use here is not from the main reducer object
316        (ie, we are not looking at one of the data files.)
317       
318        The ApplyCalibration step is related to this.
319        """
320        rawfiles = self._calib_raw_files
321        if ( len(rawfiles) == 0 ):
322            print "Indirect: No calibration run specified."
323            return
324
325        backMin, backMax, peakMin, peakMax = self._get_calib_details()
326        specMin = self._detector_range_start + 1
327        specMax = self._detector_range_end + 1
328
329        runs = []
330        for file in rawfiles:
331            (direct, filename) = os.path.split(file)
332            (root, ext) = os.path.splitext(filename)
333            try:
334                Load(Filename=file,OutputWorkspace= root, SpectrumMin=specMin, SpectrumMax=specMax,
335                    LoadLogFiles=False)
336                runs.append(root)
337            except:
338                sys.exit('Indirect: Could not load raw file: ' + file)
339        cwsn = 'calibration'
340        if ( len(runs) > 1 ):
341            MergeRuns(InputWorkspaces=",".join(runs),OutputWorkspace= cwsn)
342            factor = 1.0 / len(runs)
343            Scale(InputWorkspace=cwsn,OutputWorkspace= cwsn,Factor= factor)
344        else:
345            cwsn = runs[0]
346        FlatBackground(InputWorkspace=cwsn,OutputWorkspace= cwsn,StartX= backMin,EndX= backMax, Mode='Mean')
347        Integration(InputWorkspace=cwsn,OutputWorkspace= cwsn,RangeLower= peakMin,RangeUpper= peakMax)
348        cal_ws = mtd[cwsn]
349        sum = 0
350        for i in range(0, cal_ws.getNumberHistograms()):
351            sum += cal_ws.readY(i)[0]
352
353        runNo = cal_ws.getRun().getLogData("run_number").value
354        outWS_n = runs[0][:3] + runNo + '_' + self._analyser + self._reflection + '_calib'
355
356        value = 1.0 / ( sum / cal_ws.getNumberHistograms() )
357        Scale(InputWorkspace=cwsn,OutputWorkspace= cwsn,Factor= value,Operation= 'Multiply')
358
359        RenameWorkspace(InputWorkspace=cwsn,OutputWorkspace= outWS_n)
360        self._calib_workspace = outWS_n # Set result workspace value
361        if ( len(runs) > 1 ):
362            for run in runs:
363                DeleteWorkspace(Workspace=run)
364
365    def set_parameters(self, back_min, back_max, peak_min, peak_max):
366        self._back_min = back_min
367        self._back_max = back_max
368        self._peak_min = peak_min
369        self._peak_max = peak_max
370       
371    def set_detector_range(self, start, end):
372        self._detector_range_start = start
373        self._detector_range_end = end
374       
375    def set_instrument_workspace(self, workspace):
376        self._instrument_workspace = workspace
377       
378    def set_files(self, files):
379        if len(files) > 0:
380            self._calib_raw_files = files
381        else:
382            raise ValueError("Indirect: Can't set calib files if you don't "
383                "specify a calib file.")
384   
385    def set_analyser(self, analyser):
386        self._analyser = str(analyser)
387   
388    def set_reflection(self, reflection):
389        self._reflection = str(reflection)
390               
391    def result_workspace(self):
392        return self._calib_workspace
393       
394    def _get_calib_details(self):   
395        if ( self._back_min is None and
396                self._back_max is None and
397                self._peak_min is None and
398                self._peak_max is None ):
399            instrument = mtd[self._instrument_workspace].getInstrument()
400            try:
401                backMin = instrument.getNumberParameter('back-start')[0]
402                backMax = instrument.getNumberParameter('back-end')[0]
403                peakMin = instrument.getNumberParameter('peak-start')[0]
404                peakMax = instrument.getNumberParameter('peak-end')[0]
405            except IndexError:
406                sys.exit("Indirect: Unable to retrieve calibration details "
407                    "from instrument of workspace.")
408            else:
409                return backMin, backMax, peakMin, peakMax
410        else:
411            return ( self._back_min, self._back_max, self._peak_min,
412                self._peak_max )
413
414class ApplyCalibration(ReductionStep):
415    """Applies a calibration workspace to the data.
416    """
417
418    _multiple_frames = False
419    _calib_workspace = None
420
421    def __init__(self):
422        super(ApplyCalibration, self).__init__()
423        self._multiple_frames = False
424        self._calib_workspace = None
425
426    def execute(self, reducer, file_ws):
427        if self._calib_workspace is None: # No calibration workspace set
428            return
429        if ( self._multiple_frames ):
430            try:
431                workspaces = mtd[file_ws].getNames()
432            except AttributeError:
433                workspaces = [file_ws]
434        else:
435            workspaces = [file_ws]
436
437        for ws in workspaces:
438            Divide(LHSWorkspace=ws,RHSWorkspace= self._calib_workspace,OutputWorkspace= ws)
439
440    def set_is_multiple_frames(self, value):
441        self._multiple_frames = value
442
443    def set_calib_workspace(self, value):
444        self._calib_workspace = value
445
446class HandleMonitor(ReductionStep):
447    """Handles the montior for the reduction of inelastic indirect data.
448   
449    This uses the following parameters from the instrument:
450    * Workflow.MonitorArea
451    * Workflow.MonitorThickness
452    * Workflow.MonitorScalingFactor
453    * Workflow.UnwrapMonitor
454    """
455    _multiple_frames = False
456
457    def __init__(self, MultipleFrames=False):
458        """Constructor for HandleMonitor routine.
459        """
460        super(HandleMonitor, self).__init__()
461        self._multiple_frames = MultipleFrames
462
463    def execute(self, reducer, file_ws):
464        """Does everything we want to with the Monitor.
465        """
466        if ( self._multiple_frames ):
467            try:
468                workspaces = mtd[file_ws].getNames()
469            except AttributeError:
470                workspaces = [file_ws]
471        else:
472            workspaces = [file_ws]
473
474        for ws in workspaces:
475            monitor = ws+'_mon'
476            self._rebin_monitor(ws)
477            if self._need_to_unwrap(ws):
478                self._unwrap_monitor(ws)
479            else:
480                ConvertUnits(InputWorkspace=monitor,OutputWorkspace= monitor,Target= 'Wavelength')
481            self._monitor_efficiency(monitor)
482            self._scale_monitor(monitor)
483
484    def _rebin_monitor(self, ws):
485        """For some instruments (e.g. BASIS) the monitor binning is too
486        fine and needs to be rebinned. This is controlled
487        by the 'Workflow.Monitor.RebinStep' parameter set on the
488        instrument.  If no parameter is present, no rebinning will occur.
489        """
490        try:
491            stepsize = mtd[ws].getInstrument().getNumberParameter('Workflow.Monitor.RebinStep')[0]
492        except IndexError:
493            logger.notice("Monitor is not being rebinned.")
494        else:
495            Rebin(InputWorkspace=ws+'_mon',OutputWorkspace= ws+'_mon',Params= stepsize)
496
497    def _need_to_unwrap(self, ws):
498        try:
499            unwrap = mtd[ws].getInstrument().getStringParameter(
500                'Workflow.UnwrapMonitor')[0]
501        except IndexError:
502            return False # Default it to not unwrap
503        if ( unwrap == 'Never' ):
504            return False
505        elif ( unwrap == 'Always' ):
506            return True
507        elif ( unwrap == 'BaseOnTimeRegime' ):
508            SpecMon = mtd[ws+'_mon'].readX(0)[0]
509            SpecDet = mtd[ws].readX(0)[0]
510            if ( SpecMon == SpecDet ):
511                return True
512            else:
513                return False
514        else:
515            return False
516
517    def _unwrap_monitor(self, ws):
518        l_ref = self._get_reference_length(ws, 0)
519        monitor = ws+'_mon'
520        unwrapped_ws, join = UnwrapMonitor(InputWorkspace=monitor, OutputWorkspace=monitor, LRef=l_ref)
521        RemoveBins(InputWorkspace=monitor,OutputWorkspace= monitor,XMin= join-0.001,XMax= join+0.001, 
522            Interpolation='Linear')
523        FFTSmooth(InputWorkspace=monitor,OutputWorkspace=monitor,WorkspaceIndex=0)
524
525    def _get_reference_length(self, ws, index):
526        workspace = mtd[ws]
527        instrument = workspace.getInstrument()
528        sample = instrument.getSample()
529        source = instrument.getSource()
530        detector = workspace.getDetector(index)
531        sample_to_source = sample.getPos() - source.getPos()
532        r = detector.getDistance(sample)
533        x = sample_to_source.getZ()
534        result = x + r
535        return result
536
537    def _monitor_efficiency(self, monitor):
538        inst = mtd[monitor].getInstrument()
539        try:
540            area = inst.getNumberParameter('Workflow.MonitorArea')[0]
541            thickness = inst.getNumberParameter('Workflow.MonitorThickness')[0]
542        except IndexError:
543            raise ValueError('Unable to retrieve monitor thickness and '
544                'area from Instrument Parameter file.')
545        else:
546            if ( area == -1 or thickness == -1 ):
547                return
548            OneMinusExponentialCor(InputWorkspace=monitor,OutputWorkspace= monitor,C= (8.3 * thickness),C1= area)
549
550    def _scale_monitor(self, monitor):
551        """Some instruments wish to scale their data. Doing this at the
552        monitor is the most efficient way to do this. This is controlled
553        by the 'Workflow.MonitorScalingFactor' parameter set on the
554        instrument.
555        """
556        try:
557            factor = mtd[monitor].getInstrument().getNumberParameter(
558                'Workflow.MonitorScalingFactor')[0]
559        except IndexError:
560            print "Monitor is not being scaled."
561        else:
562            if factor != 1.0:
563                Scale(InputWorkspace=monitor,OutputWorkspace= monitor,Factor= ( 1.0 / factor ),Operation= 'Multiply')
564
565class CorrectByMonitor(ReductionStep):
566    """
567    """
568
569    _multiple_frames = False
570    _emode = "Indirect"
571
572    def __init__(self, MultipleFrames=False, EMode="Indirect"):
573        super(CorrectByMonitor, self).__init__()
574        self._multiple_frames = MultipleFrames
575        self._emode = EMode
576
577    def execute(self, reducer, file_ws):
578        if ( self._multiple_frames ):
579            try:
580                workspaces = mtd[file_ws].getNames()
581            except AttributeError:
582                workspaces = [file_ws]
583        else:
584            workspaces = [file_ws]
585
586        for ws in workspaces:
587            ConvertUnits(InputWorkspace=ws,OutputWorkspace= ws,Target= "Wavelength",EMode= self._emode)
588            RebinToWorkspace(WorkspaceToRebin=ws,WorkspaceToMatch= ws+'_mon',OutputWorkspace= ws)
589            Divide(LHSWorkspace=ws,RHSWorkspace= ws+'_mon',OutputWorkspace= ws)
590            DeleteWorkspace(Workspace=ws+'_mon')
591
592    def set_emode(self, emode):
593        """
594        """
595        self._emode = emode
596
597class FoldData(ReductionStep):
598    _result_workspaces = []
599   
600    def __init__(self):
601        super(FoldData, self).__init__()
602        self._result_workspaces = []
603       
604    def execute(self, reducer, file_ws):
605        try:
606            wsgroup = mtd[file_ws].getNames()
607        except AttributeError:
608            return # Not a grouped workspace
609        ws = file_ws+'_merged'
610        MergeRuns(InputWorkspaces=','.join(wsgroup),OutputWorkspace= ws)
611        scaling = self._create_scaling_workspace(wsgroup, ws)
612        for workspace in wsgroup:
613            DeleteWorkspace(Workspace=workspace)
614        Divide(LHSWorkspace=ws,RHSWorkspace= scaling,OutputWorkspace= ws)
615        DeleteWorkspace(Workspace=scaling)
616        RenameWorkspace(InputWorkspace=ws,OutputWorkspace= file_ws)
617        self._result_workspaces.append(file_ws)
618       
619    def get_result_workspaces(self):
620        return self._result_workspaces
621       
622    def _create_scaling_workspace(self, wsgroup, merged):
623        wsname = '__scaling'
624        unit = ''
625        ranges = []
626        lowest = 0
627        highest = 0
628        for ws in wsgroup:
629            if ( unit == '' ):
630                unit = mtd[ws].getAxis(0).getUnit().unitID()
631            low = mtd[ws].dataX(0)[0]
632            high = mtd[ws].dataX(0)[mtd[ws].blocksize()-1]
633            ranges.append([low, high])
634            if low < lowest: lowest = low
635            if high > highest: highest = high
636        dataX = mtd[merged].readX(0)
637        dataY = []
638        dataE = []
639        for i in range(0, mtd[merged].blocksize()):
640            dataE.append(0.0)
641            dataY.append(self._ws_in_range(ranges, dataX[i]))
642        CreateWorkspace(OutputWorkspace=wsname,DataX= dataX,DataY= dataY,DataE= dataE, UnitX=unit)
643        return wsname
644
645    def _ws_in_range(self, ranges, xval):
646        result = 0
647        for range in ranges:
648            if ( xval >= range[0] and xval <= range[1] ): result += 1
649        return result
650
651class ConvertToCm1(ReductionStep):
652    """
653    Converts the workspaces to cm-1.
654    """
655   
656    _multiple_frames = False
657    _save_to_cm_1 = False
658   
659    def __init__(self, MultipleFrames=False):
660        super(ConvertToCm1, self).__init__()
661        self._multiple_frames = MultipleFrames
662   
663    def execute(self, reducer, file_ws):
664       
665        if self._save_to_cm_1 == False:
666            return
667       
668        if ( self._multiple_frames ):
669            try:
670                workspaceNames = mtd[file_ws].getNames()
671            except AttributeError:
672                workspaceNames = [file_ws]
673        else:
674            workspaceNames = [file_ws]
675       
676        for wsName in workspaceNames:
677            try:
678                ws = mtd[wsName]
679            except:
680                continue
681            ConvertUnits(InputWorkspace=ws,OutputWorkspace=ws,EMode='Indirect',Target='DeltaE_inWavenumber')
682
683    def set_save_to_cm_1(self, save_to_cm_1):
684        self._save_to_cm_1 = save_to_cm_1
685           
686class ConvertToEnergy(ReductionStep):
687    """
688    """   
689    _rebin_string = None
690    _multiple_frames = False
691   
692    def __init__(self, MultipleFrames=False):
693        super(ConvertToEnergy, self).__init__()
694        self._rebin_string = None
695        self._multiple_frames = MultipleFrames
696       
697    def execute(self, reducer, file_ws):
698        if ( self._multiple_frames ):
699            try:
700                workspaces = mtd[file_ws].getNames()
701            except AttributeError:
702                workspaces = [file_ws]
703        else:
704            workspaces = [file_ws]
705           
706        for ws in workspaces:
707            ConvertUnits(InputWorkspace=ws,OutputWorkspace= ws,Target= 'DeltaE',EMode= 'Indirect')
708            CorrectKiKf(InputWorkspace=ws,OutputWorkspace= ws,EMode= 'Indirect')
709            if self._rebin_string is not None:
710                if not self._multiple_frames:
711                    Rebin(InputWorkspace=ws,OutputWorkspace= ws,Params= self._rebin_string)
712                   
713        if self._multiple_frames:
714            self._rebin_mf(workspaces)
715
716    def set_rebin_string(self, value):
717        if value is not None:
718            self._rebin_string = value
719
720    def _rebin_mf(self, workspaces):
721        nbin = 0
722        rstwo = self._rebin_string.split(",")
723        if len(rstwo) >= 5:
724            rstwo = ",".join(rstwo[2:])
725        else:
726            rstwo = self._rebin_string
727        for ws in workspaces:
728            nbins = mtd[ws].blocksize()
729            if nbins > nbin: nbin = nbins
730        for ws in workspaces:
731            if (mtd[ws].blocksize() == nbin):
732                Rebin(InputWorkspace=ws,OutputWorkspace= ws,Params= self._rebin_string)
733            else:
734                Rebin(InputWorkspace=ws,OutputWorkspace= ws,Params= rstwo)
735
736class DetailedBalance(ReductionStep):
737    """
738    """
739    _temp = None
740    _multiple_frames = False
741   
742    def __init__(self, MultipleFrames=False):
743        super(DetailedBalance, self).__init__()
744        self._temp = None
745        self._multiple_frames = MultipleFrames
746       
747    def execute(self, reducer, file_ws):
748        if self._temp is None:
749            return
750
751        correction = 11.606 / ( 2 * self._temp )
752
753        if ( self._multiple_frames ):
754            workspaces = mtd[file_ws].getNames()
755        else:
756            workspaces = [file_ws]
757
758        for ws in workspaces:
759            ExponentialCorrection(InputWorkspace=ws,OutputWorkspace= ws,C0= 1.0,C1= correction, Operation="Multiply")
760       
761    def set_temperature(self, temp):
762        self._temp = temp
763           
764class Scaling(ReductionStep):
765    """
766    """
767    _scale_factor = None
768    _multiple_frames = False
769   
770    def __init__(self, MultipleFrames=False):
771        super(Scaling, self).__init__()
772        self._scale_factor = None
773        self._multiple_frames = MultipleFrames
774       
775    def execute(self, reducer, file_ws):
776        if self._scale_factor is None: # Scale factor is the default value, 1.0
777            return
778
779        if ( self._multiple_frames ):
780            workspaces = mtd[file_ws].getNames()
781        else:
782            workspaces = [file_ws]
783
784        for ws in workspaces:
785            Scale(InputWorkspace=ws,OutputWorkspace= ws,Factor= self._scale_factor, Operation="Multiply")
786       
787    def set_scale_factor(self, scaleFactor):
788        self._scale_factor = scaleFactor
789           
790class Grouping(ReductionStep):
791    """This ReductionStep handles the grouping and renaming of the final
792    workspace. In most cases, this will require a Rebin on the data. The option
793    to do this is given in the ConvertToEnergy step.
794   
795    The step will use the following parameters on the workspace:
796    * 'Workflow.GroupingMethod' - if this is set to Fixed, it indicates that
797        the grouping is defined at an instrument level and this can not be
798        altered by the user. In this case, the value given in the function
799        set_grouping_policy() is ignored and an XML grouping file is created
800        based on the string in
801    * 'Workflow.FixedGrouping', which is of the form: "0-69,70-139" where the
802        comma seperates a group, and the hyphen indicates a range. The numbers
803        given are taken to be the workspace indices.
804       
805    If a masking list has been set using set_mask_list(), then the workspace
806    indices listed will not be included in the group (if any grouping is in
807    fact performed).
808    """
809    _grouping_policy = None
810    _masking_detectors = []
811    _result_workspaces = []
812    _multiple_frames = False
813   
814    def __init__(self, MultipleFrames=False):
815        super(Grouping, self).__init__()
816        self._grouping_policy = None
817        self._masking_detectors = []
818        self._result_workspaces = []
819        self._multiple_frames = MultipleFrames
820       
821    def execute(self, reducer, file_ws):
822        if ( self._multiple_frames ):
823            try:
824                workspaces = mtd[file_ws].getNames()
825            except AttributeError:
826                workspaces = [file_ws]
827        else:
828            workspaces = [file_ws]
829           
830        for ws in workspaces:
831            if self._grouping_policy is not None:
832                self._result_workspaces.append(self._group_data(ws))
833            else:
834                try:
835                    group = mtd[ws].getInstrument().getStringParameter(
836                        'Workflow.GroupingMethod')[0]
837                except IndexError:
838                    group = 'User'
839                if ( group == 'Fixed' ):
840                    self._result_workspaces.append(self._group_fixed(ws))
841                elif (group == 'File' ):
842                    self._grouping_policy =  mtd[ws].getInstrument().getStringParameter(
843                        'Workflow.GroupingFile')[0]             
844                    self._result_workspaces.append(self._group_data(ws))
845                else:
846                    self._result_workspaces.append(self._group_data(ws))
847           
848    def set_grouping_policy(self, value):
849        self._grouping_policy = value
850       
851    def set_mask_list(self, value):
852        self._masking_detectors = value
853       
854    def get_result_workspaces(self):
855        return self._result_workspaces
856
857    def _group_fixed(self, workspace):
858        try:
859            grps = mtd[workspace].getInstrument().getStringParameter(
860                'Workflow.FixedGrouping')[0]
861        except IndexError:
862            raise AttributeError('Could not retrieve fixed grouping setting '
863                'from the instrument parameter file.')
864
865        groups = grps.split(",")
866        group_list = []
867        for group in groups:
868            group_to_from = group.split("-")
869            group_vals = range(int(group_to_from[0]), int(group_to_from[1])+1)
870            group_list.append(group_vals)
871           
872        for i in self._masking_detectors:
873            for grp in group_list:
874                try:
875                    grp.remove(i)
876                except ValueError:
877                    pass
878
879        xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
880        xml += "<detector-grouping>\n"
881        for grp in group_list:
882            xml += "<group name=\"group\">\n"
883            xml += "    <ids val=\""
884            for i in grp:
885                xml += str(i + 1)
886                if i != ( grp[len(grp)-1] ):
887                    xml += ","
888            xml += "\"/>\n"
889            xml += "</group>\n"
890        xml += "</detector-grouping>\n"
891       
892        xfile = os.path.join(config.getString('defaultsave.directory'), 'fixedGrp.xml')
893        file = open(xfile, 'w')
894        file.write(xml)
895        file.close()
896        GroupDetectors(InputWorkspace=workspace,OutputWorkspace= workspace, MapFile=xfile, 
897            Behaviour='Average')
898        return workspace
899
900    def _group_data(self, workspace):
901        grouping = self._grouping_policy
902        if ( grouping == 'Individual' ) or ( grouping is None ):
903            return workspace
904        elif ( grouping == 'All' ):
905            nhist = mtd[workspace].getNumberHistograms()
906            wslist = []
907            for i in range(0, nhist):
908                if i not in self._masking_detectors:
909                    wslist.append(i)
910            GroupDetectors(InputWorkspace=workspace,OutputWorkspace= workspace, 
911                WorkspaceIndexList=wslist, Behaviour='Average')
912        else:
913            # Assume we have a grouping file.
914            # First lets, find the file...
915            if (os.path.isfile(grouping)):
916                grouping_filename = grouping
917            else:
918                grouping_filename = os.path.join(config.getString('groupingFiles.directory'),
919                        grouping)
920            # Final check that the Mapfile exists, if not don't run the alg.
921            if os.path.isfile(grouping_filename):
922                GroupDetectors(InputWorkspace=workspace,OutputWorkspace= workspace, MapFile=grouping_filename, 
923                        Behaviour='Average')
924        return workspace
925
926class SaveItem(ReductionStep):
927    """This routine will save a given workspace in the selected file formats.
928    The currently recognised formats are:
929        * 'spe' - SPE ASCII format
930        * 'nxs' - NeXus compressed file format
931        * 'nxspe' - NeXus SPE file format
932        * 'ascii' - Comma Seperated Values (file extension '.dat')
933        * 'gss' - GSAS file format (N.B.: units will be converted to Time of
934            Flight if not already in that unit for saving in this format).
935    """
936    _formats = []
937    _save_to_cm_1 = False
938   
939    def __init__(self):
940        super(SaveItem, self).__init__()
941        self._formats = []
942       
943    def execute(self, reducer, file_ws):
944        naming = Naming()
945        filename = naming._get_ws_name(file_ws)
946        for format in self._formats:
947            if format == 'spe':
948                SaveSPE(InputWorkspace=file_ws,Filename= filename+'.spe')
949            elif format == 'nxs':
950                SaveNexusProcessed(InputWorkspace=file_ws,Filename= filename+'.nxs')
951            elif format == 'nxspe':
952                SaveNXSPE(InputWorkspace=file_ws,Filename= filename+'.nxspe')
953            elif format == 'ascii':
954                SaveAscii(InputWorkspace=file_ws,Filename= filename+'.dat')
955            elif format == 'gss':
956                ConvertUnits(InputWorkspace=file_ws,OutputWorkspace= "__save_item_temp",Target= "TOF")
957                SaveGSS(InputWorkspace="__save_item_temp",Filename= filename+".gss")
958                DeleteWorkspace(Workspace="__save_item_temp")
959            elif format == 'aclimax':
960                if (self._save_to_cm_1 == False):
961                    bins = '3, -0.005, 500' #meV
962                else:
963                    bins = '24, -0.005, 4000' #cm-1
964                Rebin(InputWorkspace=file_ws,OutputWorkspace= file_ws + '_aclimax_save_temp',Params= bins)
965                SaveAscii(InputWorkspace=file_ws + '_aclimax_save_temp',Filename= filename+ '_aclimax.dat', Separator='Tab')
966                DeleteWorkspace(Workspace=file_ws + '_aclimax_save_temp')
967               
968    def set_formats(self, formats):
969        self._formats = formats
970    def set_save_to_cm_1(self, save_to_cm_1):
971        self._save_to_cm_1 = save_to_cm_1
972
973class Naming(ReductionStep):
974    """Takes the responsibility of naming the results away from the Grouping
975    step so that ws names are consistent right up until the last step. This
976    uses the following instrument parameters:
977    * 'Workflow.NamingConvention' - to decide how to name the result workspace.
978        The default (when nothing is selected) is to use the run title.
979    """
980    _result_workspaces = []
981   
982    def __init__(self):
983        super(Naming, self).__init__()
984        self._result_workspaces = []
985       
986    def execute(self, reducer, file_ws):
987        wsname = self._get_ws_name(file_ws)
988        RenameWorkspace(InputWorkspace=file_ws,OutputWorkspace= wsname)
989        self._result_workspaces.append(wsname)
990
991    def get_result_workspaces(self):
992        return self._result_workspaces
993
994    def _get_ws_name(self, workspace):
995        try:
996            type = mtd[workspace].getInstrument().getStringParameter(
997                'Workflow.NamingConvention')[0]
998        except IndexError:
999            type = 'RunTitle'
1000           
1001        if ( type == 'AnalyserReflection' ):
1002            return self._analyser_reflection(workspace)
1003        elif ( type == 'RunTitle' ):
1004            return self._run_title(workspace)
1005        else:
1006            raise NotImplementedError('Unknown \'Workflow.NamingConvention\''
1007                ' parameter encountered on workspace: ' + workspace)
1008       
1009    def _run_title(self, workspace):
1010        ws = mtd[workspace]
1011        title = ws.getRun()['run_title'].value.strip()
1012        runNo = ws.getRun()['run_number'].value
1013        inst = ws.getInstrument().getName()
1014        isn = config.getFacility().instrument(inst).shortName().upper()
1015        valid = "-_.() %s%s" % (string.ascii_letters, string.digits)
1016        title = ''.join(ch for ch in title if ch in valid)
1017        title = isn + runNo + '-' + title
1018        return title
1019       
1020    def _analyser_reflection(self, workspace):
1021        if workspace == '':
1022            return ''
1023        ws = mtd[workspace]
1024        ins = ws.getInstrument().getName()
1025        ins = config.getFacility().instrument(ins).shortName().lower()
1026        run = ws.getRun().getLogData('run_number').value
1027        try:
1028            analyser = ws.getInstrument().getStringParameter('analyser')[0]
1029            reflection = ws.getInstrument().getStringParameter('reflection')[0]
1030        except IndexError:
1031            analyser = ''
1032            reflection = ''
1033        prefix = ins + run + '_' + analyser + reflection + '_red'
1034        return prefix