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

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

Add ability to input detector metadata

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