diff --git a/GSASII/GSASIIdataGUI.py b/GSASII/GSASIIdataGUI.py index 76495e79..851dc609 100644 --- a/GSASII/GSASIIdataGUI.py +++ b/GSASII/GSASIIdataGUI.py @@ -1104,7 +1104,7 @@ def _Add_ImportMenu_Phase(self,parent): submenu = wx.Menu() item = parent.AppendSubMenu(submenu,'Phase','Import phase data') for reader in self.ImportPhaseReaderlist: - item = submenu.Append(wx.ID_ANY,u'from '+reader.formatName+u' file',reader.longFormatName) + item = submenu.Append(wx.ID_ANY,readFromFile(reader),reader.longFormatName) self.ImportMenuId[item.GetId()] = reader self.Bind(wx.EVT_MENU, self.OnImportPhase, id=item.GetId()) item = submenu.Append(wx.ID_ANY,'guess format from file','Import phase data, use file to try to determine format') @@ -1291,7 +1291,7 @@ def _Add_ImportMenu_Image(self,parent): submenu = wx.Menu() item = parent.AppendSubMenu(submenu, 'Image','Import image file') for reader in self.ImportImageReaderlist: - item = submenu.Append(wx.ID_ANY,u'from '+reader.formatName+u' file',reader.longFormatName) + item = submenu.Append(wx.ID_ANY,readFromFile(reader),reader.longFormatName) self.ImportMenuId[item.GetId()] = reader self.Bind(wx.EVT_MENU, self.OnImportImage, id=item.GetId()) item = submenu.Append(wx.ID_ANY,'guess format from file','Import image data, use file to try to determine format') @@ -1321,7 +1321,7 @@ def _Add_ImportMenu_Sfact(self,parent): submenu = wx.Menu() item = parent.AppendSubMenu(submenu,'Structure Factor','Import Structure Factor data') for reader in self.ImportSfactReaderlist: - item = submenu.Append(wx.ID_ANY,u'from '+reader.formatName+u' file',reader.longFormatName) + item = submenu.Append(wx.ID_ANY,readFromFile(reader),reader.longFormatName) self.ImportMenuId[item.GetId()] = reader self.Bind(wx.EVT_MENU, self.OnImportSfact, id=item.GetId()) item = submenu.Append(wx.ID_ANY,'guess format from file','Import Structure Factor, use file to try to determine format') @@ -1437,7 +1437,7 @@ def OnAutoImport(event): submenu = wx.Menu() item = parent.AppendSubMenu(submenu,'Powder Data','Import Powder data') for reader in self.ImportPowderReaderlist: - item = submenu.Append(wx.ID_ANY,u'from '+reader.formatName+u' file',reader.longFormatName) + item = submenu.Append(wx.ID_ANY,readFromFile(reader),reader.longFormatName) self.ImportMenuId[item.GetId()] = reader self.Bind(wx.EVT_MENU, self.OnImportPowder, id=item.GetId()) item = submenu.Append(wx.ID_ANY,'guess format from file','Import powder data, use file to try to determine format') @@ -2371,7 +2371,7 @@ def _Add_ImportMenu_smallangle(self,parent): submenu = wx.Menu() item = parent.AppendSubMenu(submenu,'Small Angle Data','Import small angle data') for reader in self.ImportSmallAngleReaderlist: - item = submenu.Append(wx.ID_ANY,u'from '+reader.formatName+u' file',reader.longFormatName) + item = submenu.Append(wx.ID_ANY,readFromFile(reader),reader.longFormatName) self.ImportMenuId[item.GetId()] = reader self.Bind(wx.EVT_MENU, self.OnImportSmallAngle, id=item.GetId()) # item = submenu.Append(wx.ID_ANY, @@ -2464,7 +2464,7 @@ def _Add_ImportMenu_reflectometry(self,parent): submenu = wx.Menu() item = parent.AppendSubMenu(submenu,'Reflectometry Data','Import reflectometry data') for reader in self.ImportReflectometryReaderlist: - item = submenu.Append(wx.ID_ANY,u'from '+reader.formatName+u' file',reader.longFormatName) + item = submenu.Append(wx.ID_ANY,readFromFile(reader),reader.longFormatName) self.ImportMenuId[item.GetId()] = reader self.Bind(wx.EVT_MENU, self.OnImportReflectometry, id=item.GetId()) # item = submenu.Append(wx.ID_ANY, @@ -2559,7 +2559,7 @@ def _Add_ImportMenu_PDF(self,parent): submenu = wx.Menu() item = parent.AppendSubMenu(submenu,'PDF G(R) Data','Import PDF G(R) data') for reader in self.ImportPDFReaderlist: - item = submenu.Append(wx.ID_ANY,u'from '+reader.formatName+u' file',reader.longFormatName) + item = submenu.Append(wx.ID_ANY,readFromFile(reader),reader.longFormatName) self.ImportMenuId[item.GetId()] = reader self.Bind(wx.EVT_MENU, self.OnImportPDF, id=item.GetId()) submenu.AppendSeparator() @@ -7512,6 +7512,14 @@ def _makemenu(): # routine to create menu when first used self.DataGeneral = _makemenu # end of GSAS-II menu definitions +def readFromFile(reader): + '''Define a caption for a file import menu item''' + nam = reader.formatName + if nam.startswith('(user'): + return nam.replace('(user)','(user) from')+' file' + else: + return f'from {reader.formatName} file' + #### Notebook Tree Item editor ############################################## NBinfo = {} def UpdateNotebook(G2frame,data): diff --git a/GSASII/GSASIIfiles.py b/GSASII/GSASIIfiles.py index 10bd7c6d..8e8e0219 100644 --- a/GSASII/GSASIIfiles.py +++ b/GSASII/GSASIIfiles.py @@ -579,173 +579,155 @@ def WriteInstprm(fp, InstPrm, InstPrm1, Sample={}, bank=None): #write output string fp.write(resString) -# version of LoadImportRoutines from before switch to "main" -# def _old_LoadImportRoutines(prefix, errprefix=None, traceback=False): -# '''Routine to locate GSASII importers matching a prefix string. -# -# Warns if more than one file with the same name is in the path -# or if a file is found that is not in the main directory tree. -# ''' -# if errprefix is None: -# errprefix = prefix -# -# readerlist = [] -# import_files = {} -# if '.' not in sys.path: sys.path.append('.') -# for path in sys.path: -# for filename in glob.iglob(os.path.join(path, 'G2'+prefix+'*.py')): -# pkg = os.path.splitext(os.path.split(filename)[1])[0] -# if pkg in import_files: -# G2Print('Warning: importer {} overrides {}'.format(import_files[pkg],os.path.abspath(filename))) -# elif not filename.startswith(GSASIIpath.path2GSAS2): -# G2Print('Note: found importer in non-standard location:'+ -# f'\n\t{os.path.abspath(filename)}') -# import_files[pkg] = filename -# else: -# import_files[pkg] = filename - -# for pkg in sorted(import_files.keys()): -# try: -# exec('import '+pkg) -# #print(eval(pkg+'.__file__')) -# for name, value in inspect.getmembers(eval(pkg)): -# if name.startswith('_'): -# continue -# if inspect.isclass(value): -# for method in 'Reader', 'ExtensionValidator', 'ContentsValidator': -# if not hasattr(value, method): -# break -# if not callable(getattr(value, method)): -# break -# else: -# reader = value() -# if reader.UseReader: -# readerlist.append(reader) -# except AttributeError: -# G2Print ('Import_' + errprefix + ': Attribute Error ' + import_files[pkg]) -# if traceback: -# traceback.print_exc(file=sys.stdout) -# except Exception as exc: -# G2Print ('\nImport_' + errprefix + ': Error importing file ' + import_files[pkg]) -# G2Print (u'Error message: {}\n'.format(exc)) -# if traceback: -# traceback.print_exc(file=sys.stdout) -# return readerlist - def LoadImportRoutines(prefix, errprefix=None, traceback=False): + '''Loads import routines from the GSASII.imports area and + from a user-supplied area, ~/.GSASII/imports. + + :param str prefix: string used in the file name to differentiate the + importer type (e.g. `G2img_xyz.py` is an importer for images. Will + be one of 'img' (images), 'pdf' (pair distribution function), + 'phase' (cell/coordinates), 'pwd' (powder diffraction), 'rfd' + (reflectivity), 'sad' (small-angle scattering) or 'sfact' + (single crystal). + :param errprefix: no longer used + :param traceback: no longer used + :returns: a list of reader routines (callable objects) + ''' from . import imports readerlist = [] - # how to import directly from a file for extra search magic if we need - # https://stackoverflow.com/questions/67631/how-can-i-import-a-module-dynamically-given-the-full-path - # TODO handle non-bundled readers + # configure readers that have already been imported for mod_name in (_ for _ in dir(imports) if _.startswith(f'G2{prefix}')): mod = getattr(imports, mod_name) for member_name in dir(mod): if member_name.startswith('_'): continue member = getattr(mod, member_name) - if all( - hasattr(member, meth) - for meth in ('Reader', 'ExtensionValidator', 'ContentsValidator') - ): + if not callable(member): continue + for meth in ('Reader', 'ExtensionValidator', 'ContentsValidator','UseReader'): + if not hasattr(member, meth): break + else: reader = member() if reader.UseReader: readerlist.append(reader) + # Now look for modules in the "user-defined" area (~/.GSASII/imports) + fnam = os.path.expanduser(os.path.normpath(f'~/.GSASII/imports/G2{prefix}*.py')) + import importlib.util + for f in sorted(glob.glob(fnam)): + nam = os.path.splitext(os.path.split(f)[1])[0] + try: + modspec = importlib.util.spec_from_file_location(nam, f) + module = importlib.util.module_from_spec(modspec) + # will process "from .." and "from ." as if found in + # GSASII/imports directory + module.__package__ = "GSASII.imports" + modspec.loader.exec_module(module) + # not sure if I want to do this or not + sys.modules[f"GSASII.userimports.{nam}"] = module + for member_name in dir(module): + if member_name.startswith('_'): + continue + member = getattr(module, member_name) + if not callable(member): continue + for meth in ('Reader', 'ExtensionValidator', 'ContentsValidator','UseReader'): + if not hasattr(member, meth): break + else: + reader = member() + reader.formatName = '(user) ' + reader.formatName + reader.longFormatName = '(user supplied) ' + reader.longFormatName + if reader.UseReader: + readerlist.append(reader) + except Exception as msg: + G2Print(f'\nImporter init: error with importer file {f!r}') + G2Print ('Error message: {}\n'.format(msg)) return readerlist -# version of LoadExportRoutines from before switch to "main" -# def _LoadExportRoutines(parent, usetraceback=False): -# '''Routine to locate GSASII exporters. Warns if more than one file -# with the same name is in the path or if a file is found that is not -# in the main directory tree. -# ''' -# exporterlist = [] -# export_files = {} -# if '.' not in sys.path: sys.path.append('.') -# for path in sys.path: -# for filename in glob.iglob(os.path.join(path,"G2export*.py")): -# pkg = os.path.splitext(os.path.split(filename)[1])[0] -# if pkg in export_files: -# G2Print('Warning: exporter {} overrides {}'.format(export_files[pkg],os.path.abspath(filename))) -# elif not filename.startswith(GSASIIpath.path2GSAS2): -# G2Print('Note, found non-standard exporter: {}'.format(os.path.abspath(filename))) -# export_files[pkg] = filename -# else: -# export_files[pkg] = filename -# # go through the routines and import them, saving objects that -# # have export routines (method Exporter) -# for pkg in sorted(export_files.keys()): -# try: -# exec('import '+pkg) -# for clss in inspect.getmembers(eval(pkg)): # find classes defined in package -# if clss[0].startswith('_'): continue -# if not inspect.isclass(clss[1]): continue -# # check if we have the required methods -# if not hasattr(clss[1],'Exporter'): continue -# if not callable(getattr(clss[1],'Exporter')): continue -# if parent is None: -# if not hasattr(clss[1],'Writer'): continue -# else: -# if not hasattr(clss[1],'loadParmDict'): continue -# if not callable(getattr(clss[1],'loadParmDict')): continue -# try: -# exporter = clss[1](parent) # create an export instance -# except AttributeError: -# pass -# except Exception as exc: -# G2Print ('\nExport init: Error substantiating class ' + clss[0]) -# G2Print (u'Error message: {}\n'.format(exc)) -# if usetraceback: -# import traceback -# traceback.print_exc(file=sys.stdout) -# continue -# exporterlist.append(exporter) -# except AttributeError: -# G2Print ('Export Attribute Error ' + export_files[pkg]) -# if usetraceback: -# import traceback -# traceback.print_exc(file=sys.stdout) -# except Exception as exc: -# G2Print ('\nExport init: Error importing file ' + export_files[pkg]) -# G2Print (u'Error message: {}\n'.format(exc)) -# if usetraceback: -# import traceback -# traceback.print_exc(file=sys.stdout) -# return exporterlist - def LoadExportRoutines(parent, usetraceback=False): + '''Loads all export routines from the GSASII.exports area and + from a user-supplied area, ~/.GSASII/exports. + + :param parent: a reference to the main GSAS-II window when called + from the GUI or None when called from GSASIIscriptable + :param usetraceback: if True (used for debug only) a traceback + is shown when accessing the exporter object fails + :returns: a list of exporter routines (callable objects) + ''' from . import exports exporterlist = [] - # how to import directly from a file for extra search magic if we need - # https://stackoverflow.com/questions/67631/how-can-i-import-a-module-dynamically-given-the-full-path - # TODO handle non-bundled readers + + # configure exporters that have already been imported for mod_name in (_ for _ in dir(exports) if _.startswith('G2export')): mod = getattr(exports, mod_name) for member_name in dir(mod): if member_name.startswith('_'): continue member = getattr(mod, member_name) - if not hasattr(member, 'Exporter'): + if not callable(member): continue + if not hasattr(member, 'Exporter'): # exporters must have an exporter continue - if parent is None: - if not hasattr(member, 'Writer'): + try: + if parent is None: # scripting, must also have Writer + if not hasattr(member, 'Writer'): + continue + exporter = member(None) + else: # from GUI, must also have loadParmDict + if not hasattr(member, 'loadParmDict'): + continue + exporter = member(parent) # pass the main window to the class + exporterlist.append(exporter) + except Exception as exc: + G2Print (f'\nExport init: Error substantiating class {member_name}') + G2Print ('Error message: {}\n'.format(exc)) + if usetraceback: + import traceback + traceback.print_exc(file=sys.stdout) + # Now look for modules in the "user-defined" area (~/.GSASII/exports) + fnam = os.path.expanduser(os.path.normpath('~/.GSASII/exports/G2export*.py')) + import importlib.util + for f in sorted(glob.glob(fnam)): + nam = os.path.splitext(os.path.split(f)[1])[0] + try: + modspec = importlib.util.spec_from_file_location(nam, f) + module = importlib.util.module_from_spec(modspec) + # will process "from .." and "from ." as if found in + # GSASIIexports directory + module.__package__ = "GSASII.exports" + modspec.loader.exec_module(module) + # not sure if I want to do this or not + sys.modules[f"GSASII.userexports.{nam}"] = module + except Exception as exc: + G2Print (f'\nExport init: Error with exporter file {f!r}') + G2Print ('Error message: {}\n'.format(exc)) + if usetraceback: + import traceback + traceback.print_exc(file=sys.stdout) + continue + for member_name in dir(module): + try: + if member_name.startswith('_'): continue - else: - if not hasattr(member, 'loadParmDict'): + member = getattr(module, member_name) + if not hasattr(member, 'Exporter'): # exporters must have an exporter continue - try: - exporter = member(parent) + if parent is None: # scripting, must also have Writer + if not hasattr(member, 'Writer'): + continue + exporter = member(None) + else: # from GUI, must also have loadParmDict + if not hasattr(member, 'loadParmDict'): + continue + exporter = member(parent) # pass the main window to the class + exporter.formatName = '(user) ' + exporter.formatName + exporter.longFormatName = '(user supplied) ' + exporter.longFormatName + exporterlist.append(exporter) except Exception as exc: - G2Print ('\nExport init: Error substantiating class ' + member_name) + G2Print (f'\nExport init: Error with substantiating class {member_name}') G2Print ('Error message: {}\n'.format(exc)) if usetraceback: import traceback traceback.print_exc(file=sys.stdout) - continue return exporterlist - def readColMetadata(imagefile): '''Reads image metadata from a column-oriented metadata table (1-ID style .par file). Called by :func:`GetColumnMetadata` diff --git a/docs/source/exports.rst b/docs/source/exports.rst index 2a403323..d0e6dc9f 100644 --- a/docs/source/exports.rst +++ b/docs/source/exports.rst @@ -1,19 +1,142 @@ *GSAS-II Export Modules* ==================================== +Many of the data files written by GSAS-II for use by other software is +written using a layer of routines called exporters. Exporters usually require quite +simple code, so they can be written quickly to allow GSAS-II to +provide output in new data formats. +The interface to the exporters +is self-configuring, so all supplied exporters are available once the +exporter is added to the code base. This allows GSAS-II to be quite +flexible in adapting to use many data formats without need for +extensive coding. + Exports are implemented by deriving a class from :class:`~GSASII.GSASIIfiles.ExportBaseclass` in module -:mod:`~GSASII.GSASIIfiles`. Initialization of -``self.exporttype`` determines the type of export that will be performed -('project', 'phase', 'single', 'powder', 'image', 'map' or (someday) -'pdf') and of ``self.multiple`` +:mod:`~GSASII.GSASIIfiles`. +Export routines commonly access the GUI to determine information on +the contents of the file(s) to be written, but for powder diffraction +and phase data export, a routine named ``Writer()`` may be defined. If this is +present, the exporter can be used from: mod:`~GSASII.GSASIIscriptable` +without GUI access. Note that the arguments for the``Writer()`` method +include a histogram tree name as well as a file name to +be written. + +A file containing one or more export routine can be placed +either in the ``GSASII/exports`` directory (which requires +modification of the ``__init__.py`` file or the file can be placed in +the ``~/.GSASII/exports`` directory. +(Note that ``~`` here is translated to the +user's home directory; for Windows this is usually taken from the +USERPROFILE setting or a combination of HOMEPATH and HOMEDRIVE, +so this directory will usually have form +``C:\\Users\\YourUsername\\.GSASII\\exports``. +The next time GSAS-II is started, +the file will be loaded with all the other GSAS-II files and +the new data format will appear in the appropriate exporter menu. + +.. _export_routines: + +====================================== + Writing an Exporter Routine +====================================== + +When writing a exporter routine, one should create a new class derived +from class `~GSASII.GSASIIfiles.ExportBaseclass`. +The name of the class is arbitrary, but if more than one class is +placed in file, each class must have a different name. The same name +can be repeated if it is in different files. +As described below, this class will implement +an ``__init__()`` and an ``Exporter()`` method, and many will supply a +``Writer()`` method, too. The purpose of each of these +routines is described below. The easiest way to craft a new exporter +will be to use the other exporters of the same data type as a model +for where to find the data values that will be written, but the documentation +for the parent class (`~GSASII.GSASIIfiles.ExportBaseclass`) provides +useful information on support routines that pull information from the +GSAS-II data structures into the exporter. + +__init__() +-------------- + +The ``__init__`` method will follow standard boilerplate largely independent +of the data type: + +.. code-block:: python + + def __init__(self): + super(self.__class__,self).__init__( # fancy way to self-reference + G2frame=G2frame, + formatName = 'Format name for menu', + extension='.extn', + longFormatName = 'Longer more detailed format name for status line' + ) + +The first line in the ``__init__`` method calls the parent class +``__init__`` method with the following parameters: + + * ``G2frame``: a reference to the main GSAS-II GUI window or None + when run scripted. + * ``formatName``: a string to be used in the menu. Should be short. + * ``extension``: a string to be used in the file name. All files + produced by the exporter will have this extension. + * ``longFormatName``: a longer string to be used to describe the + format in help. + +In addition, one instance variables must be defined: + +.. code-block:: python + + self.exporttype = ['phase'] + +The value for ``self.exporttype`` determines the type of export that will be performed +('project', 'phase', 'single', 'powder', 'image', 'map', 'sasd', 'refd' or (someday) +'pdf') and the menu where the exporter will be placed. Note that +'project' exports are those that include all data from a +.gpx file (all phases, histograms, etc.) + +Another item is optional. + +.. code-block:: python + + self.multiple = True + +The value specified for ``self.multiple`` determines if only a single phase, data set, etc. can be exported at a -time (when False) or more than one can be selected. +time (when False) or when True, a file can be produced with multiple +histograms, phases, etc. + +Exporter() +-------------- + +The class must supply a ``Exporter`` method that will write a +file. Depending on the settings for ``self.exporttype`` and +``self.multiple`` and the contents of the Data Tree, will dictate +what dialogs will be presented to the user to select what will be +written. + +Writer() +-------------- + +For powder and phase exports, if the class supplies a ``Writer()`` +module, then the export format +will be available for scripted output (with +:mod:`GSASIIscriptable`). These modules are supplied a histogram name +or a phase name and should not attempt to access the GUI. It is not +required that that this method be supplied, but usually it is not hard to do, +unless information from the user is required. + +Note that for phase exports the ``Writer()`` should be declared as follows: + +.. code-block:: python + + def Writer(self,hist,phasenam,mode='w'): + +while for histogram exports the ``Writer()`` should be declared as follows: + +.. code-block:: python -Powder export routines may optionally define a ``Writer()`` -method that accepts the histogram tree name as well as a file name to -be written. This allows :mod:`~GSASII.GSASIIscriptable` to use the exporters -independent of the GUI. + def Writer(self,hist,filename=None,mode='w'): ------------------------------------------- *Module G2export_examples: Examples* diff --git a/docs/source/imports.rst b/docs/source/imports.rst index 7a36b6a1..0ef9fd9e 100644 --- a/docs/source/imports.rst +++ b/docs/source/imports.rst @@ -2,6 +2,16 @@ *GSAS-II Importer Modules* -------------------------------------------------------- +Almost all the types of data files that GSAS-II can accept are read +using a layer of routines called importers. (The one exception is the +reading of powder peak positions.) Importers usually require quite +simple code, so they can be written quickly for new data formats. +The interface to the importers +is self-configuring, so all supplied importers are available once the +importer is added to the code base. This allows GSAS-II to be quite +flexible in adapting to use many data formats without need for +extensive coding. + Imports are implemented by deriving a class from :class:`GSASIIobj.ImportPhase`, :class:`GSASIIobj.ImportStructFactor`, :class:`GSASIIobj.ImportPowderData` , @@ -10,18 +20,39 @@ Imports are implemented by deriving a class from :class:`GSASIIobj.ImportPDFData`, or :class:`GSASIIobj.ImportImage`. These classes are in turn derived from :class:`GSASIIobj.ImportBaseclass`. - Module file names (`G2phase_`, `G2pwd_` and `G2sfact_`, etc.) are used to -determine which menu an importer routine should be placed -into. (N.B. in retrospect this +determine which type of data will be read and which menu an importer +routine should be placed into. (N.B. in retrospect this naming was an unnecessary choice; importer types could have been determined -from the base class.) +from the base class as is done for exporters.) + To implement the import of a phase, a single crystal or a powder dataset, etc., create a file -named with the appropriate file name prefix and place the file anywhere in the -path defined in ``sys.path``. The next time GSAS-II is started, -the file should be read by Python and the new format will appear in -the appropriate importer menu. +named with the appropriate file name prefix: + +* 'img' (images), +* 'pdf' (pair distribution function), +* 'phase' (cell/coordinates), +* 'pwd' (powder diffraction), +* 'rfd' (reflectivity), +* 'sad' (small-angle scattering) or +* 'sfact' (single crystal). + +This file can be placed +either in the ``GSASII/imports`` directory (which requires +modification of the ``__init__.py`` file or the file can be placed in +the ``~/.GSASII/imports`` directory. +(Note that ``~`` here is translated to the +user's home directory; for Windows this is usually taken from the +USERPROFILE setting or a combination of HOMEPATH and HOMEDRIVE, +so this directory will usually have form +``C:\\Users\\YourUsername\\.GSASII\\imports``. +The next time GSAS-II is started, +the file will be loaded with all the other GSAS-II files and +the new data format(s) will appear in the appropriate importer menu. +The importer file may contain a +single importer class or several. + Importers are documented below, separated by type. Importers tend to be fairly simple files, where many are in the range of 50-100 lines, and where more than half of those lines are directly copied from other @@ -42,18 +73,27 @@ from :class:`GSASIIobj.ImportSmallAngleData`, :class:`GSASIIobj.ImportReflectometryData`, :class:`GSASIIobj.ImportPDFData`, -or :class:`GSASIIobj.ImportImage`. As described below, -all these classes will implement -an ``__init__()`` and a ``Reader()`` method, and most will supply a -``ContentsValidator()`` method, too. -See the appropriate class documentation +or :class:`GSASIIobj.ImportImage`. +The name of the class is arbitrary, but if more than one class is +placed in file, each class must have a different name. The same name +can be repeated if it is in different files. +As described below, +to implement an importer class, you must implement +an ``__init__()`` and a ``Reader()`` method, and many will supply a +``ContentsValidator()`` method, too. The purpose of each of these +routines is described below. The easiest way to craft a new importer +will be to use the other importers of the same data type as a model +for what values should be set inside each routine, with most of the +work needed to create a ``Reader()`` routine. The documentation +for the parent class may also have some useful information. See the appropriate class for details on what values each type of ``Reader()`` should -set. General principles on how an importer works are described below. +set. __init__() -------------- -The ``__init__`` method will follow standard boilerplate: +The ``__init__`` method will follow standard boilerplate largely independent +of the data type: .. code-block:: python @@ -70,7 +110,7 @@ The first line in the ``__init__`` method calls the parent class * ``extensionlist``: a list of extensions that may be used for this type of file. * ``strictExtension``: Should be True if only files with extensions in - ``extensionlist`` are allows; False if all file types should be offered + ``extensionlist`` are allowed; False if all file types should be offered in the file browser. Also if False, the importer class will be used on all files when "guess from format" is tried, though readers with matching extensions will be tried first. @@ -86,7 +126,7 @@ set the value of ``self.UseReader`` to False. Another possible use for this would be an importer that requires a network connection to a remote site. Setting ``self.UseReader`` to False must be done in the ``__init__`` method and will prevent the -importer from being used or included in the expected menu. +importer from being accessed or included in the appropriate GUI menu. Reader() -------------- @@ -104,10 +144,10 @@ where the arguments have the following uses: called. * ``ParentFrame``: a reference to the main GSAS-II (tree) windows, for the unusual ``Reader`` routines that will create GUI windows to ask - questions. The Reader should do something reasonable such as take a - reasonable default if ``ParentFrame`` is None, which indicates that - GUI should not be accessed. - + questions. For use with scripting, the Reader should do something + reasonable such as assume a default if ``ParentFrame`` is None, + which indicates that the GUI should not be accessed. + In addition, the following keyword parameters are defined that ``Reader`` routines may optionally use: @@ -124,7 +164,9 @@ to hold information that will speed repeated calls. As an example, the ``buffer`` dict is used in CIF reading to hold the parsed CIF file, so that when reading multiple datasets or phases from a multi-block CIF, the parsed information can be reused without having to reread and -reparse the file for subsequent calls. +reparse the file for subsequent calls. For multi-image files, the +indexing of where images are to be found is done once and saved. This +greatly speeds the time needed to process image file. Some additional information specific to on what a ``Reader()`` method should do for images and single-crystal datasets can be found in the