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

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

Fix scroll bars not appearing when metadata section expanded

  • Property mode set to 100644
File size: 12.4 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.sasgui.perspectives.file_converter.bsl_loader import BSLLoader
20from sas.sascalc.dataloader.data_info import Detector
21from sas.sascalc.dataloader.data_info import Sample
22from sas.sascalc.dataloader.data_info import Vector
23
24# Panel size
25if sys.platform.count("win32") > 0:
26    PANEL_TOP = 0
27    _STATICBOX_WIDTH = 410
28    _BOX_WIDTH = 200
29    PANEL_SIZE = 480
30    FONT_VARIANT = 0
31else:
32    PANEL_TOP = 60
33    _STATICBOX_WIDTH = 430
34    _BOX_WIDTH = 200
35    PANEL_SIZE = 500
36    FONT_VARIANT = 1
37
38class ConverterPanel(ScrolledPanel, PanelBase):
39
40    def __init__(self, parent, base=None, *args, **kwargs):
41        ScrolledPanel.__init__(self, parent, *args, **kwargs)
42        PanelBase.__init__(self)
43        self.SetupScrolling()
44        self.SetWindowVariant(variant=FONT_VARIANT)
45
46        self.base = base
47        self.parent = parent
48        self.meta_frames = []
49
50        self.q_input = None
51        self.iq_input = None
52        self.output = None
53        self.data_type = "ascii"
54
55        self.metadata = {
56            'title': None,
57            'run': None,
58            'run_name': None,
59            'instrument': None,
60            'detector': [Detector()],
61            'sample': Sample()
62        }
63
64        self.metadata['detector'][0].name = ''
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            if self.data_type == 'ascii':
105                qdata = self.extract_data(self.q_input.GetPath())
106                iqdata = self.extract_data(self.iq_input.GetPath())
107            else: # self.data_type == 'bsl'
108                loader = BSLLoader(self.q_input.GetPath(),
109                    self.iq_input.GetPath())
110                bsl_data = loader.load_bsl_data()
111                qdata = bsl_data.q_axis.data[0]
112                iqdata = bsl_data.data_axis.data[0]
113        except Exception as ex:
114            msg = str(ex)
115            wx.PostEvent(self.parent.manager.parent,
116                StatusEvent(status=msg, info='error'))
117            return
118
119        output_path = self.output.GetPath()
120        data = Data1D(x=qdata, y=iqdata)
121        data.filename = output_path.split('\\')[-1]
122
123        if self.metadata['run'] is not None:
124            run = self.metadata['run']
125            run_name = self.metadata['run_name']
126
127            if not isinstance(run, list) and run is not None:
128                self.metadata['run'] = [run]
129            else:
130                run = run[0]
131
132            if not isinstance(run_name, dict):
133                if run_name is not None:
134                    self.metadata['run_name'] = { run: run_name }
135                else:
136                    self.metadata['run_name'] = {}
137            elif run_name != {}:
138                self.metadata['run_name'][run] = run_name.values()[0]
139        else:
140            self.metadata['run'] = []
141            self.metadata['run_name'] = {}
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        return True
165
166    def show_detector_window(self, event):
167        if self.meta_frames != []:
168            for frame in self.meta_frames:
169                frame.panel.on_close()
170        detector_frame = MetadataWindow(DetectorPanel,
171            parent=self.parent.manager.parent, manager=self,
172            metadata=self.metadata['detector'][0], title='Detector Metadata')
173        self.meta_frames.append(detector_frame)
174        self.parent.manager.put_icon(detector_frame)
175        detector_frame.Show(True)
176
177    def show_sample_window(self, event):
178        if self.meta_frames != []:
179            for frame in self.meta_frames:
180                frame.panel.on_close()
181        sample_frame = MetadataWindow(SamplePanel,
182            parent=self.parent.manager.parent, manager=self,
183            metadata=self.metadata['sample'], title='Sample Metadata')
184        self.meta_frames.append(sample_frame)
185        self.parent.manager.put_icon(sample_frame)
186        sample_frame.Show(True)
187
188    def on_collapsible_pane(self, event):
189        self.Freeze()
190        self.SetupScrolling()
191        self.parent.Layout()
192        self.Thaw()
193
194    def datatype_changed(self, event):
195        event.Skip()
196        dtype = event.GetEventObject().GetName()
197        self.data_type = dtype
198
199    def metadata_changed(self, event):
200        event.Skip()
201        textbox = event.GetEventObject()
202        attr = textbox.GetName()
203        value = textbox.GetValue().strip()
204
205        if value == '':
206            self.metadata[attr] = None
207        else:
208            self.metadata[attr] = value
209
210
211    def _do_layout(self):
212        vbox = wx.BoxSizer(wx.VERTICAL)
213
214        instructions = ("Select either 1 column ASCII files or BSL files "
215            "containing the Q Axis and Intensity data, chose where to save "
216            "the converted file, then click Convert to convert them to CanSAS "
217            "XML format. If required, metadata can also be input below.")
218        instruction_label = wx.StaticText(self, -1, instructions,
219            size=(_STATICBOX_WIDTH+40, -1))
220        instruction_label.Wrap(_STATICBOX_WIDTH+40)
221        vbox.Add(instruction_label, flag=wx.TOP | wx.LEFT | wx.RIGHT, border=5)
222
223        section = wx.StaticBox(self, -1)
224        section_sizer = wx.StaticBoxSizer(section, wx.VERTICAL)
225        section_sizer.SetMinSize((_STATICBOX_WIDTH, -1))
226
227        input_grid = wx.GridBagSizer(5, 5)
228
229        y = 0
230
231        q_label = wx.StaticText(self, -1, "Q Axis: ")
232        input_grid.Add(q_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
233
234        self.q_input = wx.FilePickerCtrl(self, -1,
235            size=(_STATICBOX_WIDTH-80, -1),
236            message="Chose the Q Axis data file.")
237        input_grid.Add(self.q_input, (y,1), (1,1), wx.ALL, 5)
238        y += 1
239
240        iq_label = wx.StaticText(self, -1, "Intensity Data: ")
241        input_grid.Add(iq_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
242
243        self.iq_input = wx.FilePickerCtrl(self, -1,
244            size=(_STATICBOX_WIDTH-80, -1),
245            message="Chose the Intensity data file.")
246        input_grid.Add(self.iq_input, (y,1), (1,1), wx.ALL, 5)
247        y += 1
248
249        data_type_label = wx.StaticText(self, -1, "Input Format: ")
250        input_grid.Add(data_type_label, (y,0), (1,1),
251            wx.ALIGN_CENTER_VERTICAL, 5)
252        radio_sizer = wx.BoxSizer(wx.HORIZONTAL)
253        ascii_btn = wx.RadioButton(self, -1, "ASCII", name="ascii",
254            style=wx.RB_GROUP)
255        ascii_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
256        radio_sizer.Add(ascii_btn)
257        bsl_btn = wx.RadioButton(self, -1, "BSL", name="bsl")
258        bsl_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
259        radio_sizer.Add(bsl_btn)
260        input_grid.Add(radio_sizer, (y,1), (1,1), wx.ALL, 5)
261        y += 1
262
263        output_label = wx.StaticText(self, -1, "Output File: ")
264        input_grid.Add(output_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
265
266        self.output = wx.FilePickerCtrl(self, -1,
267            size=(_STATICBOX_WIDTH-80, -1),
268            message="Chose the Intensity data file.",
269            style=wx.FLP_SAVE | wx.FLP_OVERWRITE_PROMPT | wx.FLP_USE_TEXTCTRL,
270            wildcard="*.xml")
271        input_grid.Add(self.output, (y,1), (1,1), wx.ALL, 5)
272        y += 1
273
274        convert_btn = wx.Button(self, wx.ID_OK, "Convert")
275        input_grid.Add(convert_btn, (y,0), (1,1), wx.ALL, 5)
276        convert_btn.Bind(wx.EVT_BUTTON, self.on_convert)
277
278        section_sizer.Add(input_grid)
279
280        vbox.Add(section_sizer, flag=wx.ALL, border=5)
281
282        metadata_section = wx.CollapsiblePane(self, -1, "Metadata",
283            size=(_STATICBOX_WIDTH+40, -1), style=wx.WS_EX_VALIDATE_RECURSIVELY)
284        metadata_pane = metadata_section.GetPane()
285        metadata_grid = wx.GridBagSizer(5, 5)
286
287        metadata_section.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED,
288            self.on_collapsible_pane)
289
290        y = 0
291        for item in self.metadata.keys():
292            if item == 'detector' or item == 'sample': continue
293            label_txt = item.replace('_', ' ').capitalize()
294            label = wx.StaticText(metadata_pane, -1, label_txt,
295                style=wx.ALIGN_CENTER_VERTICAL)
296            input_box = wx.TextCtrl(metadata_pane, name=item,
297                size=(_STATICBOX_WIDTH-80, -1))
298            input_box.Bind(wx.EVT_TEXT, self.metadata_changed)
299            metadata_grid.Add(label, (y,0), (1,1),
300                wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
301            metadata_grid.Add(input_box, (y,1), (1,2), wx.EXPAND)
302            y += 1
303
304        detector_label = wx.StaticText(metadata_pane, -1,
305            "Detector:")
306        metadata_grid.Add(detector_label, (y, 0), (1,1), wx.ALL | wx.EXPAND, 5)
307        detector_btn = wx.Button(metadata_pane, -1, "Enter Detector Metadata")
308        metadata_grid.Add(detector_btn, (y, 1), (1,1), wx.ALL | wx.EXPAND, 5)
309        detector_btn.Bind(wx.EVT_BUTTON, self.show_detector_window)
310        y += 1
311
312        sample_label = wx.StaticText(metadata_pane, -1, "Sample: ")
313        metadata_grid.Add(sample_label, (y,0), (1,1), wx.ALL | wx.EXPAND, 5)
314        sample_btn = wx.Button(metadata_pane, -1, "Enter Sample Metadata")
315        metadata_grid.Add(sample_btn, (y,1), (1,1), wx.ALL | wx.EXPAND, 5)
316        sample_btn.Bind(wx.EVT_BUTTON, self.show_sample_window)
317        y += 1
318
319        metadata_pane.SetSizer(metadata_grid)
320
321        vbox.Add(metadata_section, proportion=0, flag=wx.ALL, border=5)
322
323        vbox.Fit(self)
324        self.SetSizer(vbox)
325
326class ConverterWindow(widget.CHILD_FRAME):
327
328    def __init__(self, parent=None, title='File Converter', base=None,
329        manager=None, size=(PANEL_SIZE * 1.05, PANEL_SIZE / 1.25),
330        *args, **kwargs):
331        kwargs['title'] = title
332        kwargs['size'] = size
333        widget.CHILD_FRAME.__init__(self, parent, *args, **kwargs)
334
335        self.manager = manager
336        self.panel = ConverterPanel(self, base=None)
337        self.Bind(wx.EVT_CLOSE, self.on_close)
338        self.SetPosition((wx.LEFT, PANEL_TOP))
339        self.Show(True)
340
341    def on_close(self, event):
342        if self.manager is not None:
343            self.manager.converter_frame = None
344        self.Destroy()
Note: See TracBrowser for help on using the repository browser.