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

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

Fix bug when convert button clicked for a 2nd time after changing metadata

  • Property mode set to 100644
File size: 13.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', 'beam_center']
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
114            if not isinstance(run, list) and run is not None:
115                self.metadata['run'] = [run]
116            else:
117                run = run[0]
118
119            if not isinstance(run_name, dict):
120                if run_name is not None:
121                    self.metadata['run_name'] = { run: run_name }
122                else:
123                    self.metadata['run_name'] = {}
124            else:
125                self.metadata['run_name'][run] = run_name.values()[0]
126        else:
127            self.metadata['run'] = []
128            self.metadata['run_name'] = {}
129        if self.metadata['detector'][0].name is None:
130            self.metadata['detector'][0].name = ''
131
132        # Convert vectors from strings to float
133        for vector_name in self.vectors:
134            # Vector of strings or Nones
135            vector = getattr(self.metadata['detector'][0], vector_name)
136            for direction in ['x', 'y', 'z']:
137                value = getattr(vector, direction)
138                if value is not None:
139                    value = float(value)
140                    setattr(vector, direction, value)
141            setattr(self.metadata['detector'][0], vector_name, vector)
142
143        for attr, value in self.metadata.iteritems():
144            if value is not None:
145                setattr(data, attr, value)
146
147        self.convert_to_cansas(data, output_path)
148        wx.PostEvent(self.parent.manager.parent,
149            StatusEvent(status="Conversion completed."))
150
151    def validate_inputs(self):
152        msg = "You must select a"
153        if self.q_input.GetPath() == '':
154            msg += " Q Axis input file."
155        elif self.iq_input.GetPath() == '':
156            msg += "n Intensity input file."
157        elif self.output.GetPath() == '':
158            msg += "destination for the converted file."
159        if msg != "You must select a":
160            wx.PostEvent(self.parent.manager.parent,
161                StatusEvent(status=msg, info='error'))
162            return
163
164        for ctrl in self.to_validate:
165            ctrl_valid = True
166            invalid_control = None
167            if isinstance(ctrl, VectorInput):
168                ctrl_valid, invalid_control = ctrl.Validate()
169            else:
170                if ctrl.GetValue() == '': continue
171                ctrl_valid = check_float(ctrl)
172                invalid_control = ctrl
173            if not ctrl_valid:
174                msg = "{} must be a valid float".format(
175                    invalid_control.GetName().replace('_', ' '))
176                wx.PostEvent(self.parent.manager.parent,
177                    StatusEvent(status=msg, info='error'))
178                return False
179
180        return True
181
182
183    def metadata_changed(self, event):
184        event.Skip()
185        textbox = event.GetEventObject()
186        attr = textbox.GetName()
187        value = textbox.GetValue().strip()
188
189        if attr.startswith('detector_'):
190            attr = attr[9:] # Strip detector_
191            is_vector = False
192            for vector_name in self.vectors:
193                if attr.startswith(vector_name): is_vector = True
194            if is_vector:
195                if value == '': value = None
196                direction = attr[-1]
197                attr = attr[:-2]
198                vector = getattr(self.metadata['detector'][0], attr)
199                setattr(vector, direction, value)
200                value = vector
201            setattr(self.metadata['detector'][0], attr, value)
202            return
203
204        if value == '':
205            self.metadata[attr] = None
206        else:
207            self.metadata[attr] = value
208
209
210    def _do_layout(self):
211        vbox = wx.BoxSizer(wx.VERTICAL)
212
213        instructions = ("Select the 1 column ASCII files containing the Q Axis"
214            " and Intensity data, chose where to save the converted file, then"
215            " click Convert to convert them to CanSAS XML format. If required,"
216            " metadata can also be input below.")
217        instruction_label = wx.StaticText(self, -1, instructions,
218            size=(_STATICBOX_WIDTH+40, -1))
219        instruction_label.Wrap(_STATICBOX_WIDTH+40)
220        vbox.Add(instruction_label, flag=wx.TOP | wx.LEFT | wx.RIGHT, border=5)
221
222        section = wx.StaticBox(self, -1)
223        section_sizer = wx.StaticBoxSizer(section, wx.VERTICAL)
224        section_sizer.SetMinSize((_STATICBOX_WIDTH, -1))
225
226        input_grid = wx.GridBagSizer(5, 5)
227
228        q_label = wx.StaticText(self, -1, "Q Axis: ")
229        input_grid.Add(q_label, (0,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
230
231        self.q_input = wx.FilePickerCtrl(self, -1,
232            size=(_STATICBOX_WIDTH-80, -1),
233            message="Chose the Q Axis data file.")
234        input_grid.Add(self.q_input, (0,1), (1,1), wx.ALL, 5)
235
236        iq_label = wx.StaticText(self, -1, "Intensity Data: ")
237        input_grid.Add(iq_label, (1,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
238
239        self.iq_input = wx.FilePickerCtrl(self, -1,
240            size=(_STATICBOX_WIDTH-80, -1),
241            message="Chose the Intensity data file.")
242        input_grid.Add(self.iq_input, (1,1), (1,1), wx.ALL, 5)
243
244        output_label = wx.StaticText(self, -1, "Output File: ")
245        input_grid.Add(output_label, (2,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
246
247        self.output = wx.FilePickerCtrl(self, -1,
248            size=(_STATICBOX_WIDTH-80, -1),
249            message="Chose the Intensity data file.",
250            style=wx.FLP_SAVE | wx.FLP_OVERWRITE_PROMPT | wx.FLP_USE_TEXTCTRL,
251            wildcard="*.xml")
252        input_grid.Add(self.output, (2,1), (1,1), wx.ALL, 5)
253
254        convert_btn = wx.Button(self, wx.ID_OK, "Convert")
255        input_grid.Add(convert_btn, (3,0), (1,1), wx.ALL, 5)
256        convert_btn.Bind(wx.EVT_BUTTON, self.on_convert)
257
258        section_sizer.Add(input_grid)
259
260        vbox.Add(section_sizer, flag=wx.ALL, border=5)
261
262        metadata_section = wx.CollapsiblePane(self, -1, "Metadata",
263            size=(_STATICBOX_WIDTH+40, -1), style=wx.WS_EX_VALIDATE_RECURSIVELY)
264        metadata_pane = metadata_section.GetPane()
265        metadata_grid = wx.GridBagSizer(5, 5)
266
267        y = 0
268        for item in self.metadata.keys():
269            if item == 'detector': continue
270            label_txt = item.replace('_', ' ').capitalize()
271            label = wx.StaticText(metadata_pane, -1, label_txt,
272                style=wx.ALIGN_CENTER_VERTICAL)
273            input_box = wx.TextCtrl(metadata_pane, name=item,
274                size=(_STATICBOX_WIDTH-80, -1))
275            input_box.Bind(wx.EVT_TEXT, self.metadata_changed)
276            metadata_grid.Add(label, (y,0), (1,1),
277                wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
278            metadata_grid.Add(input_box, (y,1), (1,2), wx.EXPAND)
279            y += 1
280
281        detector_label = wx.StaticText(metadata_pane, -1,
282            "Detector:")
283        metadata_grid.Add(detector_label, (y, 0), (1,1), wx.ALL | wx.EXPAND, 5)
284        y += 1
285
286        name_label = wx.StaticText(metadata_pane, -1, "Name: ")
287        metadata_grid.Add(name_label, (y, 1), (1,1))
288
289        name_input = wx.TextCtrl(metadata_pane,
290            name="detector_name", size=(_STATICBOX_WIDTH-80-55, -1))
291        metadata_grid.Add(name_input, (y, 2), (1,1))
292        name_input.Bind(wx.EVT_TEXT, self.metadata_changed)
293        y += 1
294
295        distance_label = wx.StaticText(metadata_pane, -1,
296            "Distance (mm): ")
297        metadata_grid.Add(distance_label, (y, 1), (1,1))
298
299        distance_input = wx.TextCtrl(metadata_pane, -1,
300            name="detector_distance", size=(50,-1))
301        metadata_grid.Add(distance_input, (y,2), (1,1))
302        self.to_validate.append(distance_input)
303        distance_input.Bind(wx.EVT_TEXT, self.metadata_changed)
304        y += 1
305
306        offset_label = wx.StaticText(metadata_pane, -1,
307            "Offset (m): ")
308        metadata_grid.Add(offset_label, (y,1), (1,1))
309
310        offset_input = VectorInput(metadata_pane, "detector_offset",
311            callback=self.metadata_changed)
312        self.to_validate.append(offset_input)
313        metadata_grid.Add(offset_input.GetSizer(), (y,2), (1,1), wx.BOTTOM, 5)
314        y += 1
315
316        orientation_label = wx.StaticText(metadata_pane, -1,
317            u"Orientation (\xb0): ")
318        metadata_grid.Add(orientation_label, (y, 1), (1, 1))
319
320        orientation_input = VectorInput(metadata_pane, "detector_orientation",
321            callback=self.metadata_changed, z_enabled=True,
322            labels=["Roll: ", "Pitch: ", "Yaw: "])
323        self.to_validate.append(orientation_input)
324        metadata_grid.Add(orientation_input.GetSizer(),
325            (y,2), (1,1), wx.BOTTOM, 5)
326        y += 1
327
328        pixel_label = wx.StaticText(metadata_pane, -1, "Pixel Size (mm): ")
329        metadata_grid.Add(pixel_label, (y,1), (1,1))
330
331        pixel_input = VectorInput(metadata_pane, "detector_pixel_size",
332             callback=self.metadata_changed)
333        self.to_validate.append(pixel_input)
334        metadata_grid.Add(pixel_input.GetSizer(), (y,2), (1,1), wx.BOTTOM, 5)
335        y += 1
336
337        beam_label = wx.StaticText(metadata_pane, -1, "Beam Center (mm): ")
338        metadata_grid.Add(beam_label, (y,1), (1,1))
339        beam_input = VectorInput(metadata_pane, "detector_beam_center",
340            callback=self.metadata_changed)
341        self.to_validate.append(beam_input)
342        metadata_grid.Add(beam_input.GetSizer(), (y,2), (1,1), wx.BOTTOM, 5)
343        y += 1
344
345        slit_label = wx.StaticText(metadata_pane, -1, "Slit Length (mm): ")
346        metadata_grid.Add(slit_label, (y,1), (1,1))
347        slit_input = wx.TextCtrl(metadata_pane, -1, size=(50,-1),
348            name="detector_slit_length")
349        self.to_validate.append(slit_input)
350        slit_input.Bind(wx.EVT_TEXT, self.metadata_changed)
351        metadata_grid.Add(slit_input, (y,2), (1,1), wx.BOTTOM, 5)
352
353        metadata_pane.SetSizer(metadata_grid)
354
355        vbox.Add(metadata_section, proportion=0, flag=wx.ALL, border=5)
356
357        vbox.Fit(self)
358        self.SetSizer(vbox)
359
360class ConverterWindow(widget.CHILD_FRAME):
361
362    def __init__(self, parent=None, title='File Converter', base=None,
363        manager=None, size=(PANEL_SIZE * 1.05, PANEL_SIZE / 1.55),
364        *args, **kwargs):
365        kwargs['title'] = title
366        kwargs['size'] = size
367        widget.CHILD_FRAME.__init__(self, parent, *args, **kwargs)
368
369        self.manager = manager
370        self.panel = ConverterPanel(self, base=None)
371        self.Bind(wx.EVT_CLOSE, self.on_close)
372        self.SetPosition((wx.LEFT, PANEL_TOP))
373        self.Show(True)
374
375    def on_close(self, event):
376        if self.manager is not None:
377            self.manager.converter_frame = None
378        self.Destroy()
Note: See TracBrowser for help on using the repository browser.