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

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 c9a519f was c9a519f, checked in by lewis, 8 years ago

Abstract vector inputs into their own class

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