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

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

Begin implementing 2D BSL loader into GUI

  • Property mode set to 100644
File size: 19.6 KB
RevLine 
[77d92cd]1"""
2This module provides a GUI for the file converter
3"""
4
5import wx
6import sys
[a58706d]7import numpy as np
[77d92cd]8from wx.lib.scrolledpanel import ScrolledPanel
9from sas.sasgui.guiframe.panel_base import PanelBase
10from sas.sasgui.perspectives.calculator import calculator_widgets as widget
[c9a519f]11from sas.sasgui.perspectives.file_converter.converter_widgets import VectorInput
[de0df2c]12from sas.sasgui.perspectives.file_converter.meta_panels import MetadataWindow
13from sas.sasgui.perspectives.file_converter.meta_panels import DetectorPanel
[2a7722b]14from sas.sasgui.perspectives.file_converter.meta_panels import SamplePanel
[55bc56bc]15from sas.sasgui.perspectives.file_converter.meta_panels import SourcePanel
[eb8da5f]16from sas.sasgui.perspectives.file_converter.frame_select_dialog import FrameSelectDialog
[a58706d]17from sas.sasgui.guiframe.events import StatusEvent
[11794f2]18from sas.sasgui.guiframe.documentation_window import DocumentationWindow
[a58706d]19from sas.sasgui.guiframe.dataFitting import Data1D
[535e181]20from sas.sascalc.dataloader.data_info import Data2D
[fdbea3c]21from sas.sasgui.guiframe.utils import check_float
[8976865]22from sas.sasgui.perspectives.file_converter.cansas_writer import CansasWriter
[535e181]23from sas.sasgui.perspectives.file_converter.bsl_loader import BSLLoader as OTOKOLoader
24from sas.sascalc.file_converter.bsl_loader import BSLLoader
[f2b3f28]25from sas.sascalc.dataloader.data_info import Detector
[2a7722b]26from sas.sascalc.dataloader.data_info import Sample
[55bc56bc]27from sas.sascalc.dataloader.data_info import Source
[fdbea3c]28from sas.sascalc.dataloader.data_info import Vector
[77d92cd]29
30# Panel size
31if sys.platform.count("win32") > 0:
32    PANEL_TOP = 0
33    _STATICBOX_WIDTH = 410
34    _BOX_WIDTH = 200
[36f4debb]35    PANEL_SIZE = 480
[77d92cd]36    FONT_VARIANT = 0
37else:
38    PANEL_TOP = 60
39    _STATICBOX_WIDTH = 430
40    _BOX_WIDTH = 200
[36f4debb]41    PANEL_SIZE = 500
[77d92cd]42    FONT_VARIANT = 1
43
44class ConverterPanel(ScrolledPanel, PanelBase):
45
46    def __init__(self, parent, base=None, *args, **kwargs):
47        ScrolledPanel.__init__(self, parent, *args, **kwargs)
48        PanelBase.__init__(self)
[a58706d]49        self.SetupScrolling()
[77d92cd]50        self.SetWindowVariant(variant=FONT_VARIANT)
51
52        self.base = base
53        self.parent = parent
[de0df2c]54        self.meta_frames = []
[77d92cd]55
[a58706d]56        self.q_input = None
57        self.iq_input = None
58        self.output = None
[4b862c4]59        self.data_type = "ascii"
[a58706d]60
[503cc34]61        self.title = None
62        self.run = None
63        self.run_name = None
64        self.instrument = None
65        self.detector = Detector()
66        self.sample = Sample()
67        self.source = Source()
68        self.properties = ['title', 'run', 'run_name', 'instrument']
69
70        self.detector.name = ''
71        self.source.radiation = 'neutron'
[4b862c4]72
[a58706d]73        self._do_layout()
[77d92cd]74        self.SetAutoLayout(True)
75        self.Layout()
76
[94f4518]77    def convert_to_cansas(self, frame_data, filename, single_file):
[8976865]78        reader = CansasWriter()
[ea2a7348]79        entry_attrs = None
80        if self.run_name is not None:
81            entry_attrs = { 'name': self.run_name }
[94f4518]82        if single_file:
83            reader.write(filename, frame_data,
84                sasentry_attrs=entry_attrs)
85        else:
86            # strip extension from filename
87            ext = "." + filename.split('.')[-1]
88            name = filename.replace(ext, '')
89            for i in range(len(frame_data)):
90                f_name = "{}{}{}".format(name, i+1, ext)
91                reader.write(f_name, [frame_data[i]],
92                    sasentry_attrs=entry_attrs)
[a58706d]93
[535e181]94    def extract_ascii_data(self, filename):
[a58706d]95        data = np.loadtxt(filename, dtype=str)
96
[ff790b3]97        if len(data.shape) != 1:
98            msg = "Error reading {}: Only one column of data is allowed"
99            raise Exception(msg.format(filename.split('\\')[-1]))
100
[a58706d]101        is_float = True
102        try:
103            float(data[0])
104        except:
105            is_float = False
106
107        if not is_float:
108            end_char = data[0][-1]
109            # If lines end with comma or semi-colon, trim the last character
110            if end_char == ',' or end_char == ';':
111                data = map(lambda s: s[0:-1], data)
112            else:
113                msg = ("Error reading {}: Lines must end with a digit, comma "
114                    "or semi-colon").format(filename.split('\\')[-1])
115                raise Exception(msg)
116
117        return np.array(data, dtype=np.float32)
118
[535e181]119    def extract_otoko_data(self, filename):
120        loader = OTOKOLoader(self.q_input.GetPath(),
121            self.iq_input.GetPath())
122        bsl_data = loader.load_bsl_data()
123        qdata = bsl_data.q_axis.data
124        iqdata = bsl_data.data_axis.data
125        if len(qdata) > 1:
126            msg = ("Q-Axis file has multiple frames. Only 1 frame is "
127                "allowed for the Q-Axis")
128            wx.PostEvent(self.parent.manager.parent,
129                StatusEvent(status=msg, info="error"))
130            return
131        else:
132            qdata = qdata[0]
133
134        return qdata, iqdata
135
[eb8da5f]136    def ask_frame_range(self, n_frames):
137        valid_input = False
138        dlg = FrameSelectDialog(n_frames)
139        frames = None
140        increment = None
[94f4518]141        single_file = True
[eb8da5f]142        while not valid_input:
143            if dlg.ShowModal() == wx.ID_OK:
144                msg = ""
145                try:
146                    first_frame = int(dlg.first_input.GetValue())
147                    last_frame = int(dlg.last_input.GetValue())
148                    increment = int(dlg.increment_input.GetValue())
[94f4518]149                    single_file = dlg.single_btn.GetValue()
[eb8da5f]150                    if last_frame < 0 or first_frame < 0:
151                        msg = "Frame values must be positive"
152                    elif increment < 1:
153                        msg = "Increment must be greater than or equal to 1"
154                    elif first_frame > last_frame:
155                        msg = "First frame must be less than last frame"
156                    elif last_frame > n_frames:
157                        msg = "Last frame must be less than {}".format(n_frames)
158                    else:
159                        valid_input = True
160                except:
161                    valid_input = False
162                    msg = "Please enter valid integer values"
163
164                if not valid_input:
165                    wx.PostEvent(self.parent.manager.parent,
166                        StatusEvent(status=msg))
167            else:
[94f4518]168                return { 'frames': [], 'inc': None, 'file': single_file }
[eb8da5f]169        frames = range(first_frame, last_frame + increment,
170            increment)
[94f4518]171        return { 'frames': frames, 'inc': increment, 'file': single_file }
[eb8da5f]172
[a58706d]173    def on_convert(self, event):
[fdbea3c]174        if not self.validate_inputs():
175            return
176
[503cc34]177        self.sample.ID = self.title
[3ea9371]178
[a58706d]179        try:
[4b862c4]180            if self.data_type == 'ascii':
[535e181]181                qdata = self.extract_ascii_data(self.q_input.GetPath())
182                iqdata = np.array([self.extract_ascii_data(self.iq_input.GetPath())])
183            elif self.data_type == 'otoko':
184                qdata, iqdata = self.extract_otoko_data(self.q_input.GetPath())
[4b862c4]185            else: # self.data_type == 'bsl'
[535e181]186                loader = BSLLoader(self.iq_input.GetPath())
187                frames = [0]
188                if loader.n_frames > 1:
189                    params = self.ask_frame_range(loader.n_frames)
[94f4518]190                    frames = params['frames']
[535e181]191                data = []
192                for frame in frames:
193                    loader.frame = frame
194                    data.append(loader.load_data())
195                data = data[0]
196                data_x = []
197                data_y = range(loader.n_pixels) * loader.n_rasters
198                data_i = data.reshape((loader.n_pixels*loader.n_rasters,1))
199                for i in range(loader.n_rasters):
200                    data_x += [i] * loader.n_pixels
201                import pdb; pdb.set_trace()
202                data_info = Data2D(data=data_i, qx_data=data_x, qy_data=data_y)
203                from sas.sascalc.dataloader.readers.red2d_reader import Reader as Writer2D
204                writer = Writer2D()
205                writer.write(self.output.GetPath(), data_info)
206                wx.PostEvent(self.parent.manager.parent,
207                    StatusEvent(status="Conversion completed."))
208                return
209
[a58706d]210        except Exception as ex:
211            msg = str(ex)
212            wx.PostEvent(self.parent.manager.parent,
213                StatusEvent(status=msg, info='error'))
214            return
215
[535e181]216        frames = []
217        increment = 1
218        single_file = True
219        # Standard file has 3 frames: SAS, calibration and WAS
220        if iqdata.shape[0] > 3:
221            # File has multiple frames
222            params = self.ask_frame_range(frames[0])
223            frames = params['frames']
224            increment = params['inc']
225            single_file = params['file']
226            if frames == []: return
227        else: # Only interested in SAS data
228            frames = [0]
229
[f2b3f28]230        output_path = self.output.GetPath()
231
[d112c30]232        if self.run is None:
[503cc34]233            self.run = []
[d112c30]234        elif not isinstance(self.run, list):
235            self.run = [self.run]
[503cc34]236
[ea2a7348]237        if self.title is None:
238            self.title = ''
239
[503cc34]240        metadata = {
241            'title': self.title,
242            'run': self.run,
243            'intrument': self.instrument,
244            'detector': [self.detector],
245            'sample': self.sample,
246            'source': self.source
247        }
[f2b3f28]248
[eb8da5f]249        frame_data = []
250        for i in frames:
251            data = Data1D(x=qdata, y=iqdata[i])
252            frame_data.append(data)
[94f4518]253        if single_file:
254            # Only need to set metadata on first Data1D object
255            frame_data[0].filename = output_path.split('\\')[-1]
256            for key, value in metadata.iteritems():
257                setattr(frame_data[0], key, value)
258        else:
259            # Need to set metadata for all Data1D objects
260            for datainfo in frame_data:
261                datainfo.filename = output_path.split('\\')[-1]
262                for key, value in metadata.iteritems():
263                    setattr(datainfo, key, value)
264
265
266        self.convert_to_cansas(frame_data, output_path, single_file)
[a58706d]267        wx.PostEvent(self.parent.manager.parent,
268            StatusEvent(status="Conversion completed."))
269
[11794f2]270    def on_help(self, event):
271        tree_location = ("user/sasgui/perspectives/file_converter/"
272            "file_converter_help.html")
273        doc_viewer = DocumentationWindow(self, -1, tree_location,
274            "", "File Converter Help")
275
[fdbea3c]276    def validate_inputs(self):
277        msg = "You must select a"
[535e181]278        if self.q_input.GetPath() == '' and self.data_type != 'bsl':
[fdbea3c]279            msg += " Q Axis input file."
280        elif self.iq_input.GetPath() == '':
281            msg += "n Intensity input file."
282        elif self.output.GetPath() == '':
283            msg += "destination for the converted file."
284        if msg != "You must select a":
285            wx.PostEvent(self.parent.manager.parent,
286                StatusEvent(status=msg, info='error'))
287            return
288
289        return True
290
[af84162]291    def show_detector_window(self, event):
[de0df2c]292        if self.meta_frames != []:
293            for frame in self.meta_frames:
294                frame.panel.on_close()
295        detector_frame = MetadataWindow(DetectorPanel,
296            parent=self.parent.manager.parent, manager=self,
[503cc34]297            metadata=self.detector, title='Detector Metadata')
[de0df2c]298        self.meta_frames.append(detector_frame)
299        self.parent.manager.put_icon(detector_frame)
300        detector_frame.Show(True)
[fdbea3c]301
[2a7722b]302    def show_sample_window(self, event):
303        if self.meta_frames != []:
304            for frame in self.meta_frames:
305                frame.panel.on_close()
306        sample_frame = MetadataWindow(SamplePanel,
307            parent=self.parent.manager.parent, manager=self,
[503cc34]308            metadata=self.sample, title='Sample Metadata')
[2a7722b]309        self.meta_frames.append(sample_frame)
310        self.parent.manager.put_icon(sample_frame)
311        sample_frame.Show(True)
312
[55bc56bc]313    def show_source_window(self, event):
314        if self.meta_frames != []:
315            for frame in self.meta_frames:
316                frame.panel.on_close()
317        source_frame = MetadataWindow(SourcePanel,
318            parent=self.parent.manager.parent, manager=self,
[503cc34]319            metadata=self.source, title="Source Metadata")
[55bc56bc]320        self.meta_frames.append(source_frame)
321        self.parent.manager.put_icon(source_frame)
322        source_frame.Show(True)
323
[a027549]324    def on_collapsible_pane(self, event):
325        self.Freeze()
326        self.SetupScrolling()
327        self.parent.Layout()
328        self.Thaw()
329
[4b862c4]330    def datatype_changed(self, event):
331        event.Skip()
332        dtype = event.GetEventObject().GetName()
333        self.data_type = dtype
[535e181]334        if dtype == 'bsl':
335            self.q_input.Disable()
336        else:
337            self.q_input.Enable()
[4b862c4]338
[55775e8]339    def radiationtype_changed(self, event):
340        event.Skip()
341        rtype = event.GetEventObject().GetValue().lower()
[503cc34]342        self.source.radiation = rtype
[55775e8]343
[f2b3f28]344    def metadata_changed(self, event):
345        event.Skip()
346        textbox = event.GetEventObject()
347        attr = textbox.GetName()
348        value = textbox.GetValue().strip()
[fdbea3c]349
[503cc34]350        if value == '': value = None
351
352        setattr(self, attr, value)
[f2b3f28]353
354
[77d92cd]355    def _do_layout(self):
[f2b3f28]356        vbox = wx.BoxSizer(wx.VERTICAL)
[a58706d]357
[13b9a63]358        instructions = ("Select either single column ASCII files or BSL/OTOKO"
359            " files containing the Q-Axis and Intensity-axis data, chose where"
360            " to save the converted file, then click Convert to convert them "
361            "to CanSAS XML format. If required, metadata can also be input "
362            "below.")
[5afe77f]363        instruction_label = wx.StaticText(self, -1, instructions,
364            size=(_STATICBOX_WIDTH+40, -1))
365        instruction_label.Wrap(_STATICBOX_WIDTH+40)
[f2b3f28]366        vbox.Add(instruction_label, flag=wx.TOP | wx.LEFT | wx.RIGHT, border=5)
[77d92cd]367
[a58706d]368        section = wx.StaticBox(self, -1)
369        section_sizer = wx.StaticBoxSizer(section, wx.VERTICAL)
370        section_sizer.SetMinSize((_STATICBOX_WIDTH, -1))
[77d92cd]371
372        input_grid = wx.GridBagSizer(5, 5)
373
[4b862c4]374        y = 0
375
[13b9a63]376        q_label = wx.StaticText(self, -1, "Q-Axis Data: ")
[4b862c4]377        input_grid.Add(q_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
[a58706d]378
379        self.q_input = wx.FilePickerCtrl(self, -1,
380            size=(_STATICBOX_WIDTH-80, -1),
[13b9a63]381            message="Chose the Q-Axis data file.")
[4b862c4]382        input_grid.Add(self.q_input, (y,1), (1,1), wx.ALL, 5)
383        y += 1
[a58706d]384
[13b9a63]385        iq_label = wx.StaticText(self, -1, "Intensity-Axis Data: ")
[4b862c4]386        input_grid.Add(iq_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
[a58706d]387
388        self.iq_input = wx.FilePickerCtrl(self, -1,
389            size=(_STATICBOX_WIDTH-80, -1),
[13b9a63]390            message="Chose the Intensity-Axis data file.")
[4b862c4]391        input_grid.Add(self.iq_input, (y,1), (1,1), wx.ALL, 5)
392        y += 1
393
394        data_type_label = wx.StaticText(self, -1, "Input Format: ")
395        input_grid.Add(data_type_label, (y,0), (1,1),
396            wx.ALIGN_CENTER_VERTICAL, 5)
397        radio_sizer = wx.BoxSizer(wx.HORIZONTAL)
398        ascii_btn = wx.RadioButton(self, -1, "ASCII", name="ascii",
399            style=wx.RB_GROUP)
400        ascii_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
401        radio_sizer.Add(ascii_btn)
[535e181]402        otoko_btn = wx.RadioButton(self, -1, "OTOKO 1D", name="otoko")
403        otoko_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
404        radio_sizer.Add(otoko_btn)
405        input_grid.Add(radio_sizer, (y,1), (1,1), wx.ALL, 5)
406        bsl_btn = wx.RadioButton(self, -1, "BSL 2D", name="bsl")
[4b862c4]407        bsl_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
408        radio_sizer.Add(bsl_btn)
409        y += 1
[a58706d]410
[55775e8]411        radiation_label = wx.StaticText(self, -1, "Radiation Type: ")
412        input_grid.Add(radiation_label, (y,0), (1,1), wx.ALL, 5)
413        radiation_input = wx.ComboBox(self, -1,
414            choices=["Neutron", "X-Ray", "Muon", "Electron"],
415            name="radiation", style=wx.CB_READONLY, value="Neutron")
416        radiation_input.Bind(wx.EVT_COMBOBOX, self.radiationtype_changed)
417        input_grid.Add(radiation_input, (y,1), (1,1))
418        y += 1
419
[a58706d]420        output_label = wx.StaticText(self, -1, "Output File: ")
[4b862c4]421        input_grid.Add(output_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
[a58706d]422
423        self.output = wx.FilePickerCtrl(self, -1,
424            size=(_STATICBOX_WIDTH-80, -1),
[55775e8]425            message="Chose where to save the output file.",
[a58706d]426            style=wx.FLP_SAVE | wx.FLP_OVERWRITE_PROMPT | wx.FLP_USE_TEXTCTRL,
427            wildcard="*.xml")
[4b862c4]428        input_grid.Add(self.output, (y,1), (1,1), wx.ALL, 5)
429        y += 1
[a58706d]430
[fdbea3c]431        convert_btn = wx.Button(self, wx.ID_OK, "Convert")
[4b862c4]432        input_grid.Add(convert_btn, (y,0), (1,1), wx.ALL, 5)
[a58706d]433        convert_btn.Bind(wx.EVT_BUTTON, self.on_convert)
[77d92cd]434
[11794f2]435        help_btn = wx.Button(self, -1, "HELP")
436        input_grid.Add(help_btn, (y,1), (1,1), wx.ALL, 5)
437        help_btn.Bind(wx.EVT_BUTTON, self.on_help)
438
[a58706d]439        section_sizer.Add(input_grid)
[77d92cd]440
[f2b3f28]441        vbox.Add(section_sizer, flag=wx.ALL, border=5)
442
443        metadata_section = wx.CollapsiblePane(self, -1, "Metadata",
[fdbea3c]444            size=(_STATICBOX_WIDTH+40, -1), style=wx.WS_EX_VALIDATE_RECURSIVELY)
445        metadata_pane = metadata_section.GetPane()
[f2b3f28]446        metadata_grid = wx.GridBagSizer(5, 5)
447
[a027549]448        metadata_section.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED,
449            self.on_collapsible_pane)
450
[f2b3f28]451        y = 0
[503cc34]452        for item in self.properties:
[7c8ddb83]453            # Capitalise each word
454            label_txt = " ".join(
455                [s.capitalize() for s in item.replace('_', ' ').split(' ')])
456            if item == 'run':
457                label_txt = "Run Number"
[fdbea3c]458            label = wx.StaticText(metadata_pane, -1, label_txt,
[f2b3f28]459                style=wx.ALIGN_CENTER_VERTICAL)
[fdbea3c]460            input_box = wx.TextCtrl(metadata_pane, name=item,
[f2b3f28]461                size=(_STATICBOX_WIDTH-80, -1))
462            input_box.Bind(wx.EVT_TEXT, self.metadata_changed)
463            metadata_grid.Add(label, (y,0), (1,1),
464                wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
[fdbea3c]465            metadata_grid.Add(input_box, (y,1), (1,2), wx.EXPAND)
[f2b3f28]466            y += 1
467
[fdbea3c]468        detector_label = wx.StaticText(metadata_pane, -1,
469            "Detector:")
470        metadata_grid.Add(detector_label, (y, 0), (1,1), wx.ALL | wx.EXPAND, 5)
[af84162]471        detector_btn = wx.Button(metadata_pane, -1, "Enter Detector Metadata")
472        metadata_grid.Add(detector_btn, (y, 1), (1,1), wx.ALL | wx.EXPAND, 5)
473        detector_btn.Bind(wx.EVT_BUTTON, self.show_detector_window)
[fdbea3c]474        y += 1
475
[2a7722b]476        sample_label = wx.StaticText(metadata_pane, -1, "Sample: ")
477        metadata_grid.Add(sample_label, (y,0), (1,1), wx.ALL | wx.EXPAND, 5)
478        sample_btn = wx.Button(metadata_pane, -1, "Enter Sample Metadata")
479        metadata_grid.Add(sample_btn, (y,1), (1,1), wx.ALL | wx.EXPAND, 5)
480        sample_btn.Bind(wx.EVT_BUTTON, self.show_sample_window)
481        y += 1
482
[55bc56bc]483        source_label = wx.StaticText(metadata_pane, -1, "Source: ")
484        metadata_grid.Add(source_label, (y,0), (1,1), wx.ALL | wx.EXPAND, 5)
485        source_btn = wx.Button(metadata_pane, -1, "Enter Source Metadata")
486        source_btn.Bind(wx.EVT_BUTTON, self.show_source_window)
487        metadata_grid.Add(source_btn, (y,1), (1,1), wx.ALL | wx.EXPAND, 5)
488        y += 1
489
[fdbea3c]490        metadata_pane.SetSizer(metadata_grid)
[f2b3f28]491
492        vbox.Add(metadata_section, proportion=0, flag=wx.ALL, border=5)
[77d92cd]493
494        vbox.Fit(self)
495        self.SetSizer(vbox)
496
497class ConverterWindow(widget.CHILD_FRAME):
498
499    def __init__(self, parent=None, title='File Converter', base=None,
[a027549]500        manager=None, size=(PANEL_SIZE * 1.05, PANEL_SIZE / 1.25),
[77d92cd]501        *args, **kwargs):
502        kwargs['title'] = title
503        kwargs['size'] = size
504        widget.CHILD_FRAME.__init__(self, parent, *args, **kwargs)
505
506        self.manager = manager
507        self.panel = ConverterPanel(self, base=None)
508        self.Bind(wx.EVT_CLOSE, self.on_close)
509        self.SetPosition((wx.LEFT, PANEL_TOP))
510        self.Show(True)
511
512    def on_close(self, event):
513        if self.manager is not None:
514            self.manager.converter_frame = None
515        self.Destroy()
Note: See TracBrowser for help on using the repository browser.