Exemple #1
0
    def main(self, mex_url=None, bisque_token=None, bq=None):
        #  Allow for testing by passing an alreay initialized session
        if bq is None:
            bq = BQSession().init_mex(mex_url, bisque_token)
        pars = bq.parameters()
        image_url = pars.get('dataset_url', None)
        annotation_type = pars.get('annotation_type', None)
        annotation_attribute = pars.get('annotation_attribute', None)
        value_old = pars.get('value_old', None)

        bq.update_mex('Starting')
        mex_id = mex_url.split('/')[-1]
        if image_url is None or len(image_url) < 2:
            datasets = bq.fetchxml('/data_service/dataset')
            datasets = [d.get('uri') for d in datasets.xpath('dataset')]
        else:
            datasets = [image_url]
        total = 0
        changes = []

        # delete annotations for each element in the dataset
        for ds_url in datasets:
            dataset = bq.fetchxml(ds_url, view='deep')
            dataset_name = dataset.get('name')
            bq.update_mex('processing "%s"' % dataset_name)

            refs = dataset.xpath('value[@type="object"]')
            for r in refs:
                url = r.text
                image = bq.fetchxml(url, view='deep')
                uuid = image.get('resource_uniq')
                modified = delete(image,
                                  value_old,
                                  ann_type=annotation_type,
                                  ann_attr=annotation_attribute)
                if len(modified) > 0:
                    bq.postxml(url, image, method='PUT')
                    total = total + len(modified)
                    changes.append({
                        'name':
                        '%s (%s)' %
                        (image.get('name'), image.get('resource_uniq')),
                        'value':
                        '%s' % len(modified)
                    })

        changes.insert(0, {'name': 'Total', 'value': '%s' % total})
        bq.finish_mex(tags=[{
            'name': 'outputs',
            'tag': [{
                'name': 'deleted',
                'tag': changes,
            }]
        }])
Exemple #2
0
    fn = data['ImageName'][i].strip(' ')
    m = {}
    for k in Ks:
        v = data[k][i]
        if isinstance(v, basestring):
            m[k] = v.strip(' ')
        else:
            m[k] = v
    meta[fn] = m

##################################################################
# load dataset
##################################################################

query_url = '/data_service/dataset?tag_query=@name:%s&offset=0&limit=10'%(dataset_name)
q = session.fetchxml (query_url)
ds = q.xpath('dataset')
if len(ds)>1:
    print 'Stopping: found more than one dataset with a given name: %s'%dataset_name
    sys.exit()
dataset_url = ds[0].get('uri')

dataset = session.fetchxml (dataset_url, view='deep')
images = dataset.xpath('value')

for m in images:
    image_url = m.text
    image = session.fetchxml (image_url, view='deep')
    if image.tag != 'image':
        continue
    #print etree.tostring(image)
Exemple #3
0
class ImageServiceTestBase(unittest.TestCase):
    """
        Test image service operations
    """
    @classmethod
    def setUpClass(self):
        config = ConfigParser.ConfigParser()
        config.read('config.cfg')

        self.root = config.get('Host', 'root') or 'localhost:8080'
        self.user = config.get('Host', 'user') or 'test'
        self.pswd = config.get('Host', 'password') or 'test'

        self.session = BQSession().init_local(self.user,
                                              self.pswd,
                                              bisque_root=self.root,
                                              create_mex=False)

        # download and upload test images ang get their IDs
        #self.uniq_2d_uint8  = self.ensure_bisque_file(image_rgb_uint8)
        #self.uniq_3d_uint16 = self.ensure_bisque_file(image_zstack_uint16)

    @classmethod
    def tearDownClass(self):
        #self.delete_resource(self.uniq_2d_uint8)
        #self.delete_resource(self.uniq_3d_uint16)
        self.cleanup_tests_dir()
        pass

    @classmethod
    def fetch_file(self, filename):
        _mkdir(local_store_images)
        _mkdir(local_store_tests)
        url = posixpath.join(url_image_store, filename).encode('utf-8')
        path = os.path.join(local_store_images, filename)
        if not os.path.exists(path):
            urllib.urlretrieve(url, path)
        return path

    @classmethod
    def upload_file(self, path, resource=None):
        #if resource is not None:
        #    print etree.tostring(resource)
        r = save_blob(self.session, path, resource=resource)
        if r is None or r.get('uri') is None:
            print 'Error uploading: %s' % path.encode('ascii', 'replace')
            return None
        print 'Uploaded id: %s url: %s' % (r.get('resource_uniq'),
                                           r.get('uri'))
        return r

    @classmethod
    def delete_resource(self, r):
        if r is None:
            return
        url = r.get('uri')
        print 'Deleting id: %s url: %s' % (r.get('resource_uniq'), url)
        self.session.deletexml(url)

    @classmethod
    def delete_package(self, package):
        if 'dataset' in package:
            # delete dataset
            url = package['dataset']
            print 'Deleting dataset: %s' % (url)
            try:
                self.session.fetchxml('/dataset_service/delete?duri=%s' % url)
            except BQCommError:
                print 'Error deleting the dataset'
        elif 'items' in package:
            # delete all items
            for url in package['items']:
                print 'Deleting item: %s' % (url)
                try:
                    self.session.deletexml(url)
                except BQCommError:
                    print 'Error deleting the item'

        # # delete dataset
        # if 'dataset' in package:
        #     url = package['dataset']
        #     print 'Deleting dataset: %s'%(url)
        #     try:
        #         self.session.deletexml(url)
        #     except BQCommError:
        #         print 'Error deleting the dataset'

        # # delete all items
        # if 'items' in package:
        #     for url in package['items']:
        #         print 'Deleting item: %s'%(url)
        #         try:
        #             self.session.deletexml(url)
        #         except BQCommError:
        #             print 'Error deleting the item'

    @classmethod
    def ensure_bisque_file(self, filename, metafile=None):
        path = self.fetch_file(filename)
        if metafile is None:
            filename = u'%s/%s' % (TEST_PATH, filename)
            resource = etree.Element('resource', name=filename)
            return self.upload_file(path, resource=resource)
        else:
            metafile = self.fetch_file(metafile)
            return self.upload_file(path,
                                    resource=etree.parse(metafile).getroot())

    @classmethod
    def ensure_bisque_package(self, package):
        path = self.fetch_file(package['file'])
        r = self.upload_file(path, resource=etree.XML(package['resource']))
        package['resource'] = r
        if r is None:
            return None
        print 'Uploaded id: %s url: %s' % (r.get('resource_uniq'),
                                           r.get('uri'))
        #print etree.tostring(r)
        if r.tag != 'dataset':
            package['items'] = [r.get('uri')]
        else:
            package['dataset'] = r.get('uri')
            values = r.xpath('value')
            if len(values) != package['count']:
                print 'Error: uploaded %s has %s elements but needs %s' % (
                    package['file'], len(values), package['count'])
            if r.get('name') != package['name']:
                print 'Error: uploaded %s name is %s but should be %s' % (
                    package['file'], r.get('name'), package['name'])
            package['items'] = [x.text for x in values]

        package['last'] = self.session.fetchxml(package['items'][-1],
                                                view='deep')
        #print 'Last item\n'
        #print etree.tostring(package['last'])

    @classmethod
    def cleanup_tests_dir(self):
        print 'Cleaning-up %s' % local_store_tests
        for root, dirs, files in os.walk(local_store_tests, topdown=False):
            for name in files:
                os.remove(os.path.join(root, name))

    def validate_image_variant(self,
                               resource,
                               filename,
                               commands,
                               meta_required=None):
        path = os.path.join(local_store_tests, filename)
        try:
            #image = fromXml(resource, session=self.session)
            image = self.session.factory.from_etree(resource)
            px = image.pixels()
            for c, a in commands:
                px = px.command(c, a)
            px.fetch(path)
        except BQCommError:
            logging.exception('Comm error')
            self.fail('Communication error while fetching image')

        if meta_required is not None:
            meta_test = metadata_read(path)
            self.assertTrue(meta_test is not None,
                            msg='Retrieved image can not be read')
            self.assertTrue(
                compare_info(meta_required, meta_test),
                msg='Retrieved metadata differs from test template')

    def validate_xml(self, resource, filename, commands, xml_parts_required):
        path = os.path.join(local_store_tests, filename)
        try:
            #image = fromXml(resource, session=self.session)
            image = self.session.factory.from_etree(resource)
            px = image.pixels()
            for c, a in commands:
                px = px.command(c, a)
            px.fetch(path)
        except BQCommError:
            self.fail()

        xml_test = etree.parse(path).getroot()
        #print etree.tostring(xml_test)
        self.assertTrue(compare_xml(xml_parts_required, xml_test),
                        msg='Retrieved XML differs from test template')
Exemple #4
0
class SeedSize(object):

    def setup(self):
        if not os.path.exists(self.images):
            os.makedirs(self.images)

        self.bq.update_mex('initializing')
        if self.is_dataset:
            results = fetch_dataset(self.bq, self.resource_url, self.images)
        else:
            results = fetch_image_pixels(self.bq, self.resource_url, self.images)

        with open(self.image_map_name, 'wb') as f:
            pickle.dump(results, f)
        return 0


    def start(self):
        self.bq.update_mex('executing')
        # Matlab requires trailing slash
        r = subprocess.call([EXEC, 'images/'])
        return r

    def teardown(self):
        with  open(self.image_map_name, 'rb') as f:
            self.url2file = pickle.load(f) #
            self.file2url =  dict((v,k) for k,v in self.url2file.iteritems())

        summary    = os.path.join(self.images, 'summary.csv')

        if not os.path.exists (summary):
            self.bq.fail_mex (msg = "did not find any seeds: missing %s" % summary)
            return 0

        summary_tags = self._read_summary(summary)

        # Post all submex for files and return xml list of results
        tags = []
        gobjects = []
        submexes = []
        if not self.is_dataset:
            localfiles = glob.glob(os.path.join(self.images, '*C.csv'))
            gobs = self._read_results(localfiles[0])
            #tags = [{ 'name':'image_url', 'value' : self.resource_url}]
            tags = [{ 'name': 'outputs',
                      'tag' : [{'name': 'Summary',  'tag' : summary_tags} ,
                               {'name': 'seed-resource', 'type':'image', 'value':self.resource_url,
                                'gobject' : [{ 'name': 'seeds', 'type' : 'seedsize', 'gobject':gobs }], }],
                      }]
        else:
            submexes = self._get_submexes()
            tags = [
                { 'name': 'execute_options',
                  'tag' : [ {'name': 'iterable', 'value' : 'image_url' } ]
                  },
                { 'name': 'outputs',
                  'tag' : [{'name': 'Summary',  'tag' : summary_tags },
                           {'name': 'mex_url', 'value': self.mex_url, 'type': 'mex'},
                           {'name': 'image_url', 'type':'dataset', 'value':self.resource_url,}]
                  },
                    ]
            # for i, submex in enumerate(mexlist):
            #     tag, image_url = gettag(submex, 'image_url')
            #     gob, gob_url = gettag(submex, 'SeedSize')
            #     mexlink = { 'name' : 'submex',
            #                 'tag'  : [{ 'name':'mex_url', 'value':submex.get('uri')},
            #                           { 'name':'image_url', 'value' : image_url},
            #                           { 'name':'gobject_url', 'value' : gob.get('uri') } ]
            #                 }
            #     tags.append(mexlink)
        self.bq.finish_mex(tags = tags, gobjects = gobjects, children= [('mex', submexes)])
        return 0


    def _get_submexes(self):
        submex = []
        localfiles = glob.glob(os.path.join(self.images, '*C.csv'))
        result2url = dict( (os.path.splitext(f)[0] + 'C.csv', u) for f, u in self.file2url.items())
        for result in localfiles:
            gobs = self._read_results(result)
            if result not in result2url:
                logging.error ("Can't find url for %s given files %s and map %s" %
                           result, localfiles, result2url)
            mex = { 'type' : self.bq.mex.type,
                    'name' : self.bq.mex.name,
                    'value': 'FINISHED',
                    'tag': [ { 'name': 'inputs',
                               'tag' : [ {'name': 'image_url', 'value' : result2url [result] } ]
                             },
                             { 'name': 'outputs',
                              'tag' : [{'name': 'seed-resource', 'type':'image', 'value':  result2url [result],
                                        'gobject':{ 'name': 'seeds', 'type': 'seedsize', 'gobject': gobs}, }] }]
                    }
            submex.append (mex)
        return submex

        #url = self.bq.service_url('data_service', 'mex', query={ 'view' : 'deep' })
        #response = self.bq.postxml(url, d2xml({'request' : {'mex': submex}} ))
        #return response


    def _read_summary(self, csvfile):
        #%mean(area), mean(minoraxislen), mean(majoraxislen), standarddev(area),
        #standarddev(minoraxislen), standarddev(majoraxislen), total seedcount,
        #mean thresholdused, weighted mean of percentclusters1, weighted mean of percentclusters2
        f= open(csvfile,'rb')
        rows = csv.reader (f)
        tag_names = [ 'mean_area', 'mean_minoraxis', 'mean_majoraxis',
                      'std_area', 'std_minoraxis', 'std_majoraxis',
                      'seedcount',
                      'mean_threshhold',
                      'weighted_mean_cluster_1','weighted_mean_cluster_2',
                      ]

        # Read one row(rows.next()) and zip ( name, col) unpacking in d2xml format
        summary_tags = [ { 'name': n[0], 'value' : n[1] }
                         for n in itertools.izip(tag_names, rows.next()) ]
        f.close()

        return summary_tags

    def _read_results(self, csvfile):
        results  = []
        f= open(csvfile,'rb')
        rows = csv.reader (f)
        for col in rows:
            results.append( {
                    'type' : 'seed',
                    'tag' : [ { 'name': 'area', 'value': col[0]},
                              { 'name': 'major', 'value': col[2]},
                              { 'name': 'minor', 'value': col[1]} ],
                    'ellipse' : {
                        'vertex' : [ { 'x': col[3], 'y':col[4], 'index':0 },
                                     { 'x': float(col[3]) - abs(float(col[8]) - float(col[3])), 'y':col[9], 'index':1 },
                                     { 'x': col[6], 'y':col[7], 'index':2 }]
                        }
                    })
        f.close()
        return results



    def run(self):
        logging.basicConfig(level=logging.DEBUG)

        parser  = optparse.OptionParser()
        parser.add_option('-d','--debug', action="store_true")
        parser.add_option('-n','--dryrun', action="store_true")
        #parser.add_option('--resource_url')
        #parser.add_option('--mex_url')
        #parser.add_option('--staging_path')
        #parser.add_option('--bisque_token')
        #parser.add_option('--credentials')

        # Parse named arguments from list


        (options, args) = parser.parse_args()
        named_args =dict( [ y for y in [ x.split ('=') for x in args ] if len (y) == 2] )
        args  =  [  x for x in args if '=' not in x  ]

        staging_path = '.'
        self.auth_token = named_args.get ('bisque_token')
        self.image_map_name = os.path.join(staging_path, IMAGE_MAP)
        self.resource_url = named_args.get ('image_url')
        self.mex_url = named_args.get ('mex_url')
        self.images = os.path.join(staging_path, 'images') + os.sep


        if self.auth_token:
            self.bq = BQSession().init_mex(self.mex_url, self.auth_token)
        else:
            user,pwd = options.credentials.split(':')
            self.bq = BQSession().init_local(user,pwd)

        resource_xml = self.bq.fetchxml (self.resource_url, view='short')
        self.is_dataset = resource_xml.tag == 'dataset'

        if len(args) == 1:
            commands = [ args.pop(0)]
        else:
            commands =['setup','start', 'teardown']

        #if command not in ('setup','teardown', 'start'):
        #    parser.error('Command must be start, setup or teardown')


        # maltab code requires trailing slash..

        try:
            for command in commands:
                command = getattr(self, command)
                r = command()
        except Exception, e:
            logging.exception ("problem during %s" % command)
            self.bq.fail_mex(msg = "Exception during %s: %s" % (command,  str(e)))
            sys.exit(1)

        sys.exit(r)
Exemple #5
0
image_uri = sys.argv[1]

config = ConfigParser.ConfigParser()
config.read('config.cfg')

root = config.get('Host', 'root') or 'localhost:8080'
user = config.get('Host', 'user') or 'test'
pswd = config.get('Host', 'password') or 'test'

session = BQSession().init_local(user,
                                 pswd,
                                 bisque_root=root,
                                 create_mex=False)

# fetch image sizes
meta = session.fetchxml('%s?meta' %
                        image_uri.replace('data_service', 'image_service'))
w = int(meta.xpath('//tag[@name="image_num_x"]')[0].get('value'))
h = int(meta.xpath('//tag[@name="image_num_y"]')[0].get('value'))
z = int(meta.xpath('//tag[@name="image_num_z"]')[0].get('value'))
t = int(meta.xpath('//tag[@name="image_num_t"]')[0].get('value'))

# create mex
mex = etree.Element('mex',
                    name='ExternalAnnotator %s' % num_gobs,
                    value='FINISHED')

inputs = etree.SubElement(mex, 'tag', name='inputs')
etree.SubElement(inputs,
                 'tag',
                 name='resource_url',
                 type='image',
Exemple #6
0
class BaseRunner(object):
    """Base runner for ways of running a module in some runtime evironment.

    Runtime environments include the command line, condor and hadoop

    Each runner basically prepares the module environment, runs or stops
    modules, and allows the status of a run to be queried

    Runners interact with module environments (see base_env)

    For each module it is expected that custom  launcher will be written.
    A simple example might be:
       from bisque.util.launcher import Launcher
       class MyModuleLauncher (Launcher):
           execute='myrealmodule'
            ...

       if __name__ == "__main__":
           MyModuleLauncher().main()


    The engine will launch a subprocess with the following template:

      launcher arg1 arg2 arg3 mex=http://somehost/ms/mex/1212 start

    The launcher code will strip off the last 2 arguments and
    pass the other arguments to the real module.

    The last argument is command it must be one of the following
       start, stop, status

    """
    name = "Base"
    env = None  # A mapping of environment variables (or Inherit)
    executable = []  # A command line list of the arguments
    environments = []
    launcher = None  # Use Default launcher
    mexhandled = "true"

    def __init__(self, **kw):
        self.parser = optparse.OptionParser()
        self.parser.add_option("-n",
                               "--dryrun",
                               action="store_true",
                               default=False)
        self.parser.add_option('-v',
                               '--verbose',
                               action="store_true",
                               default=False)
        self.parser.add_option('-d',
                               '--debug',
                               action="store_true",
                               default=False)
        self.session = None
        self.process_environment = dict(os.environ)
        self.log = logging.getLogger("bq.engine_service.BaseRunner")
        self.mexid = None
        self.mexes = []
        self.prerun = None
        self.postrun = None
        self.entrypoint_executable = None
        self.iterables = None

    def debug(self, msg, *args):
        #if self.options.verbose:
        self.log.debug(msg, *args)

    def info(self, msg, *args):
        self.log.info(msg, *args)

    def error(self, msg, *args):
        self.log.error(msg, *args)

    ###########################################
    # Config
    def read_config(self, **kw):
        """Initial state of a runner.  The module-runtime.cfg is read
        and the relevent runner section is applied.  The environment
        list is created and setup in order to construct the
        environments next
        """
        self.config = AttrDict(executable="", environments="")
        self.sections = {}
        self.debug("BaseRunner: read_config")
        # Load any bisque related variable into this runner
        runtime_bisque_cfg = find_config_path('runtime-bisque.cfg')
        if os.path.exists(runtime_bisque_cfg):
            self.bisque_cfg = ConfigFile(runtime_bisque_cfg)
            self.load_section(None, self.bisque_cfg)
            self.config['files'] = self.config.setdefault(
                'files', '') + ',' + runtime_bisque_cfg
        else:
            self.info("BaseRunner: missing runtime-bisque.cfg")

        module_dir = kw.get('module_dir', os.getcwd())
        runtime_module_cfg = os.path.join(module_dir, 'runtime-module.cfg')
        if not os.path.exists(runtime_module_cfg):
            self.info("BaseRunner: missing %s", runtime_module_cfg)
            return
        self.module_cfg = ConfigFile(runtime_module_cfg)
        # Process Command section
        self.load_section(None, self.module_cfg)  # Globals

    def load_section(self, name, cfg):
        name = name or "__GLOBAL__"
        section = self.sections.setdefault(name, {})
        section.update(cfg.get(name, asdict=True))
        self.config.update(section)
        #for k,v in section.items():
        #    setattr(self,k,v)

    #########################################################
    # Helpers
    def create_environments(self, **kw):
        """Build the set of environments listed by the module config"""
        self.environments = strtolist(self.config.environments)
        envs = []
        for name in self.environments:
            env = ENV_MAP.get(name, None)
            if env is not None:
                envs.append(env(runner=self))
                continue
            self.log.warn('Unknown environment: %s ignoring', name)
        self.environments = envs
        self.info('created environments %s', envs)

    def process_config(self, **kw):
        """Configuration occurs in several passes.
        1.  read config file and associated runtime section
             create environments
        2.   Process the config values converting types
        """
        self.mexhandled = strtobool(self.mexhandled)

        # Find any configuration parameters in the envs
        # and add them to the class
        for env in self.environments:
            env.process_config(self)

    def setup_environments(self, **kw):
        """Call setup_environment during "start" processing
           Prepares environment and returns defined environment variable to be passed to jobs
        """
        self.debug("setup_environments: %s", kw)
        process_env = self.process_environment.copy()
        for env in self.environments:
            env.setup_environment(self, **kw)
        self.process_environment = process_env

    # Run during finish
    def teardown_environments(self, **kw):
        'Call teardown_environment during "finish" processing'
        for env in reversed(self.environments):
            env.teardown_environment(self, **kw)

    ##########################################
    # Initialize run state
    def init_runstate(self, arguments, **kw):
        """Initialize mex and module and deal with other arguments.
        """
        mex_tree = kw.pop('mex_tree', None)
        module_tree = kw.pop('module_tree', None)
        bisque_token = kw.pop('bisque_token', None)
        module_dir = kw.pop('module_dir', os.getcwd())

        # ---- the next items are preserved across execution phases ----
        # list of dict representing each mex : variables and arguments
        self.mexes = []
        self.rundir = module_dir
        self.outputs = []
        self.entrypoint_executable = []
        self.prerun = None
        self.postrun = None
        #self.process_environment (pre-set)
        #self.options (pre-set)
        # ---- the previous items are preserved across execution phases ----

        # Add remaining arguments to the executable line
        # Ensure the loaded executable is a list
        if isinstance(self.config.executable, str):
            executable = shlex.split(self.config.executable)
        #self.executable.extend (arguments)

        topmex = AttrDict(self.config)
        topmex.update(
            dict(
                named_args={},
                executable=list(executable),
                #                           arguments = [],
                mex_url=mex_tree is not None and mex_tree.get('uri') or None,
                bisque_token=bisque_token,
                staging_path=None,
                rundir=self.rundir))
        topmex.mex_id = topmex.mex_url.rsplit('/',
                                              1)[1] if topmex.mex_url else ''
        self.mexes.append(topmex)

        # Scan argument looking for named arguments (these are used by condor_dag to re-initialize mex vars)
        for arg in arguments:
            tag, sep, val = arg.partition('=')
            if sep == '=':
                topmex.named_args[tag] = val
                if tag in topmex:
                    topmex[tag] = val

        # Pull out arguments from mex
        if mex_tree is not None and module_tree is not None:
            mexparser = MexParser()
            mex_inputs = mexparser.prepare_inputs(module_tree, mex_tree,
                                                  bisque_token)
            module_options = mexparser.prepare_options(module_tree, mex_tree)
            self.outputs = mexparser.prepare_outputs(module_tree, mex_tree)

            argument_style = module_options.get('argument_style', 'positional')
            # see if we have pre/postrun option
            self.prerun = module_options.get('prerun_entrypoint', None)
            self.postrun = module_options.get('postrun_entrypoint', None)

            topmex.named_args.update(mexparser.prepare_mex_params(mex_inputs))
            topmex.executable.extend(
                mexparser.prepare_mex_params(mex_inputs, argument_style))
            topmex.rundir = self.rundir
            #topmex.options = module_options
            # remember topmex executable for pre/post runs

            # Create a nested list of  arguments  (in case of submex)
            submexes = mex_tree.xpath('/mex/mex')
            for mex in submexes:
                sub_inputs = mexparser.prepare_inputs(module_tree, mex)
                submex = AttrDict(self.config)
                submex.update(
                    dict(
                        named_args=dict(topmex.named_args),
                        #                                   arguments =list(topmex.arguments),
                        executable=list(executable),  #+ topmex.arguments,
                        mex_url=mex.get('uri'),
                        mex_id=mex.get('uri').rsplit('/', 1)[1],
                        bisque_token=bisque_token,
                        staging_path=None,
                        rundir=self.rundir))
                #if argument_style == 'named':
                #    submex.named_args.update ( [x.split('=') for x in sub_inputs] )
                submex.named_args.update(
                    mexparser.prepare_mex_params(sub_inputs))
                submex.executable.extend(
                    mexparser.prepare_mex_params(sub_inputs, argument_style))
                self.mexes.append(submex)
            # Submex's imply that we are iterated.
            # We can set up some options here and remove any execution
            # for the top mex.
            topmex.iterables = len(
                self.mexes) > 1 and mexparser.process_iterables(
                    module_tree, mex_tree)

        self.info("processing %d mexes -> %s" % (len(self.mexes), self.mexes))

    def store_runstate(self):
        staging_path = self.mexes[0].get('staging_path') or '.'
        state_file = "state%s.bq" % self.mexes[0].get('mex_id', '')
        # pickle the object variables
        with open(os.path.join(staging_path, state_file), 'wb') as f:
            pickle.dump(self.mexes, f)
            pickle.dump(self.rundir, f)
            pickle.dump([
                et.tostring(tree)
                for tree in self.outputs if tree and tree.tag == 'tag'
            ], f)
            pickle.dump(self.entrypoint_executable, f)
            pickle.dump(self.prerun, f)
            pickle.dump(self.postrun, f)
            pickle.dump(self.process_environment, f)
            pickle.dump(self.options, f)

    def load_runstate(self):
        staging_path = self.mexes[0].get('staging_path') or '.'
        state_file = "state%s.bq" % self.mexes[0].get('mex_id', '')
        # entered in a later processing phase (e.g., Condor finish) => unpickle
        with open(os.path.join(staging_path, state_file), 'rb') as f:
            #self.pool = None   # not preserved for now
            self.mexes = pickle.load(f)
            self.rundir = pickle.load(f)
            self.outputs = [et.fromstring(tree) for tree in pickle.load(f)]
            self.entrypoint_executable = pickle.load(f)
            self.prerun = pickle.load(f)
            self.postrun = pickle.load(f)
            self.process_environment = pickle.load(f)
            self.options = pickle.load(f)

    ##################################################
    # Command sections
    # A command is launched with last argument on the command line of the launcher
    # Each command must return the either the next fommand to run or None to stop
    # Derived classes should overload these functions

    def command_start(self, **kw):
        self.info("starting %d mexes -> %s", len(self.mexes), self.mexes)
        if self.session is None:
            self.session = BQSession().init_mex(self.mexes[0].mex_url,
                                                self.mexes[0].bisque_token)
        status = "starting"
        self.entrypoint_executable = self.mexes[0].executable
        if self.mexes[0].iterables:
            self.mexes[0].executable = None
            status = 'running parallel'
        # add empty "outputs" section in topmex
        #self.session.update_mex(status=status, tags=[{'name':'outputs'}])
        self.session.update_mex(
            status=status
        )  # dima: modules add outputs section and the empty one complicates module UI
        # if there is a prerun, run it now
        if self.prerun:
            self.info("prerun starting")
            self.command_single_entrypoint(self.prerun, self.command_execute,
                                           **kw)
            return None

        return self.command_execute

    def command_execute(self, **kw):
        """Execute the internal executable"""
        return self.command_finish

    def command_finish(self, **kw):
        """Cleanup the environment and perform any needed actions
        after the module completion
        """
        self.info("finishing %d mexes -> %s", len(self.mexes), self.mexes)

        # if there is a postrun (aka "reduce phase"), run it now
        if self.postrun:
            self.info("postrun starting")
            self.command_single_entrypoint(self.postrun, self.command_finish2,
                                           **kw)
            return None

        self.command_finish2(**kw)

    def command_finish2(self, **kw):
        self.teardown_environments()

        if self.mexes[0].iterables:
            if self.session is None:
                self.session = BQSession().init_mex(self.mexes[0].mex_url,
                                                    self.mexes[0].bisque_token)
            # outputs
            #   mex_url
            #   dataset_url  (if present and no list/range iterable)
            #   multiparam   (if at least one list/range iterable)
            need_multiparam = False
            for iter_name, iter_val, iter_type in self.mexes[0].iterables:
                if iter_type in ['list', 'range']:
                    need_multiparam = True
                    break
            outtag = et.Element('tag', name='outputs')
            if need_multiparam:
                # some list/range params => add single multiparam element if allowed by module def
                multiparam_name = None
                for output in self.outputs:
                    if output.get('type', '') == 'multiparam':
                        multiparam_name = output.get('name')
                        break
                if multiparam_name:
                    # get inputs section for some submex to read actual types later
                    # TODO: this can be simplified with xpath_query in 0.6
                    inputs_subtree = None
                    if len(self.mexes) > 1:
                        submextree = self.session.fetchxml(
                            self.mexes[1].mex_url,
                            view='full')  # get latest MEX doc
                        inputs_subtree = submextree.xpath(
                            './tag[@name="inputs"]')
                        inputs_subtree = inputs_subtree and self.session.fetchxml(
                            inputs_subtree[0].get('uri'), view='deep')
                    multitag = et.SubElement(outtag,
                                             'tag',
                                             name=multiparam_name,
                                             type='multiparam')
                    colnames = et.SubElement(multitag, 'tag', name='title')
                    coltypes = et.SubElement(multitag, 'tag', name='xmap')
                    colxpaths = et.SubElement(multitag, 'tag', name='xpath')
                    et.SubElement(multitag,
                                  'tag',
                                  name='xreduce',
                                  value='vector')
                    for iter_name, iter_val, iter_type in self.mexes[
                            0].iterables:
                        actual_type = inputs_subtree and inputs_subtree.xpath(
                            './/tag[@name="%s" and @type]/@type'
                        )  # read actual types from any submex
                        actual_type = actual_type[
                            0] if actual_type else 'string'
                        et.SubElement(colnames, 'value', value=iter_name)
                        et.SubElement(coltypes,
                                      'value',
                                      value="tag-value-%s" % actual_type)
                        et.SubElement(colxpaths,
                                      'value',
                                      value='./mex//tag[@name="%s"]' %
                                      iter_name)
                    # last column is the submex URI
                    et.SubElement(colnames, 'value', value="submex_uri")
                    et.SubElement(coltypes, 'value', value="resource-uri")
                    et.SubElement(colxpaths, 'value', value='./mex')
                else:
                    self.log.warn(
                        "List or range parameters in Mex but no multiparam output tag in Module"
                    )
            else:
                # no list/range params => add iterables as always
                for iter_name, iter_val, iter_type in self.mexes[0].iterables:
                    et.SubElement(outtag,
                                  'tag',
                                  name=iter_name,
                                  value=iter_val,
                                  type=iter_type)
            et.SubElement(outtag,
                          'tag',
                          name='mex_url',
                          value=self.mexes[0].mex_url,
                          type='mex')

            self.session.finish_mex(tags=[outtag])
        return None

    def command_single_entrypoint(self, entrypoint, callback, **kw):
        return None

    def command_kill(self, **kw):
        """Kill the running module if possible
        """
        return False

    def command_status(self, **kw):
        return None

    def check(self, module_tree=None, **kw):
        "check whether the module seems to be runnable"
        self.read_config(**kw)
        # check for a disabled module
        enabled = self.config.get('module_enabled', 'true').lower() == "true"
        if not enabled:
            self.info('Module is disabled')
            return False
        # Add remaining arguments to the executable line
        # Ensure the loaded executable is a list
        if isinstance(self.config.executable, str):
            executable = shlex.split(self.config.executable)
        if os.name == 'nt':
            return True
        canrun = executable and which(executable[0]) is not None
        if not canrun:
            self.error("Executable cannot be run %s" % executable)
        return canrun

    def main(self, **kw):
        # Find and read a config file for the module
        created_pool = False
        try:
            self.pool = kw.pop('pool', None)
            if self.pool is None:
                self.pool = ProcessManager(POOL_SIZE)
                created_pool = True

            self.read_config(**kw)

            args = kw.pop('arguments', None)
            # Pull out command line arguments
            self.options, arguments = self.parser.parse_args(args)
            command_str = arguments.pop()

            # the following always has to run first so that e.g., staging dirs are set up properly
            self.init_runstate(arguments, **kw)
            self.create_environments(**kw)
            self.process_config(**kw)

            if command_str == 'start':
                # new run => setup environments from scratch
                self.setup_environments()
            else:
                # continued run => load saved run state
                try:
                    self.load_runstate()
                except OSError:
                    # could not load state => OK if it was command that can run without previous 'start' (e.g., 'kill')
                    if command_str not in ['kill']:
                        raise

            self.mexes[0].arguments = arguments

            command = getattr(self, 'command_%s' % command_str, None)
            while command:
                self.info("COMMAND_RUNNER %s" % command)
                command = command(**kw)

            if command_str not in ['kill']:
                # store run state since we may come back (e.g., condor finish)
                self.store_runstate()

            if created_pool:
                self.pool.stop()

            return 0
        except ModuleEnvironmentError, e:
            self.log.exception("Problem occured in module")
            raise RunnerException(str(e), self.mexes)
        except RunnerException, e:
            raise