source: sasview/src/sas/sasgui/perspectives/file_converter/converter_panel.py @ af84162

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since af84162 was af84162, checked in by lewis, 8 years ago

Move detector metadata input to its own window

  • Property mode set to 100644
File size: 11.7 KB
Line 
1"""
2This module provides a GUI for the file converter
3"""
4
5import wx
6import sys
7import numpy as np
8from wx.lib.scrolledpanel import ScrolledPanel
9from sas.sasgui.guiframe.panel_base import PanelBase
10from sas.sasgui.perspectives.calculator import calculator_widgets as widget
11from sas.sasgui.perspectives.file_converter.converter_widgets import VectorInput
12from sas.sasgui.perspectives.file_converter.detector_panel import DetectorWindow
13from sas.sasgui.guiframe.events import StatusEvent
14from sas.sasgui.guiframe.dataFitting import Data1D
15from sas.sasgui.guiframe.utils import check_float
16from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader
17from sas.sascalc.dataloader.data_info import Detector
18from sas.sascalc.dataloader.data_info import Vector
19
20# Panel size
21if sys.platform.count("win32") > 0:
22    PANEL_TOP = 0
23    _STATICBOX_WIDTH = 410
24    _BOX_WIDTH = 200
25    PANEL_SIZE = 480
26    FONT_VARIANT = 0
27else:
28    PANEL_TOP = 60
29    _STATICBOX_WIDTH = 430
30    _BOX_WIDTH = 200
31    PANEL_SIZE = 500
32    FONT_VARIANT = 1
33
34class ConverterPanel(ScrolledPanel, PanelBase):
35
36    def __init__(self, parent, base=None, *args, **kwargs):
37        ScrolledPanel.__init__(self, parent, *args, **kwargs)
38        PanelBase.__init__(self)
39        self.SetupScrolling()
40        self.SetWindowVariant(variant=FONT_VARIANT)
41
42        self.base = base
43        self.parent = parent
44        self.detector_frame = None
45
46        self.q_input = None
47        self.iq_input = None
48        self.output = None
49        self.to_validate = []
50
51        self.metadata = {
52            'title': None,
53            'run': None,
54            'run_name': None,
55            'instrument': None,
56            'detector': [Detector()]
57        }
58        self.vectors = ['offset', 'orientation', 'pixel_size', 'beam_center']
59        for vector_name in self.vectors:
60            setattr(self.metadata['detector'][0], vector_name, Vector())
61
62        self._do_layout()
63        self.SetAutoLayout(True)
64        self.Layout()
65
66    def convert_to_cansas(self, data, filename):
67        reader = CansasReader()
68        reader.write(filename, data)
69
70    def extract_data(self, filename):
71        data = np.loadtxt(filename, dtype=str)
72
73        if len(data.shape) != 1:
74            msg = "Error reading {}: Only one column of data is allowed"
75            raise Exception(msg.format(filename.split('\\')[-1]))
76
77        is_float = True
78        try:
79            float(data[0])
80        except:
81            is_float = False
82
83        if not is_float:
84            end_char = data[0][-1]
85            # If lines end with comma or semi-colon, trim the last character
86            if end_char == ',' or end_char == ';':
87                data = map(lambda s: s[0:-1], data)
88            else:
89                msg = ("Error reading {}: Lines must end with a digit, comma "
90                    "or semi-colon").format(filename.split('\\')[-1])
91                raise Exception(msg)
92
93        return np.array(data, dtype=np.float32)
94
95    def on_convert(self, event):
96        if not self.validate_inputs():
97            return
98
99        try:
100            qdata = self.extract_data(self.q_input.GetPath())
101            iqdata = self.extract_data(self.iq_input.GetPath())
102        except Exception as ex:
103            msg = str(ex)
104            wx.PostEvent(self.parent.manager.parent,
105                StatusEvent(status=msg, info='error'))
106            return
107
108        output_path = self.output.GetPath()
109        data = Data1D(x=qdata, y=iqdata)
110        data.filename = output_path.split('\\')[-1]
111
112        if self.metadata['run'] is not None:
113            run = self.metadata['run']
114            run_name = self.metadata['run_name']
115
116            if not isinstance(run, list) and run is not None:
117                self.metadata['run'] = [run]
118            else:
119                run = run[0]
120
121            if not isinstance(run_name, dict):
122                if run_name is not None:
123                    self.metadata['run_name'] = { run: run_name }
124                else:
125                    self.metadata['run_name'] = {}
126            else:
127                self.metadata['run_name'][run] = run_name.values()[0]
128        else:
129            self.metadata['run'] = []
130            self.metadata['run_name'] = {}
131        if self.metadata['detector'][0].name is None:
132            self.metadata['detector'][0].name = ''
133
134        # Convert vectors from strings to float
135        for vector_name in self.vectors:
136            # Vector of strings or Nones
137            vector = getattr(self.metadata['detector'][0], vector_name)
138            for direction in ['x', 'y', 'z']:
139                value = getattr(vector, direction)
140                if value is not None:
141                    value = float(value)
142                    setattr(vector, direction, value)
143            setattr(self.metadata['detector'][0], vector_name, vector)
144
145        for attr, value in self.metadata.iteritems():
146            if value is not None:
147                setattr(data, attr, value)
148
149        self.convert_to_cansas(data, output_path)
150        wx.PostEvent(self.parent.manager.parent,
151            StatusEvent(status="Conversion completed."))
152
153    def validate_inputs(self):
154        msg = "You must select a"
155        if self.q_input.GetPath() == '':
156            msg += " Q Axis input file."
157        elif self.iq_input.GetPath() == '':
158            msg += "n Intensity input file."
159        elif self.output.GetPath() == '':
160            msg += "destination for the converted file."
161        if msg != "You must select a":
162            wx.PostEvent(self.parent.manager.parent,
163                StatusEvent(status=msg, info='error'))
164            return
165
166        for ctrl in self.to_validate:
167            ctrl_valid = True
168            invalid_control = None
169            if isinstance(ctrl, VectorInput):
170                ctrl_valid, invalid_control = ctrl.Validate()
171            else:
172                if ctrl.GetValue() == '': continue
173                ctrl_valid = check_float(ctrl)
174                invalid_control = ctrl
175            if not ctrl_valid:
176                msg = "{} must be a valid float".format(
177                    invalid_control.GetName().replace('_', ' '))
178                wx.PostEvent(self.parent.manager.parent,
179                    StatusEvent(status=msg, info='error'))
180                return False
181
182        return True
183
184    def show_detector_window(self, event):
185        if self.detector_frame is not None:
186            self.detector_frame.panel.on_close()
187            return
188        self.detector_frame = DetectorWindow(self.parent.manager.parent,
189            manager=self, detector=self.metadata['detector'][0])
190        self.parent.manager.put_icon(self.detector_frame)
191        self.detector_frame.Show(True)
192
193    def metadata_changed(self, event):
194        event.Skip()
195        textbox = event.GetEventObject()
196        attr = textbox.GetName()
197        value = textbox.GetValue().strip()
198
199        if attr.startswith('detector_'):
200            attr = attr[9:] # Strip detector_
201            is_vector = False
202            for vector_name in self.vectors:
203                if attr.startswith(vector_name): is_vector = True
204            if is_vector:
205                if value == '': value = None
206                direction = attr[-1]
207                attr = attr[:-2]
208                vector = getattr(self.metadata['detector'][0], attr)
209                setattr(vector, direction, value)
210                value = vector
211            setattr(self.metadata['detector'][0], attr, value)
212            return
213
214        if value == '':
215            self.metadata[attr] = None
216        else:
217            self.metadata[attr] = value
218
219
220    def _do_layout(self):
221        vbox = wx.BoxSizer(wx.VERTICAL)
222
223        instructions = ("Select the 1 column ASCII files containing the Q Axis"
224            " and Intensity data, chose where to save the converted file, then"
225            " click Convert to convert them to CanSAS XML format. If required,"
226            " metadata can also be input below.")
227        instruction_label = wx.StaticText(self, -1, instructions,
228            size=(_STATICBOX_WIDTH+40, -1))
229        instruction_label.Wrap(_STATICBOX_WIDTH+40)
230        vbox.Add(instruction_label, flag=wx.TOP | wx.LEFT | wx.RIGHT, border=5)
231
232        section = wx.StaticBox(self, -1)
233        section_sizer = wx.StaticBoxSizer(section, wx.VERTICAL)
234        section_sizer.SetMinSize((_STATICBOX_WIDTH, -1))
235
236        input_grid = wx.GridBagSizer(5, 5)
237
238        q_label = wx.StaticText(self, -1, "Q Axis: ")
239        input_grid.Add(q_label, (0,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
240
241        self.q_input = wx.FilePickerCtrl(self, -1,
242            size=(_STATICBOX_WIDTH-80, -1),
243            message="Chose the Q Axis data file.")
244        input_grid.Add(self.q_input, (0,1), (1,1), wx.ALL, 5)
245
246        iq_label = wx.StaticText(self, -1, "Intensity Data: ")
247        input_grid.Add(iq_label, (1,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
248
249        self.iq_input = wx.FilePickerCtrl(self, -1,
250            size=(_STATICBOX_WIDTH-80, -1),
251            message="Chose the Intensity data file.")
252        input_grid.Add(self.iq_input, (1,1), (1,1), wx.ALL, 5)
253
254        output_label = wx.StaticText(self, -1, "Output File: ")
255        input_grid.Add(output_label, (2,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
256
257        self.output = wx.FilePickerCtrl(self, -1,
258            size=(_STATICBOX_WIDTH-80, -1),
259            message="Chose the Intensity data file.",
260            style=wx.FLP_SAVE | wx.FLP_OVERWRITE_PROMPT | wx.FLP_USE_TEXTCTRL,
261            wildcard="*.xml")
262        input_grid.Add(self.output, (2,1), (1,1), wx.ALL, 5)
263
264        convert_btn = wx.Button(self, wx.ID_OK, "Convert")
265        input_grid.Add(convert_btn, (3,0), (1,1), wx.ALL, 5)
266        convert_btn.Bind(wx.EVT_BUTTON, self.on_convert)
267
268        section_sizer.Add(input_grid)
269
270        vbox.Add(section_sizer, flag=wx.ALL, border=5)
271
272        metadata_section = wx.CollapsiblePane(self, -1, "Metadata",
273            size=(_STATICBOX_WIDTH+40, -1), style=wx.WS_EX_VALIDATE_RECURSIVELY)
274        metadata_pane = metadata_section.GetPane()
275        metadata_grid = wx.GridBagSizer(5, 5)
276
277        y = 0
278        for item in self.metadata.keys():
279            if item == 'detector': continue
280            label_txt = item.replace('_', ' ').capitalize()
281            label = wx.StaticText(metadata_pane, -1, label_txt,
282                style=wx.ALIGN_CENTER_VERTICAL)
283            input_box = wx.TextCtrl(metadata_pane, name=item,
284                size=(_STATICBOX_WIDTH-80, -1))
285            input_box.Bind(wx.EVT_TEXT, self.metadata_changed)
286            metadata_grid.Add(label, (y,0), (1,1),
287                wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
288            metadata_grid.Add(input_box, (y,1), (1,2), wx.EXPAND)
289            y += 1
290
291        detector_label = wx.StaticText(metadata_pane, -1,
292            "Detector:")
293        metadata_grid.Add(detector_label, (y, 0), (1,1), wx.ALL | wx.EXPAND, 5)
294        detector_btn = wx.Button(metadata_pane, -1, "Enter Detector Metadata")
295        metadata_grid.Add(detector_btn, (y, 1), (1,1), wx.ALL | wx.EXPAND, 5)
296        detector_btn.Bind(wx.EVT_BUTTON, self.show_detector_window)
297        y += 1
298
299        metadata_pane.SetSizer(metadata_grid)
300
301        vbox.Add(metadata_section, proportion=0, flag=wx.ALL, border=5)
302
303        vbox.Fit(self)
304        self.SetSizer(vbox)
305
306class ConverterWindow(widget.CHILD_FRAME):
307
308    def __init__(self, parent=None, title='File Converter', base=None,
309        manager=None, size=(PANEL_SIZE * 1.05, PANEL_SIZE / 1.55),
310        *args, **kwargs):
311        kwargs['title'] = title
312        kwargs['size'] = size
313        widget.CHILD_FRAME.__init__(self, parent, *args, **kwargs)
314
315        self.manager = manager
316        self.panel = ConverterPanel(self, base=None)
317        self.Bind(wx.EVT_CLOSE, self.on_close)
318        self.SetPosition((wx.LEFT, PANEL_TOP))
319        self.Show(True)
320
321    def on_close(self, event):
322        if self.manager is not None:
323            self.manager.converter_frame = None
324        self.Destroy()
Note: See TracBrowser for help on using the repository browser.