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

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

Set sample ID to SASentry.Title to agree with Mantid output

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