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

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

Abstract MetadataPanel? from DetectorPanel?

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