forked from npshub/mantid
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ISISCommandInterface.py
2068 lines (1694 loc) · 78.8 KB
/
ISISCommandInterface.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source,
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
# SPDX - License - Identifier: GPL - 3.0 +
# pylint: disable=too-many-lines, invalid-name, redefined-builtin, protected-access, too-many-arguments
"""
Enables the SANS commands (listed at http://www.mantidproject.org/SANS) to
be run
"""
import isis_instrument
from reducer_singleton import ReductionSingleton
from mantid.kernel import Logger
import isis_reduction_steps
import isis_reducer
from centre_finder import *
from mantid.simpleapi import *
from mantid.api import WorkspaceGroup, ExperimentInfo
import copy
from SANSadd2 import *
import SANSUtility as su
from SANSUtility import deprecated
import SANSUserFileParser as UserFileParser
import warnings
warnings.simplefilter("default", category=DeprecationWarning)
warnings.warn("This ISIS Command Interface is deprecated.\n"
"Please change 'import ISISCommandInterface' or 'from ISISCommandInterface'"
"to use 'sans.command_interface.ISISCommandInterface' instead.", DeprecationWarning,
stacklevel=2)
warnings.simplefilter("ignore", category=DeprecationWarning)
sanslog = Logger("SANS")
try:
from qtpy.QtWidgets import qApp
def appwidgets():
return qApp.allWidgets()
except (ImportError, RuntimeError):
def appwidgets():
return []
_VERBOSE_ = False
LAST_SAMPLE = None
def SetVerboseMode(state):
# TODO: this needs to be on the reducer
# _VERBOSE_ = state # FIXME this does nothing
pass
# Print a message and log it if the
def _printMessage(msg, log=True, no_console=False):
if log and _VERBOSE_:
sanslog.notice(msg)
if not no_console:
print(msg)
def issueWarning(msg):
"""
Issues a Mantid message
@param msg: message to be issued
"""
isis_reduction_steps._issueWarning(msg)
def _refresh_singleton():
ReductionSingleton.clean(isis_reducer.ISISReducer)
ReductionSingleton().remove_settings()
def Clean():
"""
An exposed command to allow cleaning of the reducer, and any related
settings.
"""
_refresh_singleton()
def SANS2D(idf_path=None):
"""
Initialises the instrument settings for SANS2D
@param idf_path :: optionally specify the path to the SANS2D IDF to use.
Uses default if none specified.
@return True on success
"""
_printMessage('SANS2D()')
try:
instrument = isis_instrument.SANS2D(idf_path)
if instrument is None:
raise RuntimeError("The provided idf path seems to have been incorrect")
ReductionSingleton().set_instrument(instrument)
config['default.instrument'] = 'SANS2D'
except (Exception, Warning):
return False
return True
def SANS2DTUBES():
"""
Quick, temporary workaround for the IDF problem we're fixing in #9367.
Simply pass the correct IDF to SANS2D().
"""
return SANS2D("SANS2D_Definition_Tubes.xml")
def LOQ(idf_path='LOQ_Definition_20020226-.xml'):
"""
Initialises the instrument settings for LOQ
@return True on success
"""
_printMessage('LOQ()')
try:
instrument = isis_instrument.LOQ(idf_path)
if instrument is None:
raise RuntimeError("The provided idf path seems to have been incorrect")
ReductionSingleton().set_instrument(instrument)
config['default.instrument'] = 'LOQ'
except(Exception, Warning):
return False
return True
def LARMOR(idf_path = None):
"""
Initialises the instrument settings for LARMOR
@param idf_path :: optionally specify the path to the LARMOR IDF to use.
Uses default if none specified.
@return True on success
"""
_printMessage('LARMOR()')
try:
instrument = isis_instrument.LARMOR(idf_path)
if instrument is None:
raise RuntimeError("The provided idf path seems to have been incorrect")
ReductionSingleton().set_instrument(instrument)
config['default.instrument'] = 'LARMOR'
except (Exception, Warning):
return False
return True
def Detector(det_name):
"""
Sets the detector bank to use for the reduction e.g. 'front-detector'. The
main detector is assumed if this line is not given
@param det_name: the detector's name
"""
_printMessage('Detector("' + det_name + '")')
ReductionSingleton().instrument.setDetector(det_name)
def Mask(details):
"""
Specify regions of the detector to mask using the same syntax
as used in the user file
@param details: a string that specifies masking as it would appear in a mask file
"""
_printMessage('Mask("' + details + '")')
ReductionSingleton().mask.parse_instruction(ReductionSingleton().instrument.name(), details)
def MaskFile(file_name):
"""
Loads the settings file. The settings are loaded as soon as this line is encountered
and are overridden by other Python commands
@param file_name: the settings file
"""
_printMessage('#Opening "' + file_name + '"')
# ensure that no slice string is kept from previous executions.
ReductionSingleton().setSlicesLimits("")
ReductionSingleton().user_settings = isis_reduction_steps.UserFile(
file_name)
status = ReductionSingleton().user_settings.execute(
ReductionSingleton(), None)
_printMessage('#Success reading "' + file_name + '"' + ' is ' + str(status))
return status
def SetMonitorSpectrum(specNum, interp=False):
"""
Specifies the spectrum number of the spectrum that will be used to
for monitor normalisation
@param specNum: a spectrum number (1 or greater)
@param interp: when rebinning the wavelength bins to match the main workspace, if use interpolation default no interpolation
"""
ReductionSingleton().set_monitor_spectrum(specNum, interp)
def SetTransSpectrum(specNum, interp=False):
ReductionSingleton().set_trans_spectrum(specNum, interp)
def SetSampleOffset(value):
ReductionSingleton().instrument.set_sample_offset(value)
def Gravity(flag, extra_length=0.0):
_printMessage('Gravity(' + str(flag) + ', ' + str(extra_length) + ')')
ReductionSingleton().to_Q.set_gravity(flag)
ReductionSingleton().to_Q.set_extra_length(extra_length)
def SetFrontDetRescaleShift(scale=1.0, shift=0.0, fitScale=False, fitShift=False, qMin=None, qMax=None):
"""
Stores property about the detector which is used to rescale and shift
data in the bank after data have been reduced
@param scale: Default to 1.0. Value to multiply data with
@param shift: Default to 0.0. Value to add to data
@param fitScale: Default is False. Whether or not to try and fit this param
@param fitShift: Default is False. Whether or not to try and fit this param
@param qMin: When set to None (default) then for fitting use the overlapping q region of front and rear detectors
@param qMax: When set to None (default) then for fitting use the overlapping q region of front and rear detectors
"""
ReductionSingleton().instrument.getDetector('FRONT').rescaleAndShift = ReductionSingleton().instrument. \
getDetector('FRONT')._RescaleAndShift(scale, shift, fitScale, fitShift, qMin, qMax)
_printMessage('#Set front detector rescale/shift values')
def SetMergeQRange(q_min=None, q_max=None):
"""
Stores property about the detector which is used to specify merge range.
@param qMin: When set to None (default) then for merge use the overlapping q region of front and rear detectors
@param qMax: When set to None (default) then for merge use the overlapping q region of front and rear detectors
"""
ReductionSingleton().instrument.getDetector('FRONT').mergeRange = ReductionSingleton().instrument. \
getDetector('FRONT')._MergeRange(q_min, q_max)
_printMessage('#Set merge range values')
def SetDetectorMaskFiles(filenames):
assert isinstance(filenames, str),\
"Expected a command seperated list of filenames, got %r instead" % filenames
ReductionSingleton().settings["MaskFiles"] = filenames
_printMessage('#Set masking file names to {0}'.format(filenames))
def SetMonDirect(correction_file):
if not ReductionSingleton().instrument:
raise RuntimeError("You must create an instrument object first")
ReductionSingleton().instrument.cur_detector().correction_file = correction_file
ReductionSingleton().instrument.other_detector().correction_file = correction_file
_printMessage('#Set MonDirect to {0}'.format(correction_file))
def TransFit(mode, lambdamin=None, lambdamax=None, selector='BOTH'):
"""
Sets the fit method to calculate the transmission fit and the wavelength range
over which to do the fit. These arguments are passed to the algorithm
CalculateTransmission. If mode is set to 'Off' then the unfitted workspace is
used and lambdamin and max have no effect
@param mode: can be 'Logarithmic' ('YLOG', 'LOG') 'OFF' ('CLEAR') or 'LINEAR' (STRAIGHT', LIN'), 'POLYNOMIAL2', 'POLYNOMIAL3', ...
@param lambdamin: the lowest wavelength to use in any fit
@param lambdamax: the end of the fit range
@param selector: define for which transmission this fit specification is valid (BOTH, SAMPLE, CAN)
"""
mode = str(mode).strip().upper()
message = mode
if lambdamin:
message += ', ' + str(lambdamin)
if lambdamax:
message += ', ' + str(lambdamax)
message += ', selector=' + selector
_printMessage("TransFit(\"" + message + "\")")
ReductionSingleton().set_trans_fit(lambdamin, lambdamax, mode, selector)
def TransWorkspace(sample, can=None):
"""
Use a given workpspace that contains pre-calculated transmissions
@param sample the workspace to use for the sample
@param can calculated transmission for the can
"""
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 (Exception, Warning):
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
only the sample container is in the sample position. Hence allowing the effect of the
container to be removed. The run is specified using instrumentrunnumber.extension,
e.g. SANS2D7777.nxs. On calling this function the run is loaded to a workspace and the
detector banks and other components moved as applicable. Currently only reload=true is
supported.
@param can_run: run number to analysis e.g. SANS2D7777.nxs
@param reload: must be set to True
@param period: the period (entry) number to load, default is the first period
"""
mes = 'AssignCan("' + str(can_run) + '"'
if period != isis_reduction_steps.LoadRun.UNSET_PERIOD:
mes += ', ' + str(period)
mes += ')'
_printMessage(mes)
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):
"""
Specify the transmission and direct runs for the sample
@param sample: the transmission run
@param direct: direct run
@param reload: if to replace the workspace if it is already there
@param period_t: the entry number of the transmission run (default single entry file)
@param period_d: the entry number of the direct run (default single entry file)
"""
_printMessage('TransmissionSample("' + str(sample) + '","' + str(direct) + '")')
ReductionSingleton().set_trans_sample(sample, direct, reload, period_t, period_d)
return ReductionSingleton().samp_trans_load.execute(
ReductionSingleton(), None)
def TransmissionCan(can, direct, reload=True, period_t=-1, period_d=-1):
"""
Specify the transmission and direct runs for the can
@param can: the transmission run
@param direct: direct run
@param reload: if to replace the workspace if it is already there
@param period_t: the entry number of the transmission run (default single entry file)
@param period_d: the entry number of the direct run (default single entry file)
"""
_printMessage('TransmissionCan("' + str(can) + '","' + str(direct) + '")')
ReductionSingleton().set_trans_can(can, direct, reload, period_t, period_d)
return ReductionSingleton().can_trans_load.execute(
ReductionSingleton(), None)
def AssignSample(sample_run, reload=True, period=isis_reduction_steps.LoadRun.UNSET_PERIOD):
"""
Specifies the run to analyse using the format instrumentrunnumber.extension,
e.g. SANS2D7777.nxs. This is one of the few commands that executes Mantid algorithms
when called. Currently only reload=true is supported.
@param sample_run: run number to analysis e.g. SANS2D7777.nxs
@param reload: must be set to True
@param period: the period (entry) number to load, default is the first period
"""
mes = 'AssignSample("' + str(sample_run) + '"'
if period != isis_reduction_steps.LoadRun.UNSET_PERIOD:
mes += ', ' + str(period)
mes += ')'
_printMessage(mes)
ReductionSingleton().set_sample(sample_run, reload, period)
global LAST_SAMPLE
LAST_SAMPLE = ReductionSingleton().get_sample().wksp_name
return _return_old_compatibility_assign_methods(LAST_SAMPLE)
def SetCentre(xcoord, ycoord, bank='rear'):
"""
Configure the Beam Center position. It support the configuration of the centre for
the both detectors bank (low-angle bank and high-angle bank detectors)
It allows defining the position for both detector banks.
:param xcoord: X position of beam center in the user coordinate system.
:param ycoord: Y position of beam center in the user coordinate system.
:param bank: The selected bank ('rear' - low angle or 'front' - high angle)
Introduced #5942
"""
_printMessage('SetCentre(' + str(xcoord) + ', ' + str(ycoord) + ')')
# use the scale factors from the parameter file to scale correctly
XSF = ReductionSingleton().inst.beam_centre_scale_factor1
YSF = ReductionSingleton().inst.beam_centre_scale_factor2
ReductionSingleton().set_beam_finder(isis_reduction_steps.BaseBeamFinder(
float(xcoord) / XSF, float(ycoord) / YSF), bank)
def GetMismatchedDetList():
"""
Return the list of mismatched detector names
"""
return ReductionSingleton().instrument.get_marked_dets()
# pylint: disable = too-many-branches
def WavRangeReduction(wav_start=None, wav_end=None, full_trans_wav=None, name_suffix=None, combineDet=None, # noqa: C901
resetSetup=True, out_fit_settings=dict()):
"""
Run reduction from loading the raw data to calculating Q. Its optional arguments allows specifics
details to be adjusted, and optionally the old setup is reset at the end. Note if FIT of RESCALE or SHIFT
is selected then both REAR and FRONT detectors are both reduced EXCEPT if only the REAR detector is selected
to be reduced
@param wav_start: the first wavelength to be in the output data
@param wav_end: the last wavelength in the output data
@param full_trans_wav: if to use a wide wavelength range, the instrument's default wavelength range,
for the transmission correction, false by default
@param name_suffix: append the created output workspace with this
@param combineDet: combineDet can be one of the following:
'rear' (run one reduction for the 'rear' detector data)
'front' (run one reduction for the 'front' detector data, and rescale+shift 'front' data)
'both' (run both the above two reductions)
'merged' (run the same reductions as 'both' and additionally create a merged data workspace)
None (run one reduction for whatever detector has been set as the current detector
before running this method. If front apply rescale+shift)
@param resetSetup: if true reset setup at the end
@param out_fit_settings: An output parameter. It is used, specially when resetSetup is True, in order to remember the
'scale and fit' of the fitting algorithm.
@return Name of one of the workspaces created
"""
_printMessage('WavRangeReduction(' + str(wav_start) + ', ' + str(wav_end) + ', ' + str(full_trans_wav) + ')')
# these flags indicate if it is necessary to reduce the front bank, the rear bank and if it is supposed to
# merge them
reduce_rear_flag = False
reduce_front_flag = False
merge_flag = False
retWSname_rear, retWSname_front, retWSname_merged = ["", "", ""]
# combineDet from None to 'rear' or 'front'
if combineDet is None:
if ReductionSingleton().instrument.cur_detector().isAlias('FRONT'):
combineDet = 'front'
else:
combineDet = 'rear'
if full_trans_wav is not None:
ReductionSingleton().full_trans_wav = full_trans_wav
ReductionSingleton().to_wavelen.set_range(wav_start, wav_end)
rAnds = ReductionSingleton().instrument.getDetector('FRONT').rescaleAndShift
# check if fit is required.
fitRequired = False
if rAnds.fitScale or rAnds.fitShift:
fitRequired = True
com_det_option = combineDet.lower()
# the only special case where reduce rear is not required is
# if the user chose to reduce front and does not require fit
if not (com_det_option == 'front' and not fitRequired):
reduce_rear_flag = True
if com_det_option != 'rear':
reduce_front_flag = True
if com_det_option == 'merged':
merge_flag = True
# The shift and scale is always on the front detector.
if not reduce_front_flag:
fitRequired = False
# To backup value of singleton which are temporarily modified in this method
toRestoreAfterAnalysis = ReductionSingleton().instrument.cur_detector().name()
toRestoreOutputParts = ReductionSingleton().to_Q.outputParts
# if 'merged' then when cross section is calculated output the two individual parts
# of the cross section. These additional outputs are required to calculate
# the merged workspace
if merge_flag:
ReductionSingleton().to_Q.outputParts = True
# do reduce rear bank data
if reduce_rear_flag:
ReductionSingleton().instrument.setDetector('rear')
retWSname_rear, rear_slices = _WavRangeReduction(name_suffix)
retWSname = retWSname_rear
# do reduce front bank
if reduce_front_flag:
# it is necessary to replace the Singleton if a reduction was done before
if reduce_rear_flag:
# In this case, it is necessary to reload the files, in order to move the components to the
# correct position defined by its get_beam_center. (ticket #5942)
# first copy the settings
ReductionSingleton.replace(ReductionSingleton().cur_settings())
# for the LOQ instrument, if the beam centers are different, we have to reload the data.
if ReductionSingleton().instrument._NAME == 'LOQ' and \
ReductionSingleton().get_beam_center('rear') != ReductionSingleton(). \
get_beam_center('front'):
# It is necessary to reload sample, transmission and can files.
# reload sample
issueWarning('Trying to reload workspaces')
ReductionSingleton().instrument.setDetector('front')
ReductionSingleton()._sample_run.reload(ReductionSingleton())
# reassign can
if ReductionSingleton().get_can():
ReductionSingleton().get_can().reload(ReductionSingleton())
if ReductionSingleton().samp_trans_load:
# refresh Transmission
ReductionSingleton().samp_trans_load.execute(ReductionSingleton(), None)
if ReductionSingleton().can_trans_load:
ReductionSingleton().can_trans_load.execute(ReductionSingleton(), None)
ReductionSingleton().instrument.setDetector('front')
retWSname_front, front_slices = _WavRangeReduction(name_suffix)
retWSname = retWSname_front
# This section provides a the REAR -- FRONT fitting and a stitched workspace.
# If merge_flag is selected we use SANSStitch and get the fitting for free
# If fitRequired is selected, then we explicitly call the SANSFitScale algorithm
if merge_flag:
if ReductionSingleton().getNumSlices() > 1:
slices = []
for index in range(ReductionSingleton().getNumSlices()):
merge_workspace = _merge_workspaces(front_slices[index], rear_slices[index], rAnds)
slices.append(merge_workspace)
ReductionSingleton().setSliceIndex(0)
group_name = _common_substring(slices[0], slices[1])
if group_name[-2] == "_":
group_name = group_name[:-2]
_group_workspaces(slices, group_name)
retWSname_merged = group_name
else:
retWSname_merged = _merge_workspaces(retWSname_front, retWSname_rear, rAnds)
retWSname = retWSname_merged
elif fitRequired:
# Get fit parameters
scale_factor, shift_factor, fit_mode = su.extract_fit_parameters(rAnds)
# Since only the fit is required we use only the SANSFitScale algorithm
kwargs_fit = {"HABWorkspace": mtd[retWSname_front],
"LABWorkspace": mtd[retWSname_rear],
"Mode": fit_mode,
"ScaleFactor": scale_factor,
"ShiftFactor": shift_factor}
alg_fit = su.createUnmanagedAlgorithm("SANSFitShiftScale", **kwargs_fit)
alg_fit.execute()
# Get the fit values
shift_from_alg = alg_fit.getProperty("OutShiftFactor").value
scale_from_alg = alg_fit.getProperty("OutScaleFactor").value
ReductionSingleton().instrument.getDetector('FRONT').rescaleAndShift.shift = shift_from_alg
ReductionSingleton().instrument.getDetector('FRONT').rescaleAndShift.scale = scale_from_alg
shift = ReductionSingleton().instrument.getDetector('FRONT').rescaleAndShift.shift
scale = ReductionSingleton().instrument.getDetector('FRONT').rescaleAndShift.scale
# applying scale and shift on the front detector reduced data
if reduce_front_flag:
Scale(InputWorkspace=retWSname_front, OutputWorkspace=retWSname_front, Operation="Add", Factor=shift)
Scale(InputWorkspace=retWSname_front, OutputWorkspace=retWSname_front, Operation="Multiply", Factor=scale)
# finished calculating cross section so can restore these value
ReductionSingleton().to_Q.outputParts = toRestoreOutputParts
ReductionSingleton().instrument.setDetector(toRestoreAfterAnalysis)
# update the scale and shift values of out_fit_settings
out_fit_settings['scale'] = ReductionSingleton().instrument.getDetector('FRONT').rescaleAndShift.scale
out_fit_settings['shift'] = ReductionSingleton().instrument.getDetector('FRONT').rescaleAndShift.shift
if resetSetup:
_refresh_singleton()
# Relabel the YUnit of the resulting workspaces before we return anything.
# Depending on the given options, we may have rear, front and merged
# workspaces to handle. These may also be WorkspaceGroups.
for ws_name in [retWSname_rear, retWSname_front, retWSname_merged]:
if ws_name not in mtd:
continue
ws = mtd[ws_name]
if isinstance(ws, WorkspaceGroup):
relabel_ws_list = [mtd[name] for name in ws.getNames()]
else:
relabel_ws_list = [ws]
for relabel_ws in relabel_ws_list:
relabel_ws.setYUnitLabel("I(q) (cm-1)")
return retWSname
def _merge_workspaces(retWSname_front, retWSname_rear, rAnds):
# Prepare the Norm and Count workspaces for the FRONT and the REAR detectors
retWSname_merged = retWSname_rear
if retWSname_merged.count('rear') == 1:
retWSname_merged = retWSname_merged.replace('rear', 'merged')
elif retWSname_merged.count('main') == 1:
retWSname_merged = retWSname_merged.replace('main', 'merged')
else:
retWSname_merged = retWSname_merged + "_merged"
Nf = mtd[retWSname_front + "_sumOfNormFactors"]
Nr = mtd[retWSname_rear + "_sumOfNormFactors"]
Cf = mtd[retWSname_front + "_sumOfCounts"]
Cr = mtd[retWSname_rear + "_sumOfCounts"]
consider_can = True
try:
Nf_can = mtd[retWSname_front + "_can_tmp_sumOfNormFactors"]
Nr_can = mtd[retWSname_rear + "_can_tmp_sumOfNormFactors"]
Cf_can = mtd[retWSname_front + "_can_tmp_sumOfCounts"]
Cr_can = mtd[retWSname_rear + "_can_tmp_sumOfCounts"]
if Cr_can is None:
consider_can = False
except KeyError:
# The CAN was not specified
consider_can = False
# Get fit parameters
scale_factor, shift_factor, fit_mode, fit_min, fit_max = su.extract_fit_parameters(rAnds)
merge_range = ReductionSingleton().instrument.getDetector('FRONT').mergeRange
kwargs_stitch = {"HABCountsSample": Cf,
"HABNormSample": Nf,
"LABCountsSample": Cr,
"LABNormSample": Nr,
"ProcessCan": False,
"Mode": fit_mode,
"ScaleFactor": scale_factor,
"ShiftFactor": shift_factor,
"OutputWorkspace": retWSname_merged,
"MergeMask": merge_range.q_merge_range}
if consider_can:
kwargs_can = {"HABCountsCan": Cf_can,
"HABNormCan": Nf_can,
"LABCountsCan": Cr_can,
"LABNormCan": Nr_can,
"ProcessCan": True}
kwargs_stitch.update(kwargs_can)
if rAnds.qRangeUserSelected:
q_range_stitch = {"FitMin": fit_min,
"FitMax": fit_max}
kwargs_stitch.update(q_range_stitch)
if merge_range.q_merge_range:
if merge_range.q_min:
q_range_options = {"MergeMin": merge_range.q_min}
kwargs_stitch.update(q_range_options)
if merge_range.q_max:
q_range_options = {"MergeMax": merge_range.q_max}
kwargs_stitch.update(q_range_options)
alg_stitch = su.createUnmanagedAlgorithm("SANSStitch", **kwargs_stitch)
alg_stitch.execute()
# Get the fit values
shift_from_alg = alg_stitch.getProperty("OutShiftFactor").value
scale_from_alg = alg_stitch.getProperty("OutScaleFactor").value
ReductionSingleton().instrument.getDetector('FRONT').rescaleAndShift.shift = shift_from_alg
ReductionSingleton().instrument.getDetector('FRONT').rescaleAndShift.scale = scale_from_alg
# Get the merged workspace
mergedQ = alg_stitch.getProperty("OutputWorkspace").value
# Add the output to the Analysis Data Service
AnalysisDataService.addOrReplace(retWSname_merged, mergedQ)
# save the properties Transmission and TransmissionCan inside the merged workspace
# get these values from the rear_workspace because they are the same value as the front one.
# ticket #6929
rear_ws = mtd[retWSname_rear]
for prop in ['Transmission', 'TransmissionCan']:
if rear_ws.getRun().hasProperty(prop):
ws_name = rear_ws.getRun().getLogData(prop).value
if mtd.doesExist(ws_name): # ensure the workspace has not been deleted
AddSampleLog(Workspace=retWSname_merged, LogName=prop, LogText=ws_name)
retWSname = retWSname_merged
# Remove the partial workspaces, this needs to be done for when we merge and/or fit
delete_workspaces(retWSname_rear + "_sumOfCounts")
delete_workspaces(retWSname_rear + "_sumOfNormFactors")
delete_workspaces(retWSname_front + "_sumOfCounts")
delete_workspaces(retWSname_front + "_sumOfNormFactors")
if consider_can:
delete_workspaces(retWSname_front + "_can_tmp_sumOfNormFactors")
delete_workspaces(retWSname_rear + "_can_tmp_sumOfNormFactors")
delete_workspaces(retWSname_front + "_can_tmp_sumOfCounts")
delete_workspaces(retWSname_rear + "_can_tmp_sumOfCounts")
return retWSname
def _common_substring(val1, val2):
l = []
for i in range(len(val1)):
if val1[i] == val2[i]:
l.append(val1[i])
else:
return ''.join(l)
def _group_workspaces(list_of_values, outputname):
allnames = ','.join(list_of_values)
GroupWorkspaces(InputWorkspaces=allnames, OutputWorkspace=outputname)
def _WavRangeReduction(name_suffix=None):
"""
Run a reduction that has been set up, from loading the raw data to calculating Q
"""
def _setUpPeriod(period):
assert ReductionSingleton().get_sample().loader.move2ws(period)
can = ReductionSingleton().get_can()
if can and can.loader.periods_in_file > 1:
can.loader.move2ws(period)
for trans in [ReductionSingleton().samp_trans_load, ReductionSingleton().can_trans_load]:
if trans and trans.direct.periods_in_file > 1 and trans.trans.periods_in_file > 1:
trans.direct.move2ws(period)
trans.trans.move2ws(period)
return
def _applySuffix(result, name_suffix):
if name_suffix:
old = result
result += name_suffix
RenameWorkspace(InputWorkspace=old, OutputWorkspace=result)
return result
def _reduceAllSlices():
if ReductionSingleton().getNumSlices() > 1:
slices = []
for index in range(ReductionSingleton().getNumSlices()):
ReductionSingleton().setSliceIndex(index)
slices.append(ReductionSingleton()._reduce())
ReductionSingleton().setSliceIndex(0)
group_name = _common_substring(slices[0], slices[1])
if group_name[-2] == "_":
group_name = group_name[:-2]
_group_workspaces(slices, group_name)
return group_name, slices
else:
return ReductionSingleton()._reduce(), None
result = ""
slices = []
if ReductionSingleton().get_sample().loader.periods_in_file == 1:
result, slices = _reduceAllSlices()
return _applySuffix(result, name_suffix), slices
calculated = []
try:
for period in ReductionSingleton().get_sample().loader.entries:
_setUpPeriod(period)
result, slices = _reduceAllSlices()
calculated.append(result)
finally:
if len(calculated) > 0:
result = ReductionSingleton().get_out_ws_name(show_period=False)
_group_workspaces(calculated, result)
return _applySuffix(result, name_suffix), slices
def delete_workspaces(workspaces):
"""
Delete the list of workspaces if possible but fail siliently if there is
a problem
@param workspaces: the list to delete
"""
if not isinstance(workspaces, type(list())):
if not isinstance(workspaces, type(tuple())):
workspaces = [workspaces]
for wksp in workspaces:
if wksp and wksp in mtd:
try:
DeleteWorkspace(Workspace=wksp)
except (Exception, Warning):
# we're only deleting to save memory, if the workspace really won't delete leave it
pass
def CompWavRanges(wavelens, plot=True, combineDet=None, resetSetup=True):
"""
Compares the momentum transfer results calculated from different wavelength ranges. Given
the list of wave ranges [a, b, c] it reduces for wavelengths a-b, b-c and a-c.
@param wavelens: the list of wavelength ranges
@param plot: set this to true to plot the result (must be run in Mantid), default is true
@param combineDet: see description in WavRangeReduction
@param resetSetup: if true reset setup at the end
"""
_printMessage('CompWavRanges( %s,plot=%s)' % (str(wavelens), plot))
if not isinstance(wavelens, type([])) or len(wavelens) < 2:
if not isinstance(wavelens, type((1,))):
raise RuntimeError(
'Error CompWavRanges() requires a list of wavelengths between which reductions will be performed.')
calculated = [WavRangeReduction(wav_start=wavelens[0], wav_end=wavelens[len(wavelens) - 1], combineDet=combineDet,
resetSetup=False)]
for i in range(0, len(wavelens) - 1):
calculated.append(
WavRangeReduction(wav_start=wavelens[i], wav_end=wavelens[i + 1], combineDet=combineDet, resetSetup=False))
if resetSetup:
_refresh_singleton()
if plot:
raise NotImplementedError("Plotting on the deprecated ISISCommandInterface required MantidPlot and is no"
" longer implemented. Please switch to sans.command_interface.ISISCommandInterface")
# return just the workspace name of the full range
return calculated[0]
def PhiRanges(phis, plot=True):
"""
Given a list of phi ranges [a, b, c, d] it reduces in the phi ranges a-b and c-d
@param phis: the list of phi ranges
@param plot: set this to true to plot the result (must be run in Mantid), default is true
"""
_printMessage('PhiRanges( %s,plot=%s)' % (str(phis), plot))
# todo covert their string into Python array
if len(phis) / 2 != float(len(phis)) / 2.:
raise RuntimeError('Phi ranges must be given as pairs')
try:
# run the reductions, calculated will be an array with the names of all the workspaces produced
calculated = []
for i in range(0, len(phis), 2):
SetPhiLimit(phis[i], phis[i + 1])
# reducedResult = ReductionSingleton()._reduce()
# RenameWorkspace(reducedResult,'bob')
# calculated.append(reducedResult)
calculated.append(ReductionSingleton()._reduce())
ReductionSingleton.replace(ReductionSingleton().cur_settings())
finally:
_refresh_singleton()
if plot:
raise NotImplementedError("Plotting on the deprecated ISISCommandInterface required MantidPlot and is no"
" longer implemented. Please switch to sans.command_interface.ISISCommandInterface")
# return just the workspace name of the full range
return calculated[0]
def Reduce():
try:
result = ReductionSingleton()._reduce()
finally:
_refresh_singleton()
return result
def _SetWavelengthRange(start, end):
ReductionSingleton().to_wavelen.set_range(start, end)
def Set1D():
_printMessage('Set1D()')
ReductionSingleton().set_Q_output_type('1D')
def Set2D():
_printMessage('Set2D()')
ReductionSingleton().set_Q_output_type('2D')
def SetDetectorFloodFile(filename, detector_name="REAR"):
ReductionSingleton().prep_normalize.setPixelCorrFile(filename, detector_name)
def SetPhiLimit(phimin, phimax, use_mirror=True):
"""
Call this function to restrict the analyse segments of the detector. Phimin and
phimax define the limits of the segment where phi=0 is the -x axis and phi = 90
is the y-axis. Setting use_mirror to true includes a second segment to be included
it is the same as the first but rotated 180 degrees.
@param phimin: the minimum phi angle to include
@param phimax: the upper limit on phi for the segment
@param use_mirror: when True (default) another segment is included, rotated 180 degrees from the first
"""
_printMessage("SetPhiLimit(" + str(phimin) + ', ' + str(phimax) + ',use_mirror=' + str(use_mirror) + ')')
# a beam centre of [0,0,0] makes sense if the detector has been moved such that beam centre is at [0,0,0]
ReductionSingleton().mask.set_phi_limit(phimin, phimax, use_mirror)
# pylint: disable = too-many-arguments
def SetDetectorOffsets(bank, x, y, z, rot, radius, side, xtilt=0.0, ytilt=0.0):
# 10/03/15 RKH added 2 more parameters - xtilt & ytilt
"""
Adjust detector position away from position defined in IDF. On SANS2D the detector
banks can be moved around. This method allows fine adjustments of detector bank position
in the same way as the DET/CORR userfile command works. Hence please see
http://www.mantidproject.org/SANS_User_File_Commands#DET for details.
Note, for now, this command will only have an effect on runs loaded
after this command have been executed (because it is when runs are loaded
that components are moved away from the positions set in the IDF)
@param bank: Must be either 'front' or 'rear' (not case sensitive)
@param x: shift in mm
@param y: shift in mm
@param z: shift in mm
@param rot: shift in degrees
@param radius: shift in mm
@param side: shift in mm
@param side: xtilt in degrees
@param side: ytilt in degrees
"""
_printMessage("SetDetectorOffsets(" + str(bank) + ', ' + str(x)
+ ',' + str(y) + ',' + str(z) + ',' + str(rot)
+ ',' + str(radius) + ',' + str(side) + ',' + str(xtilt) + ',' + str(ytilt) + ')')
detector = ReductionSingleton().instrument.getDetector(bank)
detector.x_corr = x
detector.y_corr = y
detector.z_corr = z
detector.rot_corr = rot
detector.radius_corr = radius
detector.side_corr = side
# 10/03/15 RKH add 2 more
detector.x_tilt = xtilt
detector.y_tilt = ytilt
def SetCorrectionFile(bank, filename):
# 10/03/15 RKH, create a new routine that allows change of "direct beam file" = correction file,
# for a given detector, this simplify the iterative process used to adjust it.
# Will still have to keep changing the name of the file
# for each iteratiom to avoid Mantid using a cached version, but can then use
# only a single user (=mask) file for each set of iterations.
# Modelled this on SetDetectorOffsets above ...
"""
@param bank: Must be either 'front' or 'rear' (not case sensitive)
@param filename: self explanatory
"""
_printMessage("SetCorrectionFile(" + str(bank) + ', ' + filename + ')')
detector = ReductionSingleton().instrument.getDetector(bank)
detector.correction_file = filename
def LimitsR(rmin, rmax, quiet=False, reducer=None):
if reducer is None:
reducer = ReductionSingleton().reference()
if not quiet:
_printMessage('LimitsR(' + str(rmin) + ', ' + str(rmax) + ')', reducer)
reducer.mask.set_radi(rmin, rmax)
reducer.CENT_FIND_RMIN = float(rmin) / 1000.
reducer.CENT_FIND_RMAX = float(rmax) / 1000.
def LimitsWav(lmin, lmax, step, bin_type):
_printMessage('LimitsWav(' + str(lmin) + ', ' + str(lmax) + ', ' + str(step) + ', ' + bin_type + ')')
if bin_type.upper().strip() == 'LINEAR':
bin_type = 'LIN'
if bin_type.upper().strip() == 'LOGARITHMIC':
bin_type = 'LOG'
if bin_type == 'LOG':
bin_sym = '-'
else:
bin_sym = ''
ReductionSingleton().to_wavelen.set_rebin(lmin, bin_sym + str(step), lmax)
def LimitsQXY(qmin, qmax, step, type):
"""
To set the bin parameters for the algorithm Qxy()
@param qmin: the first Q value to include
@param qmaz: the last Q value to include
@param step: bin width
@param type: pass LOG for logarithmic binning
"""
_printMessage('LimitsQXY(' + str(qmin) + ', ' + str(qmax) + ', ' + str(step) + ', ' + str(type) + ')')
settings = ReductionSingleton().user_settings
if settings is None:
raise RuntimeError('MaskFile() first')
settings.readLimitValues('L/QXY ' + str(qmin) + ' ' + str(qmax) + ' ' + str(step) + '/' + type,
ReductionSingleton())