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

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

Only perform writing on separate thread

Also move reader/writer classes to sascalc

  • Property mode set to 100644
File size: 26.0 KB
RevLine 
[77d92cd]1"""
2This module provides a GUI for the file converter
3"""
4
5import wx
6import sys
[05595c4]7import os
[a58706d]8import numpy as np
[77d92cd]9from wx.lib.scrolledpanel import ScrolledPanel
10from sas.sasgui.guiframe.panel_base import PanelBase
11from sas.sasgui.perspectives.calculator import calculator_widgets as widget
[0e11ec7]12from sas.sasgui.perspectives.file_converter.converter_widgets import FileInput
[de0df2c]13from sas.sasgui.perspectives.file_converter.meta_panels import MetadataWindow
14from sas.sasgui.perspectives.file_converter.meta_panels import DetectorPanel
[2a7722b]15from sas.sasgui.perspectives.file_converter.meta_panels import SamplePanel
[55bc56bc]16from sas.sasgui.perspectives.file_converter.meta_panels import SourcePanel
[eb8da5f]17from sas.sasgui.perspectives.file_converter.frame_select_dialog import FrameSelectDialog
[a58706d]18from sas.sasgui.guiframe.events import StatusEvent
[11794f2]19from sas.sasgui.guiframe.documentation_window import DocumentationWindow
[a58706d]20from sas.sasgui.guiframe.dataFitting import Data1D
[fdbea3c]21from sas.sasgui.guiframe.utils import check_float
[0b1a677]22from sas.sascalc.file_converter.cansas_writer import CansasWriter
23from sas.sascalc.file_converter.otoko_loader import OTOKOLoader
24from sas.sascalc.file_converter.bsl_loader import BSLLoader
[35488b2]25from sas.sascalc.file_converter.convert_bsl_thread import ConvertBSLThread
[f2b3f28]26from sas.sascalc.dataloader.data_info import Detector
[2a7722b]27from sas.sascalc.dataloader.data_info import Sample
[55bc56bc]28from sas.sascalc.dataloader.data_info import Source
[fdbea3c]29from sas.sascalc.dataloader.data_info import Vector
[77d92cd]30
31# Panel size
32if sys.platform.count("win32") > 0:
33    PANEL_TOP = 0
34    _STATICBOX_WIDTH = 410
35    _BOX_WIDTH = 200
[36f4debb]36    PANEL_SIZE = 480
[77d92cd]37    FONT_VARIANT = 0
38else:
39    PANEL_TOP = 60
40    _STATICBOX_WIDTH = 430
41    _BOX_WIDTH = 200
[36f4debb]42    PANEL_SIZE = 500
[77d92cd]43    FONT_VARIANT = 1
44
45class ConverterPanel(ScrolledPanel, PanelBase):
[ba65aff]46    """
47    This class provides the File Converter GUI
48    """
[77d92cd]49
50    def __init__(self, parent, base=None, *args, **kwargs):
51        ScrolledPanel.__init__(self, parent, *args, **kwargs)
52        PanelBase.__init__(self)
[a58706d]53        self.SetupScrolling()
[77d92cd]54        self.SetWindowVariant(variant=FONT_VARIANT)
55
56        self.base = base
57        self.parent = parent
[de0df2c]58        self.meta_frames = []
[0b1a677]59        self.to_convert = {}
[35488b2]60        self.bsl_thread = None
[77d92cd]61
[ba65aff]62        # GUI inputs
[a58706d]63        self.q_input = None
64        self.iq_input = None
65        self.output = None
[d6bf064]66        self.radiation_input = None
[35488b2]67        self.convert_btn = None
[d6bf064]68        self.metadata_section = None
[ba65aff]69
[4b862c4]70        self.data_type = "ascii"
[a58706d]71
[ba65aff]72        # Metadata values
[94667e27]73        self.title = ''
74        self.run = ''
75        self.run_name = ''
76        self.instrument = ''
[503cc34]77        self.detector = Detector()
78        self.sample = Sample()
79        self.source = Source()
80        self.properties = ['title', 'run', 'run_name', 'instrument']
81
82        self.detector.name = ''
83        self.source.radiation = 'neutron'
[4b862c4]84
[a58706d]85        self._do_layout()
[77d92cd]86        self.SetAutoLayout(True)
87        self.Layout()
88
[9e9f848]89    def convert_to_cansas(self, frame_data, filepath, single_file):
[ba65aff]90        """
91        Saves an array of Data1D objects to a single CanSAS file with multiple
92        <SasData> elements, or to multiple CanSAS files, each with one
93        <SasData> element.
94
[9e9f848]95        :param frame_data: If single_file is true, an array of Data1D objects.
96        If single_file is false, a dictionary of the form frame_number: Data1D.
97        :param filepath: Where to save the CanSAS file
[ba65aff]98        :param single_file: If true, array is saved as a single file, if false,
99        each item in the array is saved to it's own file
100        """
101        writer = CansasWriter()
[ea2a7348]102        entry_attrs = None
[94667e27]103        if self.run_name != '':
[ea2a7348]104            entry_attrs = { 'name': self.run_name }
[5451a78]105
[94f4518]106        if single_file:
[9e9f848]107            writer.write(filepath, frame_data,
[94f4518]108                sasentry_attrs=entry_attrs)
109        else:
[9e9f848]110            # Folder and base filename
111            [group_path, group_name] = os.path.split(filepath)
112            ext = "." + group_name.split('.')[-1] # File extension
113            for frame_number, frame_data in frame_data.iteritems():
114                # Append frame number to base filename
115                filename = group_name.replace(ext, str(frame_number)+ext)
116                destination = os.path.join(group_path, filename)
117                writer.write(destination, [frame_data],
[94f4518]118                    sasentry_attrs=entry_attrs)
[a58706d]119
[535e181]120    def extract_ascii_data(self, filename):
[ba65aff]121        """
122        Extracts data from a single-column ASCII file
123
124        :param filename: The file to load data from
125        :return: A numpy array containing the extracted data
126        """
[514a00e]127        try:
128            data = np.loadtxt(filename, dtype=str)
129        except:
130            is_bsl = False
131            # Check if file is a BSL or OTOKO header file
132            f = open(filename, 'r')
133            f.readline()
134            f.readline()
135            bsl_metadata = f.readline().strip().split()
136            f.close()
137            if len(bsl_metadata) == 10:
138                msg = ("Error parsing ASII data. {} looks like a BSL or OTOKO "
139                    "header file.")
140                raise Exception(msg.format(os.path.split(filename)[-1]))
[a58706d]141
[ff790b3]142        if len(data.shape) != 1:
143            msg = "Error reading {}: Only one column of data is allowed"
144            raise Exception(msg.format(filename.split('\\')[-1]))
145
[a58706d]146        is_float = True
147        try:
148            float(data[0])
149        except:
150            is_float = False
151
152        if not is_float:
153            end_char = data[0][-1]
154            # If lines end with comma or semi-colon, trim the last character
155            if end_char == ',' or end_char == ';':
156                data = map(lambda s: s[0:-1], data)
157            else:
158                msg = ("Error reading {}: Lines must end with a digit, comma "
159                    "or semi-colon").format(filename.split('\\')[-1])
160                raise Exception(msg)
161
162        return np.array(data, dtype=np.float32)
163
[535e181]164    def extract_otoko_data(self, filename):
[ba65aff]165        """
166        Extracts data from a 1D OTOKO file
167
168        :param filename: The OTOKO file to load the data from
169        :return: A numpy array containing the extracted data
170        """
[535e181]171        loader = OTOKOLoader(self.q_input.GetPath(),
172            self.iq_input.GetPath())
[9c500ab]173        otoko_data = loader.load_otoko_data()
174        qdata = otoko_data.q_axis.data
175        iqdata = otoko_data.data_axis.data
[535e181]176        if len(qdata) > 1:
177            msg = ("Q-Axis file has multiple frames. Only 1 frame is "
178                "allowed for the Q-Axis")
179            wx.PostEvent(self.parent.manager.parent,
180                StatusEvent(status=msg, info="error"))
181            return
182        else:
183            qdata = qdata[0]
184
185        return qdata, iqdata
186
[0b1a677]187    def extract_bsl_data(self, filename):
188        """
189        Extracts data from a 2D BSL file
190
191        :param filename: The header file to extract the data from
192        :return x_data: A 1D array containing all the x coordinates of the data
193        :return y_data: A 1D array containing all the y coordinates of the data
194        :return frame_data: A dictionary of the form frame_number: data, where
195        data is a 2D numpy array containing the intensity data
196        """
197        loader = BSLLoader(filename)
198        frames = [0]
199        should_continue = True
200
201        if loader.n_frames > 1:
202            params = self.ask_frame_range(loader.n_frames)
203            frames = params['frames']
204            if len(frames) == 0:
205                should_continue = False
206        elif loader.n_rasters == 1 and loader.n_frames == 1:
207            message = ("The selected file is an OTOKO file. Please select the "
208            "'OTOKO 1D' option if you wish to convert it.")
209            dlg = wx.MessageDialog(self,
210            message,
211            'Error!',
212            wx.OK | wx.ICON_WARNING)
213            dlg.ShowModal()
214            should_continue = False
215            dlg.Destroy()
216        else:
217            message = ("The selected data file only has 1 frame, it might be"
218                " a multi-frame OTOKO file.\nContinue conversion?")
219            dlg = wx.MessageDialog(self,
220            message,
221            'Warning!',
222            wx.YES_NO | wx.ICON_WARNING)
223            should_continue = (dlg.ShowModal() == wx.ID_YES)
224            dlg.Destroy()
225
226        if not should_continue:
227            return None, None, None
228
229        frame_data = {}
230
231        for frame in frames:
232            loader.frame = frame
233            frame_data[frame] = loader.load_data()
234
235        # TODO: Tidy this up
236        # Prepare axes values (arbitrary scale)
237        x_data = []
238        y_data = range(loader.n_pixels) * loader.n_rasters
239        for i in range(loader.n_rasters):
240            x_data += [i] * loader.n_pixels
241
242        return x_data, y_data, frame_data
243
[eb8da5f]244    def ask_frame_range(self, n_frames):
[ba65aff]245        """
246        Display a dialog asking the user to input the range of frames they
247        would like to export
248
249        :param n_frames: How many frames the loaded data file has
250        :return: A dictionary containing the parameters input by the user
251        """
[eb8da5f]252        valid_input = False
[05595c4]253        is_bsl = (self.data_type == 'bsl')
254        dlg = FrameSelectDialog(n_frames, is_bsl)
[eb8da5f]255        frames = None
256        increment = None
[94f4518]257        single_file = True
[eb8da5f]258        while not valid_input:
259            if dlg.ShowModal() == wx.ID_OK:
260                msg = ""
261                try:
262                    first_frame = int(dlg.first_input.GetValue())
263                    last_frame = int(dlg.last_input.GetValue())
264                    increment = int(dlg.increment_input.GetValue())
[05595c4]265                    if not is_bsl:
266                        single_file = dlg.single_btn.GetValue()
267
[eb8da5f]268                    if last_frame < 0 or first_frame < 0:
269                        msg = "Frame values must be positive"
270                    elif increment < 1:
271                        msg = "Increment must be greater than or equal to 1"
272                    elif first_frame > last_frame:
273                        msg = "First frame must be less than last frame"
[05595c4]274                    elif last_frame >= n_frames:
[eb8da5f]275                        msg = "Last frame must be less than {}".format(n_frames)
276                    else:
277                        valid_input = True
278                except:
279                    valid_input = False
280                    msg = "Please enter valid integer values"
281
282                if not valid_input:
283                    wx.PostEvent(self.parent.manager.parent,
284                        StatusEvent(status=msg))
285            else:
[94f4518]286                return { 'frames': [], 'inc': None, 'file': single_file }
[eb8da5f]287        frames = range(first_frame, last_frame + increment,
288            increment)
[94f4518]289        return { 'frames': frames, 'inc': increment, 'file': single_file }
[eb8da5f]290
[a58706d]291    def on_convert(self, event):
[ba65aff]292        """Called when the Convert button is clicked"""
[fdbea3c]293        if not self.validate_inputs():
294            return
295
[35488b2]296        if self.bsl_thread is not None and self.bsl_thread.isrunning():
[0b1a677]297            self.to_convert = {}
[35488b2]298            self.bsl_thread.stop()
299            return
300
[503cc34]301        self.sample.ID = self.title
[3ea9371]302
[a58706d]303        try:
[4b862c4]304            if self.data_type == 'ascii':
[535e181]305                qdata = self.extract_ascii_data(self.q_input.GetPath())
306                iqdata = np.array([self.extract_ascii_data(self.iq_input.GetPath())])
307            elif self.data_type == 'otoko':
308                qdata, iqdata = self.extract_otoko_data(self.q_input.GetPath())
[4b862c4]309            else: # self.data_type == 'bsl'
[0b1a677]310
311                x, y, frame_data = self.extract_bsl_data(self.iq_input.GetPath())
312                if x == None and y == None and frame_data == None:
313                    # Cancelled by user
314                    return
315
316                self.to_convert = frame_data
317
318                frame_number, data = self.to_convert.popitem()
319                self.bsl_thread = ConvertBSLThread((x, y), data,
320                    self.output.GetPath(), frame_number=frame_number,
321                    updatefn=self.conversion_update,
[35488b2]322                    completefn=self.conversion_complete)
323                self.bsl_thread.queue()
[0b1a677]324
[35488b2]325                self.convert_btn.SetLabel("Stop Conversion")
[535e181]326                return
327
[a58706d]328        except Exception as ex:
329            msg = str(ex)
330            wx.PostEvent(self.parent.manager.parent,
331                StatusEvent(status=msg, info='error'))
332            return
333
[535e181]334        frames = []
335        increment = 1
336        single_file = True
[05595c4]337        n_frames = iqdata.shape[0]
[535e181]338        # Standard file has 3 frames: SAS, calibration and WAS
[05595c4]339        if n_frames > 3:
[ba65aff]340            # File has multiple frames - ask the user which ones they want to
341            # export
[05595c4]342            params = self.ask_frame_range(n_frames)
[535e181]343            frames = params['frames']
344            increment = params['inc']
345            single_file = params['file']
346            if frames == []: return
347        else: # Only interested in SAS data
348            frames = [0]
349
[f2b3f28]350        output_path = self.output.GetPath()
351
[ba65aff]352        # Prepare the metadata for writing to a file
[d112c30]353        if self.run is None:
[503cc34]354            self.run = []
[d112c30]355        elif not isinstance(self.run, list):
356            self.run = [self.run]
[503cc34]357
[ea2a7348]358        if self.title is None:
359            self.title = ''
360
[503cc34]361        metadata = {
362            'title': self.title,
363            'run': self.run,
[5451a78]364            'instrument': self.instrument,
[503cc34]365            'detector': [self.detector],
366            'sample': self.sample,
367            'source': self.source
368        }
[f2b3f28]369
[9e9f848]370        frame_data = {}
[eb8da5f]371        for i in frames:
372            data = Data1D(x=qdata, y=iqdata[i])
[9e9f848]373            frame_data[i] = data
[94f4518]374        if single_file:
375            # Only need to set metadata on first Data1D object
[9e9f848]376            frame_data = frame_data.values() # Don't need to know frame numbers
[94f4518]377            frame_data[0].filename = output_path.split('\\')[-1]
378            for key, value in metadata.iteritems():
379                setattr(frame_data[0], key, value)
380        else:
381            # Need to set metadata for all Data1D objects
[9e9f848]382            for datainfo in frame_data.values():
[94f4518]383                datainfo.filename = output_path.split('\\')[-1]
384                for key, value in metadata.iteritems():
385                    setattr(datainfo, key, value)
386
387
388        self.convert_to_cansas(frame_data, output_path, single_file)
[a58706d]389        wx.PostEvent(self.parent.manager.parent,
390            StatusEvent(status="Conversion completed."))
391
[35488b2]392    def conversion_update(self, msg="", exception=None):
393        if exception is not None:
394            msg = str(exception)
395            wx.PostEvent(self.parent.manager.parent,
396                StatusEvent(status=msg, info='error'))
397        else:
398            wx.PostEvent(self.parent.manager.parent,
399                StatusEvent(status=msg))
400
401    def conversion_complete(self, success=True):
[0b1a677]402        msg = "Conversion of {} ".format(self.bsl_thread.frame_filename)
403
[35488b2]404        if success:
405            msg += "completed"
406        else:
407            msg += "failed"
408        wx.PostEvent(self.parent.manager.parent,
409            StatusEvent(status=msg))
410
[0b1a677]411        if len(self.to_convert) == 0:
412            self.convert_btn.SetLabel("Convert")
413            self.bsl_thread = None
414            wx.PostEvent(self.parent.manager.parent,
415                StatusEvent(status="Conversion finished"))
416        else:
417            n, data = self.to_convert.popitem()
418            self.bsl_thread.frame_data = data
419            self.bsl_thread.frame_number = n
420            self.bsl_thread.queue()
421
[35488b2]422
[11794f2]423    def on_help(self, event):
[ba65aff]424        """
425        Show the File Converter documentation
426        """
[11794f2]427        tree_location = ("user/sasgui/perspectives/file_converter/"
428            "file_converter_help.html")
429        doc_viewer = DocumentationWindow(self, -1, tree_location,
430            "", "File Converter Help")
431
[fdbea3c]432    def validate_inputs(self):
433        msg = "You must select a"
[535e181]434        if self.q_input.GetPath() == '' and self.data_type != 'bsl':
[fdbea3c]435            msg += " Q Axis input file."
436        elif self.iq_input.GetPath() == '':
437            msg += "n Intensity input file."
438        elif self.output.GetPath() == '':
[35488b2]439            msg += " destination for the converted file."
[fdbea3c]440        if msg != "You must select a":
441            wx.PostEvent(self.parent.manager.parent,
442                StatusEvent(status=msg, info='error'))
443            return
444
445        return True
446
[af84162]447    def show_detector_window(self, event):
[ba65aff]448        """
[b7c21a7]449        Show the window for inputting Detector metadata
[ba65aff]450        """
[de0df2c]451        if self.meta_frames != []:
452            for frame in self.meta_frames:
453                frame.panel.on_close()
454        detector_frame = MetadataWindow(DetectorPanel,
455            parent=self.parent.manager.parent, manager=self,
[503cc34]456            metadata=self.detector, title='Detector Metadata')
[de0df2c]457        self.meta_frames.append(detector_frame)
458        self.parent.manager.put_icon(detector_frame)
459        detector_frame.Show(True)
[fdbea3c]460
[2a7722b]461    def show_sample_window(self, event):
[ba65aff]462        """
[b7c21a7]463        Show the window for inputting Sample metadata
[ba65aff]464        """
[2a7722b]465        if self.meta_frames != []:
466            for frame in self.meta_frames:
467                frame.panel.on_close()
468        sample_frame = MetadataWindow(SamplePanel,
469            parent=self.parent.manager.parent, manager=self,
[503cc34]470            metadata=self.sample, title='Sample Metadata')
[2a7722b]471        self.meta_frames.append(sample_frame)
472        self.parent.manager.put_icon(sample_frame)
473        sample_frame.Show(True)
474
[55bc56bc]475    def show_source_window(self, event):
[ba65aff]476        """
[b7c21a7]477        Show the window for inputting Source metadata
[ba65aff]478        """
[55bc56bc]479        if self.meta_frames != []:
480            for frame in self.meta_frames:
481                frame.panel.on_close()
482        source_frame = MetadataWindow(SourcePanel,
483            parent=self.parent.manager.parent, manager=self,
[503cc34]484            metadata=self.source, title="Source Metadata")
[55bc56bc]485        self.meta_frames.append(source_frame)
486        self.parent.manager.put_icon(source_frame)
487        source_frame.Show(True)
488
[a027549]489    def on_collapsible_pane(self, event):
[ba65aff]490        """
491        Resize the scrollable area to fit the metadata pane when it's
492        collapsed or expanded
493        """
[a027549]494        self.Freeze()
495        self.SetupScrolling()
496        self.parent.Layout()
497        self.Thaw()
498
[4b862c4]499    def datatype_changed(self, event):
[ba65aff]500        """
501        Update the UI and self.data_type when a data type radio button is
502        pressed
503        """
[4b862c4]504        event.Skip()
505        dtype = event.GetEventObject().GetName()
506        self.data_type = dtype
[535e181]507        if dtype == 'bsl':
[05595c4]508            self.q_input.SetPath("")
[535e181]509            self.q_input.Disable()
[d6bf064]510            self.radiation_input.Disable()
511            self.metadata_section.Collapse()
512            self.on_collapsible_pane(None)
513            self.metadata_section.Disable()
[f5c52b0]514            self.output.SetWildcard("IGOR/DAT 2D file in Q_map (*.dat)|*.DAT")
[535e181]515        else:
516            self.q_input.Enable()
[d6bf064]517            self.radiation_input.Enable()
518            self.metadata_section.Enable()
[0e11ec7]519            self.output.SetWildcard("CanSAS 1D (*.xml)|*.xml")
[4b862c4]520
[55775e8]521    def radiationtype_changed(self, event):
522        event.Skip()
523        rtype = event.GetEventObject().GetValue().lower()
[503cc34]524        self.source.radiation = rtype
[55775e8]525
[f2b3f28]526    def metadata_changed(self, event):
527        event.Skip()
528        textbox = event.GetEventObject()
529        attr = textbox.GetName()
530        value = textbox.GetValue().strip()
[fdbea3c]531
[503cc34]532        setattr(self, attr, value)
[f2b3f28]533
534
[77d92cd]535    def _do_layout(self):
[f2b3f28]536        vbox = wx.BoxSizer(wx.VERTICAL)
[a58706d]537
[f5c52b0]538        instructions = ("Select either single column ASCII files or 1D OTOKO "
539        "files containing the Q-Axis and Intensity-Axis data, or a 2D BSL file"
540        ", then chose where to save the converted file, and click Convert.\n"
541        "ASCII and OTOKO files will be converted to CanSAS XML, and OTKO files"
542        " to IGOR/DAT 2D Q_map files.\nCanSAS metadata can also be optionally "
543        "input below.")
544
[5afe77f]545        instruction_label = wx.StaticText(self, -1, instructions,
546            size=(_STATICBOX_WIDTH+40, -1))
547        instruction_label.Wrap(_STATICBOX_WIDTH+40)
[f2b3f28]548        vbox.Add(instruction_label, flag=wx.TOP | wx.LEFT | wx.RIGHT, border=5)
[77d92cd]549
[a58706d]550        section = wx.StaticBox(self, -1)
551        section_sizer = wx.StaticBoxSizer(section, wx.VERTICAL)
552        section_sizer.SetMinSize((_STATICBOX_WIDTH, -1))
[77d92cd]553
554        input_grid = wx.GridBagSizer(5, 5)
555
[4b862c4]556        y = 0
557
[489bb46]558        data_type_label = wx.StaticText(self, -1, "Input Format: ")
559        input_grid.Add(data_type_label, (y,0), (1,1),
560            wx.ALIGN_CENTER_VERTICAL, 5)
561        radio_sizer = wx.BoxSizer(wx.HORIZONTAL)
562        ascii_btn = wx.RadioButton(self, -1, "ASCII", name="ascii",
563            style=wx.RB_GROUP)
564        ascii_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
565        radio_sizer.Add(ascii_btn)
566        otoko_btn = wx.RadioButton(self, -1, "OTOKO 1D", name="otoko")
567        otoko_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
568        radio_sizer.Add(otoko_btn)
569        input_grid.Add(radio_sizer, (y,1), (1,1), wx.ALL, 5)
570        bsl_btn = wx.RadioButton(self, -1, "BSL 2D", name="bsl")
571        bsl_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
572        radio_sizer.Add(bsl_btn)
573        y += 1
574
[13b9a63]575        q_label = wx.StaticText(self, -1, "Q-Axis Data: ")
[4b862c4]576        input_grid.Add(q_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
[a58706d]577
578        self.q_input = wx.FilePickerCtrl(self, -1,
579            size=(_STATICBOX_WIDTH-80, -1),
[785fa53]580            message="Chose the Q-Axis data file.",
581            style=wx.FLP_USE_TEXTCTRL | wx.FLP_CHANGE_DIR)
[4b862c4]582        input_grid.Add(self.q_input, (y,1), (1,1), wx.ALL, 5)
583        y += 1
[a58706d]584
[13b9a63]585        iq_label = wx.StaticText(self, -1, "Intensity-Axis Data: ")
[4b862c4]586        input_grid.Add(iq_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
[a58706d]587
588        self.iq_input = wx.FilePickerCtrl(self, -1,
589            size=(_STATICBOX_WIDTH-80, -1),
[785fa53]590            message="Chose the Intensity-Axis data file.",
591            style=wx.FLP_USE_TEXTCTRL | wx.FLP_CHANGE_DIR)
[4b862c4]592        input_grid.Add(self.iq_input, (y,1), (1,1), wx.ALL, 5)
593        y += 1
594
[55775e8]595        radiation_label = wx.StaticText(self, -1, "Radiation Type: ")
[489bb46]596        input_grid.Add(radiation_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
[d6bf064]597        self.radiation_input = wx.ComboBox(self, -1,
[55775e8]598            choices=["Neutron", "X-Ray", "Muon", "Electron"],
599            name="radiation", style=wx.CB_READONLY, value="Neutron")
[d6bf064]600        self.radiation_input.Bind(wx.EVT_COMBOBOX, self.radiationtype_changed)
601        input_grid.Add(self.radiation_input, (y,1), (1,1), wx.ALL, 5)
[55775e8]602        y += 1
603
[a58706d]604        output_label = wx.StaticText(self, -1, "Output File: ")
[4b862c4]605        input_grid.Add(output_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
[a58706d]606
[0e11ec7]607        self.output = FileInput(self,
608            wildcard="CanSAS 1D (*.xml)|*.xml")
609        input_grid.Add(self.output.GetCtrl(), (y,1), (1,1), wx.EXPAND | wx.ALL, 5)
[4b862c4]610        y += 1
[a58706d]611
[35488b2]612        self.convert_btn = wx.Button(self, wx.ID_OK, "Stop Converstion")
613        self.convert_btn.SetLabel("Convert")
614        input_grid.Add(self.convert_btn, (y,0), (1,1), wx.ALL, 5)
615        self.convert_btn.Bind(wx.EVT_BUTTON, self.on_convert)
[77d92cd]616
[11794f2]617        help_btn = wx.Button(self, -1, "HELP")
618        input_grid.Add(help_btn, (y,1), (1,1), wx.ALL, 5)
619        help_btn.Bind(wx.EVT_BUTTON, self.on_help)
620
[a58706d]621        section_sizer.Add(input_grid)
[77d92cd]622
[f2b3f28]623        vbox.Add(section_sizer, flag=wx.ALL, border=5)
624
[d6bf064]625        self.metadata_section = wx.CollapsiblePane(self, -1, "Metadata",
[fdbea3c]626            size=(_STATICBOX_WIDTH+40, -1), style=wx.WS_EX_VALIDATE_RECURSIVELY)
[d6bf064]627        metadata_pane = self.metadata_section.GetPane()
[f2b3f28]628        metadata_grid = wx.GridBagSizer(5, 5)
629
[d6bf064]630        self.metadata_section.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED,
[a027549]631            self.on_collapsible_pane)
632
[f2b3f28]633        y = 0
[503cc34]634        for item in self.properties:
[7c8ddb83]635            # Capitalise each word
636            label_txt = " ".join(
637                [s.capitalize() for s in item.replace('_', ' ').split(' ')])
638            if item == 'run':
639                label_txt = "Run Number"
[fdbea3c]640            label = wx.StaticText(metadata_pane, -1, label_txt,
[f2b3f28]641                style=wx.ALIGN_CENTER_VERTICAL)
[fdbea3c]642            input_box = wx.TextCtrl(metadata_pane, name=item,
[f2b3f28]643                size=(_STATICBOX_WIDTH-80, -1))
644            input_box.Bind(wx.EVT_TEXT, self.metadata_changed)
645            metadata_grid.Add(label, (y,0), (1,1),
646                wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
[fdbea3c]647            metadata_grid.Add(input_box, (y,1), (1,2), wx.EXPAND)
[f2b3f28]648            y += 1
649
[fdbea3c]650        detector_label = wx.StaticText(metadata_pane, -1,
651            "Detector:")
652        metadata_grid.Add(detector_label, (y, 0), (1,1), wx.ALL | wx.EXPAND, 5)
[af84162]653        detector_btn = wx.Button(metadata_pane, -1, "Enter Detector Metadata")
654        metadata_grid.Add(detector_btn, (y, 1), (1,1), wx.ALL | wx.EXPAND, 5)
655        detector_btn.Bind(wx.EVT_BUTTON, self.show_detector_window)
[fdbea3c]656        y += 1
657
[2a7722b]658        sample_label = wx.StaticText(metadata_pane, -1, "Sample: ")
659        metadata_grid.Add(sample_label, (y,0), (1,1), wx.ALL | wx.EXPAND, 5)
660        sample_btn = wx.Button(metadata_pane, -1, "Enter Sample Metadata")
661        metadata_grid.Add(sample_btn, (y,1), (1,1), wx.ALL | wx.EXPAND, 5)
662        sample_btn.Bind(wx.EVT_BUTTON, self.show_sample_window)
663        y += 1
664
[55bc56bc]665        source_label = wx.StaticText(metadata_pane, -1, "Source: ")
666        metadata_grid.Add(source_label, (y,0), (1,1), wx.ALL | wx.EXPAND, 5)
667        source_btn = wx.Button(metadata_pane, -1, "Enter Source Metadata")
668        source_btn.Bind(wx.EVT_BUTTON, self.show_source_window)
669        metadata_grid.Add(source_btn, (y,1), (1,1), wx.ALL | wx.EXPAND, 5)
670        y += 1
671
[fdbea3c]672        metadata_pane.SetSizer(metadata_grid)
[f2b3f28]673
[d6bf064]674        vbox.Add(self.metadata_section, proportion=0, flag=wx.ALL, border=5)
[77d92cd]675
676        vbox.Fit(self)
677        self.SetSizer(vbox)
678
679class ConverterWindow(widget.CHILD_FRAME):
[ba65aff]680    """Displays ConverterPanel"""
[77d92cd]681
682    def __init__(self, parent=None, title='File Converter', base=None,
[f5c52b0]683        manager=None, size=(PANEL_SIZE * 1.05, PANEL_SIZE / 1.1),
[77d92cd]684        *args, **kwargs):
685        kwargs['title'] = title
686        kwargs['size'] = size
687        widget.CHILD_FRAME.__init__(self, parent, *args, **kwargs)
688
689        self.manager = manager
690        self.panel = ConverterPanel(self, base=None)
691        self.Bind(wx.EVT_CLOSE, self.on_close)
692        self.SetPosition((wx.LEFT, PANEL_TOP))
693        self.Show(True)
694
695    def on_close(self, event):
[0b1a677]696        if self.panel.bsl_thread.isrunning():
697            self.panel.bsl_thread.stop()
[77d92cd]698        if self.manager is not None:
699            self.manager.converter_frame = None
700        self.Destroy()
Note: See TracBrowser for help on using the repository browser.