diff --git a/Code/Mantid/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h b/Code/Mantid/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h index e298d51c6e32..b001069e3949 100644 --- a/Code/Mantid/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h +++ b/Code/Mantid/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h @@ -156,9 +156,9 @@ class SANSRunWindow : public MantidQt::API::UserSubWindow /// Create a mask string void addUserMaskStrings(QString & exec_script,const QString& importCommand,enum MaskType mType); /// Set geometry details - void setGeometryDetails(const QString & sample_logs, const QString & can_logs); + void setGeometryDetails(); /// Set the SANS2D geometry - void setSANS2DGeometry(boost::shared_ptr workspace, const QString & logs, int wscode); + void setSANS2DGeometry(boost::shared_ptr workspace, int wscode); /// Set LOQ geometry void setLOQGeometry(boost::shared_ptr workspace, int wscode); /// Mark an error on a label @@ -168,7 +168,7 @@ class SANSRunWindow : public MantidQt::API::UserSubWindow /// Run an assign command bool runAssign(int key, QString & logs); /// Load a scatter sample file or can run via Python objects using the passed Python command - bool assignDetBankRun(MantidWidgets::MWRunFiles & runFile, const QString & assignFn, QString & logs); + bool assignDetBankRun(MantidWidgets::MWRunFiles & runFile, const QString & assignFn); /// runs that contain only monitor counts can be direct or transmission runs bool assignMonitorRun(MantidWidgets::MWRunFiles & trans, MantidWidgets::MWRunFiles & direct, const QString & assignFn); /// Get the detectors' names diff --git a/Code/Mantid/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp b/Code/Mantid/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp index 0b0c90e6f353..303530353b1d 100644 --- a/Code/Mantid/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp +++ b/Code/Mantid/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp @@ -1422,7 +1422,7 @@ void SANSRunWindow::applyMask(const QString& wsName,bool time_pixel) /** * Set the information about component distances on the geometry tab */ -void SANSRunWindow::setGeometryDetails(const QString & sample_logs, const QString & can_logs) +void SANSRunWindow::setGeometryDetails() { resetGeometryDetailsBox(); @@ -1506,7 +1506,7 @@ void SANSRunWindow::setGeometryDetails(const QString & sample_logs, const QStrin } //SANS2D - Sample - setSANS2DGeometry(sample_workspace, sample_logs, 0); + setSANS2DGeometry(sample_workspace, 0); //Get the can workspace if there is one QString can = m_experCan; if( can.isEmpty() ) @@ -1530,7 +1530,7 @@ void SANSRunWindow::setGeometryDetails(const QString & sample_logs, const QStrin can_workspace = getGroupMember(workspace_ptr, 1); } - setSANS2DGeometry(can_workspace, can_logs, 1); + setSANS2DGeometry(can_workspace, 1); //Check for discrepancies bool warn_user(false); @@ -1569,10 +1569,9 @@ void SANSRunWindow::setGeometryDetails(const QString & sample_logs, const QStrin /** * Set SANS2D geometry info * @param workspace :: The workspace - * @param logs :: The log information - * @param wscode :: ????? + * @param wscode :: 0 for sample, 1 for can, others not defined */ -void SANSRunWindow::setSANS2DGeometry(boost::shared_ptr workspace, const QString & logs, int wscode) +void SANSRunWindow::setSANS2DGeometry(boost::shared_ptr workspace, int wscode) { double unitconv = 1000.; @@ -1596,33 +1595,30 @@ void SANSRunWindow::setSANS2DGeometry(boost::shared_ptrsetText(formatDouble(distance, "black", 'f', 1)); + + // get the tuple of log values and convert to a list of + QString code_to_run = QString("print ','.join([str(a) for a in i.ReductionSingleton().instrument.getDetValues('%1')])").arg(QString::fromStdString(workspace->name())); - // Detectors - QStringList det_info = logs.split(","); - QStringListIterator itr(det_info); - while( itr.hasNext() ) - { - QString line = itr.next(); - QStringList values = line.split(":"); - QString detname = values[0].trimmed(); - QString distance = values[1].trimmed(); - trimPyMarkers(detname); - - // instrument scientists wants distances printed - // out with just one digit - try - { - double d = distance.toDouble(); - distance = QString::number(d, 'f', 1); - } - catch(...) - { - // if distance is not a double for some reason - // for now just proceed - } + QStringList logvalues = runReduceScriptFunction(code_to_run).split(","); - QLabel *lbl = m_s2d_detlabels[wscode].value(detname); - if( lbl ) lbl->setText(distance); + QStringList dets_names; + dets_names << "Front_Det_Z" + << "Front_Det_X" + << "Front_Det_Rot" + << "Rear_Det_Z" + << "Rear_Det_X"; + int index = 0; + foreach(QString detname, dets_names){ + QString distance = logvalues[index]; + try{ + double d = distance.toDouble(); + distance = QString::number(d, 'f', 1); + }catch(...){ + // if distance is not a double, for now just proceed + } + QLabel * lbl = m_s2d_detlabels[wscode].value(detname); + if (lbl) lbl->setText(distance); + index += 1; } } @@ -1818,11 +1814,10 @@ bool SANSRunWindow::handleLoadButtonClick() // set the detector just before loading so to correctly move the instrument runReduceScriptFunction("\ni.ReductionSingleton().instrument.setDetector('" + m_uiForm.detbank_sel->currentText() + "')"); - QString sample_logs, can_logs; QString sample = m_uiForm.scatterSample->getFirstFilename(); try {//preliminarly error checking is over try to load that data - is_loaded &= assignDetBankRun(*(m_uiForm.scatterSample), "AssignSample", sample_logs); + is_loaded &= assignDetBankRun(*(m_uiForm.scatterSample), "AssignSample"); readNumberOfEntries("get_sample().loader", m_uiForm.scatterSample); if (m_uiForm.scatCan->isEmpty()) { @@ -1830,8 +1825,8 @@ bool SANSRunWindow::handleLoadButtonClick() } else { - is_loaded &= assignDetBankRun(*(m_uiForm.scatCan), "AssignCan", can_logs); - readNumberOfEntries("background_subtracter", m_uiForm.scatCan); + is_loaded &= assignDetBankRun(*(m_uiForm.scatCan), "AssignCan"); + readNumberOfEntries("get_can().loader", m_uiForm.scatCan); } if ( ( ! m_uiForm.transmis->isEmpty() ) && ( ! m_uiForm.direct->isEmpty() ) ) { @@ -1858,28 +1853,15 @@ bool SANSRunWindow::handleLoadButtonClick() g_log.error() << "Problem loading file\n"; is_loaded = false; } - if( m_uiForm.inst_opt->currentText() == "SANS2D" && sample_logs.isEmpty() ) - { - is_loaded = false; - showInformationBox("Error: Cannot find log file for sample run, cannot continue."); - } if (!is_loaded) { setProcessingState(NoSample); m_uiForm.load_dataBtn->setText("Load Data"); return false; } - if( m_uiForm.inst_opt->currentText() == "SANS2D" && can_logs.isEmpty() ) - { - if ( ! m_uiForm.scatCan->isEmpty() ) - { - can_logs = sample_logs; - showInformationBox("Warning: Cannot find log file for can run, using sample values."); - } - } // Sort out the log information - setGeometryDetails(sample_logs, can_logs); + setGeometryDetails(); Mantid::API::Workspace_sptr baseWS = Mantid::API::AnalysisDataService::Instance().retrieve(m_experWksp.toStdString()); @@ -2601,6 +2583,8 @@ void SANSRunWindow::handleDefSaveClick() saveCommand += "'front-detector, rear-detector'"; if ( matrix_workspace->getInstrument()->getName() == "LOQ" ) saveCommand += "'HAB, main-detector-bank'"; + if ( matrix_workspace->getInstrument()->getName() == "LARMOR") + saveCommand += "'" + m_uiForm.detbank_sel->currentText()+"'"; /* From v2, SaveCanSAS1D is able to save the Transmission workspaces related to the reduced data. The name of workspaces of the Transmission are available at the @@ -3098,10 +3082,9 @@ bool SANSRunWindow::assignMonitorRun(MantidWidgets::MWRunFiles & trans, MantidWi * Load a scatter sample file or can run via Python objects using the passed Python command * @param[in] runFile name of file to load * @param[in] assignFn the Python command to run - * @param[out] logs information loaded from the file * @return true if there were no Python errors, false otherwise */ -bool SANSRunWindow::assignDetBankRun(MantidWidgets::MWRunFiles & runFile, const QString & assignFn, QString & logs) +bool SANSRunWindow::assignDetBankRun(MantidWidgets::MWRunFiles & runFile, const QString & assignFn) { //need something to place between names printed by Python that won't be intepreted as the names or removed as white space const static QString PYTHON_SEP("C++assignDetBankRunC++"); @@ -3125,7 +3108,9 @@ bool SANSRunWindow::assignDetBankRun(MantidWidgets::MWRunFiles & runFile, const .arg( m_uiForm.rear_beam_y->text()) .arg( m_uiForm.front_beam_x->text()) .arg( m_uiForm.front_beam_y->text()); - run_info += "SCATTER_SAMPLE, logvalues = " + assignCom+";print '"+PYTHON_SEP+"',SCATTER_SAMPLE,'"+PYTHON_SEP+"',logvalues"; + run_info += "SCATTER_SAMPLE = " + assignCom; + run_info += ";ws_name = SCATTER_SAMPLE if not isinstance(SCATTER_SAMPLE, tuple) else SCATTER_SAMPLE[0]"; + run_info += ";print '"+PYTHON_SEP+"',ws_name"; run_info = runReduceScriptFunction(run_info); if (run_info.startsWith("error", Qt::CaseInsensitive)) { @@ -3134,12 +3119,6 @@ bool SANSRunWindow::assignDetBankRun(MantidWidgets::MWRunFiles & runFile, const //read the informtion returned from Python QString base_workspace = run_info.section(PYTHON_SEP, 1, 1).trimmed(); - logs = run_info.section(PYTHON_SEP, 2); - if( !logs.isEmpty() ) - { - trimPyMarkers(logs); - } - if ( assignFn.contains("can", Qt::CaseInsensitive) ) { m_experCan = base_workspace; diff --git a/Code/Mantid/instrument/Facilities.xml b/Code/Mantid/instrument/Facilities.xml index 51a255047545..8bee7a11e9ec 100644 --- a/Code/Mantid/instrument/Facilities.xml +++ b/Code/Mantid/instrument/Facilities.xml @@ -76,6 +76,7 @@ Small Angle Scattering + diff --git a/Code/Mantid/instrument/LARMOR_Parameters.xml b/Code/Mantid/instrument/LARMOR_Parameters.xml new file mode 100644 index 000000000000..7d8452b19e2c --- /dev/null +++ b/Code/Mantid/instrument/LARMOR_Parameters.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Code/Mantid/scripts/SANS/ISISCommandInterface.py b/Code/Mantid/scripts/SANS/ISISCommandInterface.py index 0e81f008d571..4f106c573fc6 100644 --- a/Code/Mantid/scripts/SANS/ISISCommandInterface.py +++ b/Code/Mantid/scripts/SANS/ISISCommandInterface.py @@ -100,6 +100,21 @@ def LOQ(): except: return False return True + +def LARMOR(): + """ + Initialises the instrument settings for LARMOR + @return True on success + """ + _printMessage('LARMOR()') + try: + instrument = isis_instrument.LARMOR() + + ReductionSingleton().set_instrument(instrument) + config['default.instrument']='LARMOR' + except: + return False + return True def Detector(det_name): """ @@ -208,6 +223,20 @@ def TransWorkspace(sample, can = None): ReductionSingleton().transmission_calculator.calculated_samp = sample ReductionSingleton().transmission_calculator.calculated_can = can +def _return_old_compatibility_assign_methods(ws_name): + """For backward compatibility, AssignCan and AssignSample returns a tuple + with workspace name and the log entry if available. + + In the future, those methods should return just workspace name + """ + logs = "" + if isinstance(ReductionSingleton().instrument, isis_instrument.SANS2D): + try: + logs = ReductionSingleton().instrument.get_detector_log(ws_name) + except: + pass + return ws_name, logs + def AssignCan(can_run, reload = True, period = isis_reduction_steps.LoadRun.UNSET_PERIOD): """ The can is a scattering run under the same conditions as the experimental run but the @@ -225,18 +254,10 @@ def AssignCan(can_run, reload = True, period = isis_reduction_steps.LoadRun.UNSE mes += ', ' + str(period) mes += ')' _printMessage(mes) - - if (not can_run) or (isinstance(can_run,str) and can_run.startswith('.')): - ReductionSingleton().background_subtracter = None - return '', None - - ReductionSingleton().background_subtracter = \ - isis_reduction_steps.CanSubtraction( - can_run, reload=reload, period=period) - #ideally this code should live in a separate load can object - logs = ReductionSingleton().background_subtracter.assign_can( - ReductionSingleton()) - return ReductionSingleton().background_subtracter.workspace.wksp_name, logs + + ReductionSingleton().set_can(can_run, reload, period) + return _return_old_compatibility_assign_methods( + ReductionSingleton().get_can().wksp_name) def TransmissionSample(sample, direct, reload = True, period_t = -1, period_d = -1): """ @@ -285,8 +306,7 @@ def AssignSample(sample_run, reload = True, period = isis_reduction_steps.LoadRu global LAST_SAMPLE LAST_SAMPLE = ReductionSingleton().get_sample().wksp_name - return ReductionSingleton().get_sample().wksp_name, \ - ReductionSingleton().get_sample().log + return _return_old_compatibility_assign_methods(LAST_SAMPLE) def SetCentre(xcoord, ycoord, bank = 'rear'): """ @@ -311,13 +331,18 @@ def GetMismatchedDetList(): return ReductionSingleton().instrument.get_marked_dets() def _setUpPeriod(i): + # it first get the reference to the loaders, then it calls the AssignSample + # (which get rid of the reducer objects (see clean_loaded_data()) + # but because we still get the reference, we can use it to query the data file and method. + # ideally, we should not use this _setUpPeriod in the future. + trans_samp = ReductionSingleton().samp_trans_load - can = ReductionSingleton().background_subtracter + can = ReductionSingleton().get_can() trans_can = ReductionSingleton().can_trans_load new_sample_workspaces = AssignSample(ReductionSingleton().get_sample().loader._data_file, period=i)[0] if can: #replace one thing that gets overwritten - AssignCan(can.workspace._data_file, True, period=can.workspace.getCorrospondingPeriod(i, ReductionSingleton())) + AssignCan(can.loader._data_file, True, period=can.loader.getCorrospondingPeriod(i, ReductionSingleton())) if trans_samp: trans = trans_samp.trans direct = trans_samp.direct @@ -426,8 +451,8 @@ def WavRangeReduction(wav_start=None, wav_end=None, full_trans_wav=None, name_su ReductionSingleton().instrument.setDetector('front') ReductionSingleton()._sample_run.reload(ReductionSingleton()) #reassign can - if ReductionSingleton().background_subtracter: - ReductionSingleton().background_subtracter.assign_can(ReductionSingleton()) + if ReductionSingleton().get_can(): + ReductionSingleton().get_can().reload(ReductionSingleton()) if ReductionSingleton().samp_trans_load: #refresh Transmission ReductionSingleton().samp_trans_load.execute(ReductionSingleton(), None) diff --git a/Code/Mantid/scripts/SANS/SANSBatchMode.py b/Code/Mantid/scripts/SANS/SANSBatchMode.py index 9cca1c9cf6f5..cd0962887faa 100644 --- a/Code/Mantid/scripts/SANS/SANSBatchMode.py +++ b/Code/Mantid/scripts/SANS/SANSBatchMode.py @@ -127,6 +127,11 @@ def BatchReduce(filename, format, plotresults=False, saveAlgs={'SaveRKH':'txt'}, ins_name = ReductionSingleton().instrument.name() # is used for SaveCanSAS1D go give the detectors names detnames = ', '.join(ReductionSingleton().instrument.listDetectors()) + + # LARMOR has just one detector, but, it defines two because ISISInstrument is defined as two banks! #8395 + if ins_name == 'LARMOR': + detnames = ReductionSingleton().instrument.cur_detector().name() + scale_shift = {'scale':1.0000, 'shift':0.0000} #first copy the user settings in case running the reductionsteps can change it settings = copy.deepcopy(ReductionSingleton().reference()) @@ -289,9 +294,13 @@ def read_run(runs, run_role, format): return run_file, period = parse_run(run_file, format) - run_ws = eval(COMMAND[run_role] + 'run_file, period=period)[0]') + run_ws = eval(COMMAND[run_role] + 'run_file, period=period)') if not run_ws: raise SkipReduction('Cannot load ' + run_role + ' run "' + run_file + '"') + + #AssignCan and AssignSample will change signature for: ws_name = AssignCan + if isinstance(run_ws, tuple): + return run_ws[0] return run_ws def read_trans_runs(runs, sample_or_can, format): diff --git a/Code/Mantid/scripts/SANS/centre_finder.py b/Code/Mantid/scripts/SANS/centre_finder.py index a605568d23f9..e9a3f61ed20e 100644 --- a/Code/Mantid/scripts/SANS/centre_finder.py +++ b/Code/Mantid/scripts/SANS/centre_finder.py @@ -45,7 +45,7 @@ def SeekCentre(self, setup, trial): self._group_into_quadrants(setup, 'centre', trial[0], trial[1], suffix='_tmp') - if setup.background_subtracter: + if setup.get_can(): #reduce the can here setup.reduce_can('centre_can', run_Q=False) @@ -100,8 +100,8 @@ def move(self, setup, x, y): y = -y MoveInstrumentComponent(Workspace=setup.get_sample().wksp_name, ComponentName=self.detector, X=x, Y=y, RelativePosition=True) - if setup.background_subtracter: - MoveInstrumentComponent(Workspace=setup.background_subtracter.workspace.wksp_name, + if setup.get_can(): + MoveInstrumentComponent(Workspace=setup.get_can().wksp_name, ComponentName=self.detector, X=x, Y=y, RelativePosition=True) # Create a workspace with a quadrant value in it diff --git a/Code/Mantid/scripts/SANS/isis_instrument.py b/Code/Mantid/scripts/SANS/isis_instrument.py index ea1c1cfb5693..7d98d0780a0f 100644 --- a/Code/Mantid/scripts/SANS/isis_instrument.py +++ b/Code/Mantid/scripts/SANS/isis_instrument.py @@ -3,6 +3,7 @@ from mantid.simpleapi import * from mantid.api import WorkspaceGroup from mantid.kernel import Logger +import re sanslog = Logger.get("SANS") import sys @@ -555,7 +556,7 @@ def reset_TOFs(self, monitor=None): self._back_start = None self._back_end = None - def move_components(self, ws): + def move_all_components(self, ws): """ Move the sample object to the location set in the logs or user settings file @param ws: the workspace containing the sample to move @@ -573,10 +574,38 @@ def move_components(self, ws): MoveInstrumentComponent(Workspace=ws,ComponentName= component, Z = offset, RelativePosition=True) + def move_components(self, ws, beamX, beamY): + """Define how to move the bank to position beamX and beamY must be implemented""" + raise RuntimeError("Not Implemented") + def cur_detector_position(self, ws_name): """Return the position of the center of the detector bank""" raise RuntimeError("Not Implemented") + def on_load_sample(self, ws_name, beamcentre, isSample): + """It will be called just after loading the workspace for sample and can + + It configures the instrument for the specific run of the workspace for handle historical changes in the instrument. + + It centralizes the detector bank to teh beamcentre (tuple of two values) + """ + ws_ref = mtd[str(ws_name)] + try: + run_num = ws_ref.getRun().getLogData('run_number').value + except: + run_num = int(re.findall(r'\d+',run_string)[-1]) + + if isSample: + self.set_up_for_run(run_num) + + # centralize the bank to the centre + self.move_components(ws_name, beamcentre[0], beamcentre[1]) + + def load_transmission_inst(self, ws_trans, ws_direct, beamcentre): + """ + Called on loading of transmissions + """ + pass class LOQ(ISISInstrument): @@ -608,7 +637,7 @@ def move_components(self, ws, xbeam, ybeam): @param ybeam: y-position of the beam @return: the locations of (in the new coordinates) beam center, center of detector bank """ - super(LOQ, self).move_components(ws) + self.move_all_components(ws) xshift = (317.5/1000.) - xbeam yshift = (317.5/1000.) - ybeam @@ -640,19 +669,14 @@ def set_up_for_run(self, base_runno): second.set_orien('Horizontal') second.place_after(first) - def load_transmission_inst(self, workspace): + def load_transmission_inst(self, ws_trans, ws_direct, beamcentre): """ Loads information about the setup used for LOQ transmission runs """ trans_definition_file = config.getString('instrumentDefinition.directory') trans_definition_file += '/'+self._NAME+'_trans_Definition.xml' - LoadInstrument(Workspace=workspace,Filename= trans_definition_file, RewriteSpectraMap=False) - - def check_can_logs(self): - """ - This function does nothing for LOQ - """ - pass + LoadInstrument(Workspace=ws_trans,Filename= trans_definition_file, RewriteSpectraMap=False) + LoadInstrument(Workspace=ws_direct, Filename = trans_definition_file, RewriteSpectraMap=False) def cur_detector_position(self, ws_name): """Return the position of the center of the detector bank""" @@ -720,7 +744,7 @@ def set_up_for_run(self, base_runno): #as spectrum numbers of the first detector have changed we'll move those in the second too second.place_after(first) - def _getDetValues(self, ws_name): + def getDetValues(self, ws_name): """ Retrive the values of Front_Det_Z, Front_Det_X, Front_Det_Rot, Rear_Det_Z and Rear_Det_X from the workspace. If it does not find the value at the run info, it takes as default value the @@ -761,7 +785,7 @@ def move_components(self, ws, xbeam, ybeam): frontDet = self.getDetector('front') rearDet = self.getDetector('rear') - FRONT_DET_Z, FRONT_DET_X, FRONT_DET_ROT, REAR_DET_Z, REAR_DET_X = self._getDetValues(ws) + FRONT_DET_Z, FRONT_DET_X, FRONT_DET_ROT, REAR_DET_Z, REAR_DET_X = self.getDetValues(ws) # Deal with front detector # 9/1/2 this all dates to Richard Heenan & Russell Taylor's original python development for SANS2d @@ -801,7 +825,7 @@ def move_components(self, ws, xbeam, ybeam): MoveInstrumentComponent(Workspace=ws,ComponentName= rearDet.name(), X = xshift, Y = yshift, Z = zshift, RelativePosition="1") - super(SANS2D, self).move_components(ws) + self.move_all_components(ws) #this implements the TRANS/TRANSPEC=4/SHIFT=... line, this overrides any other monitor move if self.monitor_4_offset: @@ -962,11 +986,87 @@ def append_marked(self, detNames): def get_marked_dets(self): return self._marked_dets - def load_transmission_inst(self, workspace): + def load_transmission_inst(self, ws_trans, ws_direct, beamcentre): """ - Not required for SANS2D + SANS2D requires the centralize the detectors of the transmission + as well as the sample and can. """ - pass + self.move_components(ws_trans, beamcentre[0], beamcentre[1]) + if ws_trans != ws_direct: + self.move_components(ws_direct, beamcentre[0], beamcentre[1]) + + + def cur_detector_position(self, ws_name): + """Return the position of the center of the detector bank""" + ws = mtd[ws_name] + pos = ws.getInstrument().getComponentByName(self.cur_detector().name()).getPos() + + return [-pos.getX(), -pos.getY()] + + def on_load_sample(self, ws_name, beamcentre, isSample): + """For SANS2D in addition of the operations defines in on_load_sample of ISISInstrument + it has to deal with the log, which defines some offsets for the movement of the + detector bank. + """ + ws_ref = mtd[str(ws_name)] + try: + log = self.get_detector_log(ws_ref) + if log == "": + raise "Invalid log" + except: + if isSample: + raise RuntimeError('Sample logs cannot be loaded, cannot continue') + else: + logger.warning("Can logs could not be loaded, using sample values.") + + + if isSample: + self.apply_detector_logs(log) + else: + self.check_can_logs(log) + + + ISISInstrument.on_load_sample(self, ws_name, beamcentre, isSample) + + +class LARMOR(ISISInstrument): + _NAME = 'LARMOR' + WAV_RANGE_MIN = 2.2 + WAV_RANGE_MAX = 10.0 + def __init__(self): + super(LARMOR,self).__init__('LARMOR_Definition.xml') + self.monitor_names = dict() + + for i in range(1,6): + self.monitor_names[i] = 'monitor'+str(i) + + def set_up_for_run(self, base_runno): + """ + Needs to run whenever a sample is loaded + """ + first = self.DETECTORS['low-angle'] + second = self.DETECTORS['high-angle'] + + first.set_orien('Horizontal') + first.set_first_spec_num(10) + second.set_orien('Horizontal') + second.place_after(first) + + def move_components(self, ws, xbeam, ybeam): + self.move_all_components(ws) + + detBanch = self.getDetector('rear') + + xshift = -xbeam + yshift = -ybeam + #zshift = ( detBanch.z_corr)/1000. + #zshift -= self.REAR_DET_DEFAULT_SD_M + zshift = 0 + sanslog.notice("Setup move " + str(xshift*1000) + " " + str(yshift*1000) + " " + str(zshift*1000)) + MoveInstrumentComponent(ws, ComponentName=detBanch.name(), X=xshift, + Y=yshift, Z=zshift) + # beam centre, translation + return [0.0, 0.0], [-xbeam, -ybeam] def cur_detector_position(self, ws_name): """Return the position of the center of the detector bank""" diff --git a/Code/Mantid/scripts/SANS/isis_reducer.py b/Code/Mantid/scripts/SANS/isis_reducer.py index cc9dc707ab2e..3541a5ee16ae 100644 --- a/Code/Mantid/scripts/SANS/isis_reducer.py +++ b/Code/Mantid/scripts/SANS/isis_reducer.py @@ -30,11 +30,10 @@ def _deepcopy_method(x, memo): current_settings = None class Sample(object): + ISSAMPLE = True def __init__(self): #will contain a LoadSample() object that converts the run number into a file name and loads that file self.loader = None - #the logs from the run file - self.log = None #geometry that comes from the run and can be overridden by user settings self.geometry = sans_reduction_steps.GetSampleGeom() #record options for the set_run @@ -64,14 +63,21 @@ def set_run(self, run, reload, period, reducer): self.period_option = period self.loader = isis_reduction_steps.LoadSample(run, reload, period) - self.log = self.loader.execute(reducer, None) - - self.geometry.execute(None, self.get_wksp_name()) + self.loader.execute(reducer, self.ISSAMPLE) + if self.ISSAMPLE: + self.geometry.execute(None, self.get_wksp_name()) def get_wksp_name(self): return self.loader.wksp_name + def get_periods_in_file(self): + return self.loader.periods_in_file + wksp_name = property(get_wksp_name, None, None, None) + periods_in_file = property(get_periods_in_file, None, None, None) + +class Can(Sample): + ISSAMPLE = False class ISISReducer(SANSReducer): """ @@ -112,7 +118,7 @@ def _to_steps(self): proc_wav.append(self._corr_and_scale) proc_wav.append(self.geometry_correcter) - self._can = [self.background_subtracter] + self._can = [self._background_subtracter] # self._tidy = [self._zero_error_flags] self._tidy = [self._rem_nans] @@ -133,8 +139,6 @@ def _init_steps(self): #except self.prep_normalize all the steps below are used by the reducer self.crop_detector = isis_reduction_steps.CropDetBank(crop_sample=True) - self.samp_trans_load = None - self.can_trans_load = None self.mask =self._mask= isis_reduction_steps.Mask_ISIS() self.to_wavelen = isis_reduction_steps.UnitsConvert('Wavelength') self.norm_mon = isis_reduction_steps.NormalizeToMonitor() @@ -151,18 +155,22 @@ def _init_steps(self): self.to_Q = isis_reduction_steps.ConvertToQISIS( self.prep_normalize) - self.background_subtracter = None - self.geometry_correcter = sans_reduction_steps.SampleGeomCor( - self._sample_run.geometry) + self._background_subtracter = isis_reduction_steps.CanSubtraction() + self.geometry_correcter = sans_reduction_steps.SampleGeomCor() # self._zero_error_flags=isis_reduction_steps.ReplaceErrors() self._rem_nans = sans_reduction_steps.StripEndNans() self.set_Q_output_type(self.to_Q.output_type) + def _clean_loaded_data(self): + self._sample_run = Sample() + self._can_run = Can() + self.samp_trans_load = None + self.can_trans_load = None + def __init__(self): SANSReducer.__init__(self) self._dark_current_subtracter_class = None - self._sample_run = Sample() self.output_wksp = None self.full_trans_wav = False self._monitor_set = False @@ -173,6 +181,7 @@ def __init__(self): #all workspaces created by this reducer self._workspace = [self._temporys, self._outputs] + self._clean_loaded_data() self._init_steps() #process the background (can) run instead of the sample @@ -191,6 +200,7 @@ def __init__(self): # register the value of transmission can self.__transmission_can = "" + def set_sample(self, run, reload, period): """ Assigns and load the run that this reduction chain will analysis @@ -198,7 +208,12 @@ def set_sample(self, run, reload, period): @param reload: if this sample should be reloaded before the first reduction @param period: the period within the sample to be analysed """ + # ensure that when you set sample, you start with no can, transmission previously used. + self._clean_loaded_data() self._sample_run.set_run(run, reload, period, self) + + def set_can(self, run, reload, period): + self._can_run.set_run(run, reload, period, self) def get_sample(self): """ @@ -208,7 +223,29 @@ def get_sample(self): if not self._process_can: return self._sample_run else: - return self.background_subtracter + return self.get_can() + + def get_transmissions(self): + """ Get the transmission and direct workspace if they were given + for the can and for the sample""" + if self._process_can: + loader = self.can_trans_load + else: + loader = self.samp_trans_load + if loader: + return loader.trans.wksp_name, loader.direct.wksp_name + else: + return "", "" + + def get_can(self): + if self._can_run.loader and self._can_run.wksp_name: + return self._can_run + else: + return None + + # for compatibility reason, previously, background_subtracter was used to + # query if the can was provided and for the can reduction. + background_subtracter = property(get_can, None, None, None) def get_out_ws_name(self, show_period=True): """ @@ -293,24 +330,27 @@ def reduce_can(self, new_wksp=None, run_Q=True): @param new_wksp: the name of the workspace that will store the result @param run_Q: set to false to stop just before converting to Q, default is convert to Q """ - # copy all the run settings from the sample, these settings can come from the user file, Python scripting or the GUI - new_reducer = self.deep_copy() - - new_reducer._process_can = True - #set the workspace that we've been setting up as the one to be processed - new_reducer.output_wksp = new_wksp + # copy settings + sample_wksp_name = self.output_wksp + sample_trans_name = self.transmission_calculator.output_wksp + # configure can + self._process_can = True + # set the workspace that we've been setting up as the one to be processed + self.output_wksp = new_wksp - can_steps = new_reducer._conv_Q + can_steps = self._conv_Q if not run_Q: #the last step in the list must be ConvertToQ or this wont work can_steps = can_steps[0:len(can_steps)-1] #the reducer is completely setup, run it - new_reducer._reduce(init=False, post=False, steps=can_steps) - ## after the reduction of can, the new_reducer will be discarded - ## so, we will keep the information of the transmission and save it - ## inside the __transmission_can attribute. - self.__transmission_can = new_reducer.transmission_calculator.output_wksp + self._reduce(init=False, post=False, steps=can_steps) + + # restore settings + self._process_can = False + self.output_wksp = sample_wksp_name + self.__transmission_can = self.transmission_calculator.output_wksp + self.__transmission_sample = sample_trans_name def run_from_raw(self): """ @@ -338,8 +378,11 @@ def post_process(self): AddSampleLog(Workspace=self.output_wksp,LogName="UserFile", LogText=user_file) # get the value of __transmission_sample from the transmission_calculator if it has - if self.transmission_calculator and self.transmission_calculator.output_wksp: + if (not self.get_can()) and self.transmission_calculator.output_wksp: + # it updates only if there was not can, because, when there is can, the __transmission_sample + # is already correct and transmission_calculator.output_wksp points to the can transmission self.__transmission_sample = self.transmission_calculator.output_wksp + # The reducer itself sometimes will be reset, and the users of the singleton # not always will have access to its settings. So, we will add the transmission workspaces # to the SampleLog, to be connected to the workspace, and be available outside. These values @@ -349,6 +392,10 @@ def post_process(self): if self.__transmission_can: AddSampleLog(Workspace=self.output_wksp,LogName= "TransmissionCan", LogText=self.__transmission_can + str('_unfitted')) + # clean these values for subsequent executions + self.__transmission_sample = "" + self.__transmission_can = "" + for role in self._temporys.keys(): try: DeleteWorkspace(Workspace=self._temporys[role]) @@ -375,18 +422,14 @@ def set_trans_fit(self, lambda_min=None, lambda_max=None, fit_method="Log", sele self.transmission_calculator.set_trans_fit(fit_method, lambda_min, lambda_max, override=True, selector=selector) def set_trans_sample(self, sample, direct, reload=True, period_t = -1, period_d = -1): - if not issubclass(self.samp_trans_load.__class__, sans_reduction_steps.BaseTransmission): - self.samp_trans_load = isis_reduction_steps.LoadTransmissions(reload=reload) + self.samp_trans_load = isis_reduction_steps.LoadTransmissions(reload=reload) self.samp_trans_load.set_trans(sample, period_t) self.samp_trans_load.set_direc(direct, period_d) - self.transmission_calculator.samp_loader = self.samp_trans_load def set_trans_can(self, can, direct, reload = True, period_t = -1, period_d = -1): - if not issubclass(self.can_trans_load.__class__, sans_reduction_steps.BaseTransmission): - self.can_trans_load = isis_reduction_steps.LoadTransmissions(is_can=True, reload=reload) + self.can_trans_load = isis_reduction_steps.LoadTransmissions(is_can=True, reload=reload) self.can_trans_load.set_trans(can, period_t) self.can_trans_load.set_direc(direct, period_d) - self.transmission_calculator.can_loader = self.can_trans_load def set_monitor_spectrum(self, specNum, interp=False, override=True): if override: diff --git a/Code/Mantid/scripts/SANS/isis_reduction_steps.py b/Code/Mantid/scripts/SANS/isis_reduction_steps.py index b6d115fd77f7..b7777f075004 100644 --- a/Code/Mantid/scripts/SANS/isis_reduction_steps.py +++ b/Code/Mantid/scripts/SANS/isis_reduction_steps.py @@ -69,7 +69,7 @@ def _load(self, inst = None, is_can=False, extra_options=dict()): @param inst: a reference to the current instrument @param iscan: set this to True for can runs @param extra_options: arguments to pass on to the Load Algorithm. - @return: log values, number of periods in the workspace + @return: number of periods in the workspace """ if self._period > 1: workspace = self._get_workspace_name(self._period) @@ -95,8 +95,6 @@ def _load(self, inst = None, is_can=False, extra_options=dict()): OutputWorkspace=workspace, **extra_options) - SANS2D_log_file = mtd[workspace] - numPeriods = self._find_workspace_num_periods(workspace) #deal with the difficult situation of not reporting the period of single period files if numPeriods > 1: @@ -106,22 +104,9 @@ def _load(self, inst = None, is_can=False, extra_options=dict()): RenameWorkspace(InputWorkspace=workspace,OutputWorkspace= period_definitely_inc) workspace = period_definitely_inc - log = self._extract_log_info(SANS2D_log_file, inst) - self.wksp_name = workspace - return numPeriods, log + self.periods_in_file = numPeriods - def _extract_log_info(self,wksp_pointer, inst): - log = None - if (not inst is None) and inst.name() == 'SANS2D': - #this instrument has logs to be loaded - try: - log = inst.get_detector_log(wksp_pointer) - except: - #transmission workspaces, don't have logs - if not self._is_trans: - raise - return log def _get_workspace_name(self, entry_num=None): """ @@ -163,7 +148,7 @@ def _loadFromWorkspace(self, reducer): self._data_file = _file_path if self._reload: # give to _assignHelper the responsibility of loading this data. - return False, None + return False #test if the sample details are already loaded: if not ws_pointer.sample().getGeometryFlag(): @@ -182,15 +167,15 @@ def _loadFromWorkspace(self, reducer): if isEventWorkspace(ws_pointer): ws_pointer = fromEvent2Histogram(ws_pointer) - return True, self._extract_log_info(ws_pointer, reducer.instrument) + return True # Helper function def _assignHelper(self, reducer): if isinstance(self._data_file, Workspace): - loaded_flag, logs = self._loadFromWorkspace(reducer) + loaded_flag= self._loadFromWorkspace(reducer) if loaded_flag: - return logs + return if self._data_file == '' or self._data_file.startswith('.'): raise RuntimeError('Sample needs to be assigned as run_number.file_type') @@ -215,13 +200,13 @@ def _assignHelper(self, reducer): try: # the spectrum_limits is not the default only for transmission data - self.periods_in_file, logs = self._load(reducer.instrument, extra_options=spectrum_limits) + self._load(reducer.instrument, extra_options=spectrum_limits) except RuntimeError, details: sanslog.warning(str(details)) self.wksp_name = '' - return '', -1 + return - return logs + return def _leaveSinglePeriod(self, workspace, period): groupW = mtd[workspace] @@ -254,12 +239,6 @@ def _leaveSinglePeriod(self, workspace, period): DeleteWorkspace(groupW.getName()) return newName - def _clearPrevious(self, inWS, others = []): - if inWS != None: - if inWs in mtd and (not inWS in others): - DeleteWorkspace(inWs) - - def _extract_run_details(self, run_string): """ Takes a run number and file type and generates the filename, workspace name and log name @@ -331,7 +310,7 @@ def getCorrospondingPeriod(self, sample_period, reducer): raise RuntimeError('There is a mismatch in the number of periods (entries) in the file between the sample and another run') -class LoadTransmissions(ReductionStep): +class LoadTransmissions(): """ Loads the file used to apply the transmission correction to the sample or can @@ -345,7 +324,6 @@ def __init__(self, is_can=False, reload=True): @param is_can: if this is to correct the can (default false i.e. it's for the sample) @param reload: setting this to false will mean the workspaces aren't reloaded if they already exist (default True i.e. reload) """ - super(LoadTransmissions, self).__init__() self.trans = None self.direct = None self._reload = reload @@ -380,14 +358,7 @@ def execute(self, reducer, workspace): raise RuntimeError('Transmission run set without direct run error') #transmission workspaces sometimes have monitor locations, depending on the instrument, load these locations - reducer.instrument.load_transmission_inst(self.trans.wksp_name) - reducer.instrument.load_transmission_inst(self.direct.wksp_name) - - if reducer.instrument.name() == 'SANS2D': - beamcoords = reducer.get_beam_center() - reducer.instrument.move_components(self.trans.wksp_name, beamcoords[0], beamcoords[1]) - if self.trans.wksp_name != self.direct.wksp_name: - reducer.instrument.move_components(self.direct.wksp_name, beamcoords[0], beamcoords[1]) + reducer.instrument.load_transmission_inst(self.trans.wksp_name, self.direct.wksp_name, reducer.get_beam_center()) return self.trans.wksp_name, self.direct.wksp_name @@ -396,49 +367,17 @@ class CanSubtraction(ReductionStep): Apply the same corrections to the can that were applied to the sample and then subtracts this can from the sample. """ - def __init__(self, can_run, reload = True, period = -1): - """ - @param can_run: the run number followed by dot and the extension - @param reload: if set to true (default) the workspace is replaced if it already exists - @param period: for multiple entry workspaces this is the period number - """ + def __init__(self): super(CanSubtraction, self).__init__() - #contains the workspace with the background (can) data - self.workspace = LoadRun(can_run, reload=reload, entry=period) - - def assign_can(self, reducer): - """ - Loads the can workspace into Mantid and reads any log file - @param reducer: the reduction chain - @return: the logs object - """ - if not reducer.user_settings.executed: - raise RuntimeError('User settings must be loaded before the can can be loaded, run UserFile() first') - - logs = self.workspace._assignHelper(reducer) - - if self.workspace.wksp_name == '': - sanslog.warning('Unable to load SANS can run, cannot continue.') - return '()' - - if logs: - reducer.instrument.check_can_logs(logs) - else: - logs = "" - if reducer.instrument.name() == 'SANS2D': - _issueWarning("Can logs could not be loaded, using sample values.") - return "()" - - beamcoords = reducer.get_beam_center() - reducer.instrument.move_components(self.wksp_name, beamcoords[0], beamcoords[1]) - - return logs def execute(self, reducer, workspace): """ Apply same corrections as for sample workspace then subtract from data """ - #remain the sample workspace, its name will be restored to the original once the subtraction has been done + if reducer.get_can() is None: + return + + #rename the sample workspace, its name will be restored to the original once the subtraction has been done tmp_smp = workspace+"_sam_tmp" RenameWorkspace(InputWorkspace=workspace,OutputWorkspace= tmp_smp) @@ -972,14 +911,13 @@ def __str__(self): ' front time mask: ', str(self.time_mask_f)+'\n' -class LoadSample(LoadRun, ReductionStep): +class LoadSample(LoadRun): """ Handles loading the sample run, this is the main experimental run with data about the sample of interest """ def __init__(self, sample=None, reload=True, entry=-1): LoadRun.__init__(self, sample, reload=reload, entry=entry) - ReductionStep.__init__(self) self._scatter_sample = None self._SAMPLE_RUN = None @@ -987,14 +925,11 @@ def __init__(self, sample=None, reload=True, entry=-1): #is set to the entry (period) number in the sample to be run self.entries = [] - def execute(self, reducer, workspace): + def execute(self, reducer, isSample): if not reducer.user_settings.executed: raise RuntimeError('User settings must be loaded before the sample can be assigned, run UserFile() first') - # Code from AssignSample - self._clearPrevious(self._scatter_sample) - - logs = self._assignHelper(reducer) + self._assignHelper(reducer) if self._period != self.UNSET_PERIOD: self.entries = [self._period] else: @@ -1003,33 +938,7 @@ def execute(self, reducer, workspace): if self.wksp_name == '': raise RuntimeError('Unable to load SANS sample run, cannot continue.') - p_run_ws = mtd[self.wksp_name] - - if isinstance(p_run_ws, WorkspaceGroup): - p_run_ws = p_run_ws[0] - - try: - run_num = p_run_ws.getRun().getLogData('run_number').value - except RuntimeError: - # if the run number is not stored in the workspace, try to take it from the filename - run_num = os.path.basename(self._data_file).split('.')[0].split('-')[0].split('0')[-1] - try: - dummy = int(run_num) - except ValueError: - logger.notice('Could not extract run number from file name ' + self._data_file) - - reducer.instrument.set_up_for_run(run_num) - - if reducer.instrument.name() == 'SANS2D': - if logs == None: - DeleteWorkspace(self.wksp_name) - raise RuntimeError('Sample logs cannot be loaded, cannot continue') - reducer.instrument.apply_detector_logs(logs) - - beamcoords = reducer.get_beam_center() - reducer.instrument.move_components(self.wksp_name, beamcoords[0], beamcoords[1]) - - return logs + reducer.instrument.on_load_sample(self.wksp_name, reducer.get_beam_center(), isSample) def get_group_name(self): return self._get_workspace_name(self._period) @@ -1111,7 +1020,7 @@ def execute(self, reducer, workspace): r_alg = 'Rebin' reducer.to_wavelen.execute(reducer, self.output_wksp, bin_alg=r_alg) -class TransmissionCalc(sans_reduction_steps.BaseTransmission): +class TransmissionCalc(ReductionStep): """ Calculates the proportion of neutrons that are transmitted through the sample as a function of wavelength. The results are stored as a workspace @@ -1144,10 +1053,6 @@ def __init__(self, loader=None): self.fit_settings = dict() for prop in self.fit_props: self.fit_settings['both::'+prop] = None - # An optional LoadTransmissions object that contains the names of the transmission and direct workspaces for the sample - self.samp_loader = None - # An optional LoadTransmissions objects for the can's transmission and direct workspaces - self.can_loader = None # this contains the spectrum number of the monitor that comes after the sample from which the transmission calculation is done self._trans_spec = None # use InterpolatingRebin @@ -1162,19 +1067,6 @@ def __init__(self, loader=None): self.loq_removePromptPeakMax = 20500.0 - def _loader(self, reducer): - """ - Returns the transmission loader objects for either the sample or the can depending - on the reduction object passed - @param reducer: the reduction chain of interest - @return: information on the transmission workspaces if these were loaded - """ - if reducer.is_can(): - return self.can_loader - else: - return self.samp_loader - - def set_trans_fit(self, fit_method, min_=None, max_=None, override=True, selector='both'): """ Set how the transmission fraction fit is calculated, the range of wavelengths @@ -1321,11 +1213,7 @@ def _get_run_wksps(self, reducer): of the transmission @return: post_sample pre_sample workspace names """ - loader = self._loader(reducer) - if (not loader) or (not loader.trans.wksp_name): - return '', '' - else: - return loader.trans.wksp_name, loader.direct.wksp_name + return reducer.get_transmissions() def calculate(self, reducer): LAMBDAMIN = 'lambda_min' diff --git a/Code/Mantid/scripts/reduction/instruments/sans/sans_reduction_steps.py b/Code/Mantid/scripts/reduction/instruments/sans/sans_reduction_steps.py index c24203a62666..12ebbe390eed 100644 --- a/Code/Mantid/scripts/reduction/instruments/sans/sans_reduction_steps.py +++ b/Code/Mantid/scripts/reduction/instruments/sans/sans_reduction_steps.py @@ -750,46 +750,45 @@ class SampleGeomCor(ReductionStep): ORNL only divides by thickness, in the absolute scaling step """ - def __init__(self, geometry): - """ - Takes a reference to the sample geometry - @param geometry: A GetSampleGeom object to load the sample dimensions from - @raise TypeError: if an object of the wrong type is passed to it - """ - super(SampleGeomCor, self).__init__() - - if issubclass(geometry.__class__, GetSampleGeom): - self.geo = geometry - else: - raise TypeError, 'Sample geometry correction requires a GetSampleGeom object' + def __init__(self): + self.volume = 1.0 - def execute(self, reducer, workspace): - """ - Divide the counts by the volume of the sample - """ + def calculate_volume(self, reducer): + geo = reducer.get_sample().geometry + assert( issubclass(geo.__class__, GetSampleGeom)) try: - if self.geo.shape == 'cylinder-axis-up': + if geo.shape == 'cylinder-axis-up': # Volume = circle area * height # Factor of four comes from radius = width/2 - volume = self.geo.height*math.pi - volume *= math.pow(self.geo.width,2)/4.0 - elif self.geo.shape == 'cuboid': + volume = geo.height*math.pi + volume *= math.pow(geo.width,2)/4.0 + elif geo.shape == 'cuboid': # Flat plate sample - volume = self.geo.width - volume *= self.geo.height*self.geo.thickness - elif self.geo.shape == 'cylinder-axis-along': + volume = geo.width + volume *= geo.height*geo.thickness + elif geo.shape == 'cylinder-axis-along': # Factor of four comes from radius = width/2 # Disc - where height is not used - volume = self.geo.thickness*math.pi - volume *= math.pow(self.geo.width, 2)/4.0 + volume = geo.thickness*math.pi + volume *= math.pow(geo.width, 2)/4.0 else: - raise NotImplemented('Shape "'+self.geo.shape+'" is not in the list of supported shapes') + raise NotImplemented('Shape "'+geo.shape+'" is not in the list of supported shapes') except TypeError: - raise TypeError('Error calculating sample volume with width='+str(self._width) + ' height='+str(self._height) + 'and thickness='+str(self._thickness)) - - ws = mtd[workspace] - ws /= volume + raise TypeError('Error calculating sample volume with width='+str(geo.width) + ' height='+str(geo.height) + 'and thickness='+str(geo.thickness)) + + return volume + + def execute(self, reducer, workspace): + """ + Divide the counts by the volume of the sample + """ + if not reducer.is_can(): + # it calculates the volume for the sample and may or not apply to the can as well. + self.volume = self.calculate_volume(reducer) + + ws = mtd[str(workspace)] + ws /= self.volume class StripEndZeros(ReductionStep): # ISIS only