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

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

Start adding sample metadata window

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