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

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

Fix things when a title isn't input

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