SetSampleFromLogs.py 8.68 KB
Newer Older
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
# 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 +
from mantid.api import (AlgorithmFactory, DistributedDataProcessorAlgorithm)
from mantid.kernel import (ConfigService)
from mantid.simpleapi import (SetSample)


PROPS_FOR_SETSAMPLE = ["InputWorkspace", "Geometry", "Material", "Environment", "ContainerGeometry", "ContainerMaterial"]


def _findKey(dictlike, *args):
    for name in args:
        if (name in dictlike) and bool(dictlike[name]):
            return name
    return ''  # indicates failure


def _hasValue(dictLike, key):  # TODO refactor into *args and return the one that exists or empty string
    return (key in dictLike) and bool(dictLike[key])


def _getLogValue(propertyManager, key):
    if not _hasValue(propertyManager, key):
        raise ValueError('Failed to find "{}" in logs'.format(key))
    return propertyManager[key].lastValue()


class SetSampleFromLogs(DistributedDataProcessorAlgorithm):
    def category(self):
        return "Sample"

    def seeAlso(self):
        return ["SetSample"]

    def name(self):
        return "SetSampleFromLogs"

    def summary(self):
        return "This algorithm looks through the logs to automatically determine the sample geometry and material"

    def PyInit(self):
        self.copyProperties("SetSample", PROPS_FOR_SETSAMPLE)
        self.declareProperty("FindGeometry", True,
                             "Whether to look for the 'Height' parameter in the logs")
        self.declareProperty("FindSample", True,
                             "Whether to look for the sample material in the logs")
        self.declareProperty("FindEnvironment", True,
                             "Whether to look for the sample container in the logs")

54
55
56
    def _createMaterial(self, runObject):
        '''Create the sample material by combining the supplied material information with things found in the logs'''
        # grab the starting information from the algorithm property
57
        material = self.getProperty("Material").value
58
59
        # only look in the logs if the user asks for it
        if self.getProperty("FindSample").value:  # SetSample calls it the material
60
61
62
63
64
65
66
67
68
69
            if (not _hasValue(material, 'ChemicalFormula')) and _hasValue(runObject, "SampleFormula"):
                self.log().information("Looking for 'SampleFormula' in logs")
                material['ChemicalFormula'] = _getLogValue(runObject, 'SampleFormula').strip()
            if (not _hasValue(material, "SampleMassDensity")) and _hasValue(runObject, "SampleDensity"):
                self.log().information("Looking for 'SampleDensity', 'SampleMass', and 'SampleMassDensity' in logs")
                value = _getLogValue(runObject, 'SampleDensity')
                if value == 1.0 or value == 0.0:
                    material['Mass'] = _getLogValue(runObject, 'SampleMass')
                elif _hasValue(runObject, "SampleMass"):
                    material['SampleMassDensity'] = value
70
71

        # log the results and return
72
        self.log().information('MATERIAL: ' + str(material))
73
        return material
74

75
    def _createGeometry(self, runObject, instrEnum):
76
77
        # get height of sample from the logs
        # this assumes the shape has a "Height" property
78
        geometry = self.getProperty("Geometry").value
79
80

        if self.getProperty("FindGeometry").value:
81
82
83
84
85
86
87
88
89
90
91
92
93
94
            if not _hasValue(geometry, "Height"):
                heightInContainerNames = ['HeightInContainer']
                heightInContainerUnitsNames = ['HeightInContainerUnits']

                # determine "special" logs for SNS instruments
                if instrEnum.facility().name() == 'SNS':
                    beamline = ''
                    # TODO this would benefit from the beamline exposed through python
                    if instrEnum.name() == 'NOMAD':
                        beamline = 'BL1B'
                    elif instrEnum.name() == 'POWGEN':
                        beamline = 'BL11A'
                    else:
                        warningMsg = 'Do not know how to create lognames for "{}"'.format(instrEnum.name())
95
                        self.log().warning(warningMsg)
96
                    if beamline:
97
                        # "internal" log names at SNS are templated from the beamline number
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
                        heightInContainerUnitsNames.append('{}:CS:ITEMS:HeightInContainerUnits'.format(beamline))
                        heightInContainerNames.append('{}:CS:ITEMS:HeightInContainer'.format(beamline))
                self.log().information("Looking for sample height from {} and units from {}".format(heightInContainerNames,
                                                                                                    heightInContainerUnitsNames))
                # Check units - SetSample expects cm
                unitsKey = _findKey(runObject, *heightInContainerUnitsNames)
                units = ''
                if unitsKey:
                    units = _getLogValue(runObject, unitsKey)
                    # not finding units will generate a warning below
                # create conversion factor into cm
                conversion = 1.0  # don't do any conversion
                if units == "cm":
                    conversion = 1.0
                elif units == "mm":
                    conversion = 0.1
                else:
                    warningMsg = "HeightInContainerUnits expects cm or mm;" + \
                                " specified units not recognized: {:s};".format(units) + \
                                " we will reply on user input for sample density information."
118
                    self.log().warning(warningMsg)
119
120
121
122
123
124

                # set the height
                heightKey = _findKey(runObject, *heightInContainerNames)
                if heightKey:
                    geometry['Height'] = conversion * _getLogValue(runObject, heightKey)
                else:
125
126
127
                    warningMsg = "No valid height found in sample logs;" + \
                                 "we will reply on user input for sample density information."
                    self.log().warning(warningMsg)
128
129

        # log the results and return
130
        self.log().information('GEOMETRY (in cm): ' + str(geometry))
131
        return geometry
132

133
134
135
    def _createEnvironment(self, runObject, instrEnum):
        '''Create the sample material by combining the supplied environment information with things found in the logs'''
        # grab the starting information from the algorithm property
136
        environment = self.getProperty("Environment").value
137
        # get the container from the logs
138
        if self.getProperty("FindEnvironment").value:
139
140
141
            if (not _hasValue(environment, "Container")) and _hasValue(runObject, "SampleContainer"):
                self.log().information('Looking for "SampleContainer" in logs')
                environment['Container'] = runObject['SampleContainer'].lastValue().replace(" ", "")
142
143
144
145
146
147

            if _hasValue(environment, "Container") and (not _hasValue(environment, "Name")):
                # set a default environment
                if instrEnum.facility().name() == 'SNS':
                    environment['Name'] = 'InAir'

148
        self.log().information('ENVIRONMENT: ' + str(environment))
149
150
151
152
153
        return environment

    def PyExec(self):
        wksp = self.getProperty("InputWorkspace").value
        # these items are not grabbed from the logs
154
155
        geometryContainer = self.getProperty("ContainerGeometry").value
        materialContainer = self.getProperty("ContainerMaterial").value
156
157
158
159
160
161
162
163
164
165
166

        # get a convenient handle to the logs
        runObject = wksp.run()

        # this is used for determining some of the log names
        instrEnum = ConfigService.getInstrument(wksp.getInstrument().getFullName())

        # get information from the logs
        material = self._createMaterial(runObject)
        geometry = self._createGeometry(runObject, instrEnum)
        environment = self._createEnvironment(runObject, instrEnum)
167
168

        # let SetSample generate errors if anything is wrong
169
170
171
172
173
        if self.getProperty("Environment").value.values():
            SetSample(InputWorkspace=wksp,
                      Material=material,
                      Geometry=geometry)
        else:
174
175
176
177
178
179
            SetSample(InputWorkspace=wksp,
                      Material=material,
                      Geometry=geometry,
                      Environment=environment,
                      ContainerGeometry=geometryContainer,
                      ContainerMaterial=materialContainer)
180

Yuanpeng Zhang's avatar
Yuanpeng Zhang committed
181

182
183
# Register algorithm with Mantid.
AlgorithmFactory.subscribe(SetSampleFromLogs)