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
RevLine 
[77d92cd]1"""
2This module provides a GUI for the file converter
3"""
4
5import wx
6import sys
[a58706d]7import numpy as np
[77d92cd]8from wx.lib.scrolledpanel import ScrolledPanel
9from sas.sasgui.guiframe.panel_base import PanelBase
10from sas.sasgui.perspectives.calculator import calculator_widgets as widget
[c9a519f]11from sas.sasgui.perspectives.file_converter.converter_widgets import VectorInput
[a58706d]12from sas.sasgui.guiframe.events import StatusEvent
13from sas.sasgui.guiframe.dataFitting import Data1D
[fdbea3c]14from sas.sasgui.guiframe.utils import check_float
[a58706d]15from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader
[f2b3f28]16from sas.sascalc.dataloader.data_info import Detector
[fdbea3c]17from sas.sascalc.dataloader.data_info import Vector
[77d92cd]18
19# Panel size
20if sys.platform.count("win32") > 0:
21    PANEL_TOP = 0
22    _STATICBOX_WIDTH = 410
23    _BOX_WIDTH = 200
[36f4debb]24    PANEL_SIZE = 480
[77d92cd]25    FONT_VARIANT = 0
26else:
27    PANEL_TOP = 60
28    _STATICBOX_WIDTH = 430
29    _BOX_WIDTH = 200
[36f4debb]30    PANEL_SIZE = 500
[77d92cd]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)
[a58706d]38        self.SetupScrolling()
[77d92cd]39        self.SetWindowVariant(variant=FONT_VARIANT)
40
41        self.base = base
42        self.parent = parent
43
[a58706d]44        self.q_input = None
45        self.iq_input = None
46        self.output = None
[fdbea3c]47        self.to_validate = []
[a58706d]48
[f2b3f28]49        self.metadata = {
50            'title': None,
51            'run': None,
52            'run_name': None,
53            'instrument': None,
[fdbea3c]54            'detector': [Detector()]
[f2b3f28]55        }
[8b633dd]56        self.vectors = ['offset', 'orientation', 'pixel_size', 'beam_center']
[fdbea3c]57        for vector_name in self.vectors:
58            setattr(self.metadata['detector'][0], vector_name, Vector())
[f2b3f28]59
[a58706d]60        self._do_layout()
[77d92cd]61        self.SetAutoLayout(True)
62        self.Layout()
63
[a58706d]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
[ff790b3]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
[a58706d]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):
[fdbea3c]94        if not self.validate_inputs():
95            return
96
[a58706d]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
[f2b3f28]106        output_path = self.output.GetPath()
[a58706d]107        data = Data1D(x=qdata, y=iqdata)
[f2b3f28]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']
[3331e11]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'] = {}
[fdbea3c]124            else:
[3331e11]125                self.metadata['run_name'][run] = run_name.values()[0]
[fdbea3c]126        else:
127            self.metadata['run'] = []
128            self.metadata['run_name'] = {}
[3331e11]129        if self.metadata['detector'][0].name is None:
[fdbea3c]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)
[f2b3f28]142
143        for attr, value in self.metadata.iteritems():
[fdbea3c]144            if value is not None:
145                setattr(data, attr, value)
[f2b3f28]146
147        self.convert_to_cansas(data, output_path)
[a58706d]148        wx.PostEvent(self.parent.manager.parent,
149            StatusEvent(status="Conversion completed."))
150
[fdbea3c]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:
[c9a519f]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
[fdbea3c]173            if not ctrl_valid:
174                msg = "{} must be a valid float".format(
[c9a519f]175                    invalid_control.GetName().replace('_', ' '))
[fdbea3c]176                wx.PostEvent(self.parent.manager.parent,
177                    StatusEvent(status=msg, info='error'))
178                return False
[c9a519f]179
[fdbea3c]180        return True
181
182
[f2b3f28]183    def metadata_changed(self, event):
184        event.Skip()
185        textbox = event.GetEventObject()
186        attr = textbox.GetName()
187        value = textbox.GetValue().strip()
[fdbea3c]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
[f2b3f28]204        if value == '':
205            self.metadata[attr] = None
206        else:
207            self.metadata[attr] = value
208
209
[77d92cd]210    def _do_layout(self):
[f2b3f28]211        vbox = wx.BoxSizer(wx.VERTICAL)
[a58706d]212
213        instructions = ("Select the 1 column ASCII files containing the Q Axis"
[fdbea3c]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.")
[5afe77f]217        instruction_label = wx.StaticText(self, -1, instructions,
218            size=(_STATICBOX_WIDTH+40, -1))
219        instruction_label.Wrap(_STATICBOX_WIDTH+40)
[f2b3f28]220        vbox.Add(instruction_label, flag=wx.TOP | wx.LEFT | wx.RIGHT, border=5)
[77d92cd]221
[a58706d]222        section = wx.StaticBox(self, -1)
223        section_sizer = wx.StaticBoxSizer(section, wx.VERTICAL)
224        section_sizer.SetMinSize((_STATICBOX_WIDTH, -1))
[77d92cd]225
226        input_grid = wx.GridBagSizer(5, 5)
227
[a58706d]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
[fdbea3c]254        convert_btn = wx.Button(self, wx.ID_OK, "Convert")
[a58706d]255        input_grid.Add(convert_btn, (3,0), (1,1), wx.ALL, 5)
256        convert_btn.Bind(wx.EVT_BUTTON, self.on_convert)
[77d92cd]257
[a58706d]258        section_sizer.Add(input_grid)
[77d92cd]259
[f2b3f28]260        vbox.Add(section_sizer, flag=wx.ALL, border=5)
261
262        metadata_section = wx.CollapsiblePane(self, -1, "Metadata",
[fdbea3c]263            size=(_STATICBOX_WIDTH+40, -1), style=wx.WS_EX_VALIDATE_RECURSIVELY)
264        metadata_pane = metadata_section.GetPane()
[f2b3f28]265        metadata_grid = wx.GridBagSizer(5, 5)
266
267        y = 0
268        for item in self.metadata.keys():
[fdbea3c]269            if item == 'detector': continue
[36f4debb]270            label_txt = item.replace('_', ' ').capitalize()
[fdbea3c]271            label = wx.StaticText(metadata_pane, -1, label_txt,
[f2b3f28]272                style=wx.ALIGN_CENTER_VERTICAL)
[fdbea3c]273            input_box = wx.TextCtrl(metadata_pane, name=item,
[f2b3f28]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)
[fdbea3c]278            metadata_grid.Add(input_box, (y,1), (1,2), wx.EXPAND)
[f2b3f28]279            y += 1
280
[fdbea3c]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,
[8b633dd]296            "Distance (mm): ")
[fdbea3c]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
[c9a519f]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)
[fdbea3c]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
[c9a519f]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)
[fdbea3c]326        y += 1
327
[8b633dd]328        pixel_label = wx.StaticText(metadata_pane, -1, "Pixel Size (mm): ")
[fdbea3c]329        metadata_grid.Add(pixel_label, (y,1), (1,1))
330
[c9a519f]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)
[8b633dd]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)
[fdbea3c]352
353        metadata_pane.SetSizer(metadata_grid)
[f2b3f28]354
355        vbox.Add(metadata_section, proportion=0, flag=wx.ALL, border=5)
[77d92cd]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.