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

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

Add source metadata

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