class BotanicamTrainer(object): """ Botanicam Trainer Module """ def mex_parameter_parser(self, mex_xml): """ Parses input of the xml and add it to BotanicamTrainer's options attribute @param: mex_xml """ mex_inputs = mex_xml.xpath('tag[@name="inputs"]') if mex_inputs: for tag in mex_inputs[0]: if tag.tag == 'tag' and tag.attrib[ 'type'] != 'system-input': #skip system input values log.debug('Set options with %s as %s' % (tag.attrib['name'], tag.attrib['value'])) setattr(self.options, tag.attrib['name'], tag.attrib['value']) else: log.debug('BotanicamFS: No Inputs Found on MEX!') def validate_input(self): """ Check to see if a mex with token or user with password was provided. @return True is returned if validation credention was provided else False is returned """ if (self.options.mexURL and self.options.token): #run module through engine service return True if (self.options.user and self.options.pwd and self.options.root): #run module locally (note: to test module) return True log.debug( 'BotanicamTrainer: Insufficient options or arguments to start this module' ) return False def setup(self): """ Fetches the mex and appends input_configurations to the option attribute of BotanicamTrainer """ #initalizes if user and password are provided if (self.options.user and self.options.pwd and self.options.root): self.bqSession = BQSession().init_local( self.options.user, self.options.pwd, bisque_root=self.options.root) self.options.mexURL = self.bqSession.mex.uri #initalizes if mex and mex token is provided elif (self.options.mexURL and self.options.token): self.bqSession = BQSession().init_mex(self.options.mexURL, self.options.token) else: raise BotanicamTrainerError( 'BotanicamTrainer: Insufficient options or arguments to start this module' ) self.bqSession.update_mex('Initializing...') self.mex_parameter_parser(self.bqSession.mex.xmltree) return def run(self): """ The core of the Botanicam Trainer Parses the tags and find all the corresponding values to from classes. Classes that have no images in them are removed. Features are requested on all the images and then trained using the tag value classes. The resulting model file is stored on bisque as a zip file. """ #retieve tags self.bqSession.update_mex('Parse Tags...') if not self.options.tag_names: raise BotanicamTrainerError('Tags are a required input!') self.tag_names = self.options.tag_names.split(',') #type check resource_short = self.bqSession.fetchxml(self.options.resource_url, view='short') if resource_short.tag == 'dataset': resource_url_values = '%s/value' % self.options.resource_url else: resource_url_values = self.options.resource_url all_images = self.bqSession.fetchxml(resource_url_values, view='full,clean') tag_query_list = [] for name in self.tag_names: name_list = [] for u in np.unique( all_images.xpath('image/tag[@name="%s"]/@value' % name)): name_list.append('"%s":"%s"' % (name, u)) tag_query_list.append(name_list) #need to find the unique values to create lists of images #hopefully the tag names and lists are not too complicated tag_query_list = [ list(element) for element in itertools.product(*tag_query_list) ] #cartesian product self.complete_tag_list = [] tag_query_url_list = [] #search for classes with images #query all the values to see if images return #removing query_tag from the resource_url and adding it back in later resource_url_wo_query = resource_url_values resource_query = None from urlparse import urlsplit, urlunsplit, parse_qs from urllib import urlencode o = urlsplit(resource_url_values) q = parse_qs(o.query) if q.get('tag_query'): resource_query = q['tag_query'] del q['tag_query'] query = urlencode(q) resource_url_wo_query = urlunsplit( (o.scheme, o.netloc, o.path, query, o.fragment)) log.debug(tag_query_list) for tag_query in tag_query_list: encoded_tag_query = tag_query if resource_query: encoded_tag_query += resource_query #adding back in the query_tag from the resource_url encoded_tag_query = map(urllib.quote, tag_query) encoded_tag_query = '%s' % ' AND '.join(encoded_tag_query) query_xml = self.bqSession.fetchxml(resource_url_wo_query, tag_query=encoded_tag_query, view='full,clean') if len(query_xml.xpath('image')) > 0: name_value_pairs = {} for t in tag_query: #create dictionary of clases with list of name value pairs m = re.match('"(?P<name>[^"]+)":"(?P<value>[^"]+)"', t) name_value_pairs[m.group('name')] = m.group('value') self.complete_tag_list.append(name_value_pairs) tag_query_url_list.append(query_xml.attrib['uri']) feature_length = Feature().length(self.bqSession, FEATURE_NAME) if len(tag_query_url_list) < 2: raise BotanicamTrainerError( 'Requires atleast 2 classes to train found %s' % len(tag_query_url_list)) #extracts all the features and then appends it to a larger table _mkdir(os.path.join(self.options.stagingPath, FEATURE_TABLE_DIR)) main_table = os.path.join(self.options.stagingPath, FEATURE_TABLE_DIR, 'feature_table.h5') with tables.open_file(main_table, 'w') as h5file: columns = { 'label': tables.Int64Col(), 'feature': tables.Float32Col(shape=(feature_length)) } table = h5file.create_table('/', 'values', columns) table.flush() self.bqSession.update_mex('Calculated features on (0/%s) spieces...' % (len(tag_query_url_list))) for i, tag_url_query in enumerate(tag_query_url_list): try: vectors = extract_bush_feature( self.bqSession, tag_url_query) #may need to be moved into local temp feature_table = vectors.root.values with tables.open_file(main_table, 'a') as h5file: table = h5file.root.values r = table.row for fr in feature_table: r['feature'] = fr['feature'] r['label'] = i r.append() table.flush() vectors.close() os.remove(vectors.filename) except FeatureError as e: raise BotanicamTrainerError(str(e)) self.bqSession.update_mex( 'Calculated features on (%s/%s) spieces...' % (i + 1, len(tag_query_url_list))) self.bqSession.update_mex('Classifying') log.debug('Training model') #classify the features pca = RandomizedPCA(whiten=True) clf = svm.SVC() with tables.open_file(main_table, 'r') as h5file: table = h5file.root.values pca.fit(table[:]['feature']) clf.fit(pca.transform(table[:]['feature']), table[:]['label']) self.bqSession.update_mex('Posting model to bisque...') log.debug('Storing and Zipping model') self.model_dir = os.path.join(self.options.stagingPath, MODEL_DIR) self.svm_model_file = os.path.join(self.options.stagingPath, MODEL_DIR, 'svm_model') self.pca_model_file = os.path.join(self.options.stagingPath, MODEL_DIR, 'pca_model') _mkdir(self.model_dir) svm_files = joblib.dump(clf, self.svm_model_file) pca_files = joblib.dump(pca, self.pca_model_file) #zip file import zipfile with zipfile.ZipFile('%s.zip' % self.model_dir, 'w') as fzip: for f in svm_files: fzip.write(f, os.path.basename(f)) for f in pca_files: fzip.write(f, os.path.basename(f)) def teardown(self): """ Post the results to the mex xml. """ #save the model and upload it to the data service with all the meta data self.bqSession.update_mex('Returning results...') #self.bqSession.update_mex('Returning home after a long day...') #constructing and storing model file #does not accept no name on the resource cl_model = etree.Element('resource', name=os.path.basename('%s.zip' % MODEL_DIR)) #classes tag_classes = etree.SubElement(cl_model, 'tag', name='Classes') for i, name_value_pairs in enumerate(self.complete_tag_list): tag_labels = etree.SubElement(tag_classes, 'tag', name='Class_%s' % str(i), value=str(i)) for n in name_value_pairs.keys(): etree.SubElement(tag_labels, 'tag', name=n, value=name_value_pairs[n]) etree.SubElement(cl_model, 'tag', name='Number of Classes', value=str(len(self.complete_tag_list))) #module identifier (a descriptor to be found by the botanicam model) etree.SubElement(cl_model, 'tag', name='module_identifier', value='Botanicam') #model filename etree.SubElement(cl_model, 'tag', name='filename', value=os.path.basename('%s.zip' % MODEL_DIR)) #classification method etree.SubElement(cl_model, 'tag', name='Classification Method', value='Bush Descriptor') #Resource url etree.SubElement(cl_model, 'tag', name='Resource', value=self.options.resource_url) #description etree.SubElement(cl_model, 'tag', name='description', value='Model File for the Botanicam Module') #storing the model file in blobservice r = self.bqSession.postblob('%s.zip' % self.model_dir, xml=cl_model) r_xml = etree.fromstring(r) outputTag = etree.Element('tag', name='outputs') etree.SubElement(outputTag, 'tag', name='classification_file', value=r_xml[0].attrib['uri'], type='model_viewer') self.bqSession.finish_mex(tags=[outputTag]) self.bqSession.close() def main(self): """ The main function that runs everything """ log.debug('sysargv : %s' % sys.argv) parser = OptionParser() parser.add_option('--resource_url', dest="resource_url") #for now only datasets parser.add_option('--Tags', dest="tag_names") #labels on the images parser.add_option('--ClassifierMethod', dest="classifier_methods") parser.add_option('--FeatureName', dest="feature_name") parser.add_option('--mex_url', dest="mexURL") parser.add_option('--module_dir', dest="modulePath") parser.add_option('--staging_path', dest="stagingPath") parser.add_option('--bisque_token', dest="token") parser.add_option('--user', dest="user") parser.add_option('--pwd', dest="pwd") parser.add_option('--root', dest="root") (options, args) = parser.parse_args() try: #pull out the mex if not options.mexURL: options.mexURL = sys.argv[1] if not options.token: options.token = sys.argv[2] except IndexError: #no argv were set pass if not options.stagingPath: options.stagingPath = '' log.debug('\n\nPARAMS : %s \n\n Options: %s' % (args, options)) self.options = options if self.validate_input(): try: self.setup() except Exception as e: log.exception("Exception during setup") self.bqSession.fail_mex(msg="Exception during setup: %s" % str(e)) return try: self.run() except (Exception, BotanicamTrainerError) as e: log.exception("Exception during run") self.bqSession.fail_mex(msg="Exception during run: %s" % str(e)) return try: self.teardown() except (Exception, BotanicamTrainerError) as e: log.exception("Exception during teardown %s") self.bqSession.fail_mex(msg="Exception during teardown: %s" % str(e)) return
class PythonScriptWrapper(object): def run(self): """ Run Python script """ session = self.bqSession # call script outputs = run_script( session, log, **self.options.__dict__ ) # save output back to BisQue for output in outputs: self.output_resources.append(output) def setup(self): """ Pre-run initialization """ self.bqSession.update_mex('Initializing...') self.mex_parameter_parser(self.bqSession.mex.xmltree) self.output_resources = [] def teardown(self): """ Post the results to the mex xml """ self.bqSession.update_mex( 'Returning results') outputTag = etree.Element('tag', name ='outputs') for r_xml in self.output_resources: if isinstance(r_xml, basestring): r_xml = etree.fromstring(r_xml) #res_type = r_xml.get('type', None) or r_xml.get('resource_type', None) or r_xml.tag # append reference to output outputTag.append(r_xml) self.bqSession.finish_mex(tags=[outputTag]) def mex_parameter_parser(self, mex_xml): """ Parses input of the xml and add it to options attribute (unless already set) @param: mex_xml """ # inputs are all non-"script_params" under "inputs" and all params under "script_params" mex_inputs = mex_xml.xpath('tag[@name="inputs"]/tag[@name!="script_params"] | tag[@name="inputs"]/tag[@name="script_params"]/tag') if mex_inputs: for tag in mex_inputs: if tag.tag == 'tag' and tag.get('type', '') != 'system-input': #skip system input values if not getattr(self.options,tag.get('name', ''), None): log.debug('Set options with %s as %s'%(tag.get('name',''),tag.get('value',''))) setattr(self.options,tag.get('name',''),tag.get('value','')) else: log.debug('No Inputs Found on MEX!') def validate_input(self): """ Check to see if a mex with token or user with password was provided. @return True is returned if validation credention was provided else False is returned """ if (self.options.mexURL and self.options.token): #run module through engine service return True if (self.options.user and self.options.pwd and self.options.root): #run module locally (note: to test module) return True log.debug('Insufficient options or arguments to start this module') return False def collect_outputs(self): """ Perform reduce phase (i.e., examine final (top) mex and create any additional outputs based on submexes) """ top_mex = self.bqSession.fetchxml(self.options.mexURL, view='deep') # add output tag if needed outputTag = top_mex.xpath('/mex/tag[@name="outputs"]') if not outputTag: # no "outputs" tag in mex => add it now etree.SubElement(top_mex, 'tag', name='outputs') top_mex = self.bqSession.postxml(url=top_mex.get('uri'), xml=top_mex, view='deep') outputTag = top_mex.xpath('/mex/tag[@name="outputs"]') outtag = outputTag[0] # add multi-param output table based on submex outputs # from inputs, collect everything except mex_url and bisque_token # from outputs/statistics, collect everything except "meanMaxMisorient" multiparam_name = 'output_table' multitag = etree.SubElement(outtag, 'tag', name=multiparam_name, type='multiparam') colnames = etree.SubElement(multitag, 'tag', name='title') coltypes = etree.SubElement(multitag, 'tag', name='xmap') colxpaths = etree.SubElement(multitag, 'tag', name='xpath') etree.SubElement(multitag, 'tag', name='xreduce', value='vector') inputs = top_mex.xpath('/mex/mex/tag[@name="inputs"]')[0] for inp in inputs.xpath('./tag'): if inp.get('name') not in ['mex_url', 'bisque_token']: etree.SubElement(colnames, 'value', value=inp.get('name')) etree.SubElement(coltypes, 'value', value=self._get_type(inp)) etree.SubElement(colxpaths, 'value', value='/mex/mex/tag[@name="inputs"]/tag[@name="%s"]' % inp.get('name')) outputs = top_mex.xpath('/mex/mex/tag[@name="outputs"]')[0] for outp in outputs.xpath('./tag[@name="statistics"]/tag'): if outp.get('name') not in ['meanMaxMisorient']: etree.SubElement(colnames, 'value', value=outp.get('name')) etree.SubElement(coltypes, 'value', value=self._get_type(outp)) etree.SubElement(colxpaths, 'value', value='/mex/mex/tag[@name="outputs"]/tag[@name="statistics"]/tag[@name="%s"]' % outp.get('name')) # last column is the submex URI etree.SubElement(colnames, 'value', value="submex_uri") etree.SubElement(coltypes, 'value', value="resource-uri") etree.SubElement(colxpaths, 'value', value='./mex') # write back to mex self.bqSession.postxml(url=outtag.get('uri'), xml=outtag) def _get_type(self, inp): if inp.get('name') == 'dream3d_run': return 'resource-uri' else: inptype = inp.get('type', 'string') if inptype == 'combo': inptype = 'string' return "tag-value-%s" % inptype def main(self): parser = optparse.OptionParser() parser.add_option('--mex_url' , dest="mexURL") parser.add_option('--module_dir' , dest="modulePath") parser.add_option('--staging_path' , dest="stagingPath") parser.add_option('--bisque_token' , dest="token") parser.add_option('--user' , dest="user") parser.add_option('--pwd' , dest="pwd") parser.add_option('--root' , dest="root") # for the reduce phase: create output dataset of output HDFs (in this case, mexURL is top mex) parser.add_option('--entrypoint' , dest="entrypoint") (options, args) = parser.parse_args() fh = logging.FileHandler('phase_%s.log' % (options.entrypoint or 'scriptrun.log'), mode='a') fh.setLevel(logging.DEBUG) formatter = logging.Formatter('[%(asctime)s] %(levelname)8s --- %(message)s ' + '(%(filename)s:%(lineno)s)',datefmt='%Y-%m-%d %H:%M:%S') fh.setFormatter(formatter) log.addHandler(fh) try: #pull out the mex if not options.mexURL: options.mexURL = sys.argv[1] if not options.token: options.token = sys.argv[2] except IndexError: #no argv were set pass if not options.stagingPath: options.stagingPath = '' log.info('\n\nPARAMS : %s \n\n Options: %s' % (args, options)) self.options = options if self.validate_input(): #initalizes if user and password are provided if (self.options.user and self.options.pwd and self.options.root): self.bqSession = BQSession().init_local( self.options.user, self.options.pwd, bisque_root=self.options.root) self.options.mexURL = self.bqSession.mex.uri #initalizes if mex and mex token is provided elif (self.options.mexURL and self.options.token): self.bqSession = BQSession().init_mex(self.options.mexURL, self.options.token) else: raise ScriptError('Insufficient options or arguments to start this module') if not self.options.entrypoint: # NOT a reduce phase => perform regular run processing try: self.setup() except Exception as e: log.exception("Exception during setup") self.bqSession.fail_mex(msg = "Exception during setup: %s" % str(e)) return try: self.run() except (Exception, ScriptError) as e: log.exception("Exception during run") self.bqSession.fail_mex(msg = "Exception during run: %s" % str(e)) return try: self.teardown() except (Exception, ScriptError) as e: log.exception("Exception during teardown") self.bqSession.fail_mex(msg = "Exception during teardown: %s" % str(e)) return else: # in a reduce phase => run reduce code if self.options.entrypoint != 'collect_outputs': self.bqSession.fail_mex(msg = "Unknown entrypoint: %s" % self.options.entrypoint) return try: self.collect_outputs() except (Exception, ScriptError) as e: log.exception("Exception during collect_outputs") self.bqSession.fail_mex(msg = "Exception during collect_outputs: %s" % str(e)) return self.bqSession.close()
class Dream3D(object): """ Dream3D Module """ def mex_parameter_parser(self, mex_xml): """ Parses input of the xml and add it to Dream3D's options attribute (unless already set) @param: mex_xml """ # inputs are all non-"pipeline params" under "inputs" and all params under "pipeline_params" mex_inputs = mex_xml.xpath( 'tag[@name="inputs"]/tag[@name!="pipeline_params"] | tag[@name="inputs"]/tag[@name="pipeline_params"]/tag' ) if mex_inputs: for tag in mex_inputs: if tag.tag == 'tag' and tag.get( 'type', '') != 'system-input': #skip system input values if not getattr(self.options, tag.get('name', ''), None): log.debug('Set options with %s as %s' % (tag.get('name', ''), tag.get('value', ''))) setattr(self.options, tag.get('name', ''), tag.get('value', '')) else: log.debug('Dream3D: No Inputs Found on MEX!') def validate_input(self): """ Check to see if a mex with token or user with password was provided. @return True is returned if validation credention was provided else False is returned """ if (self.options.mexURL and self.options.token): #run module through engine service return True if (self.options.user and self.options.pwd and self.options.root): #run module locally (note: to test module) return True log.debug( 'Dream3D: Insufficient options or arguments to start this module') return False def setup(self): """ Fetches the mex and appends input_configurations to the option attribute of Dream3D """ self.bqSession.update_mex('Initializing...') self.mex_parameter_parser(self.bqSession.mex.xmltree) self.output_resources = [] self.ppops = None self.ppops_url = None def run(self): """ The core of the Dream3D runner """ module_time = datetime.now() #retrieve tags self.bqSession.update_mex('Extracting properties') #type check hdf_resource = self.bqSession.fetchxml(self.options.InputFile, view='deep,clean') if (hdf_resource.tag != 'resource' or hdf_resource.get('resource_type', '') != 'table' ) and hdf_resource.tag != 'table': raise Dream3DError("trying to run Dream3D on non-table resource") # run prerun operations filelist_file = self._run_prerun_ops( module_time, pipeline_url=self.options.pipeline_url, input_xml=hdf_resource) # create pipeline with correct parameters pipeline_params = self.bqSession.mex.xmltree.xpath( 'tag[@name="inputs"]/tag[@name="pipeline_params"]/tag') params = {} for tag in pipeline_params: params[tag.get('name', '')] = getattr(self.options, tag.get('name', '')) pipeline_file, err_file = self._instantiate_pipeline( pipeline_url=self.options.pipeline_url, params=params) if not pipeline_file: raise Dream3DError("trying to run incompatible Dream.3D pipeline") # run Dream3D on the pipeline self.bqSession.update_mex('Running Dream3D') log.debug('run Dream3D on %s', pipeline_file) res = 1 with open(err_file, 'w') as fo: res = subprocess.call( ['/dream3d/bin/PipelineRunner', '-p', pipeline_file], stderr=fo, stdout=fo) log.debug("Dream3D returned: %s", str(res)) if res > 0: err_msg = 'pipeline execution failed\n' with open(err_file, 'r') as fo: err_msg += ''.join(fo.readlines()) if len(err_msg) > 1024: err_msg = err_msg[:512] + '...' + err_msg[-512:] raise Dream3DError(err_msg) # run postrun operations self.output_resources = self._run_postrun_ops( module_time, pipeline_url=self.options.pipeline_url) def _cache_ppops(self, pipeline_url): if not self.ppops or self.ppops_url != pipeline_url: pipeline_path = urlparse.urlsplit(pipeline_url).path.split('/') pipeline_uid = pipeline_path[1] if is_uniq_code( pipeline_path[1]) else pipeline_path[2] url = self.bqSession.service_url('pipeline', path='/'.join([pipeline_uid] + ['ppops:dream3d'])) self.ppops = json.loads(self.bqSession.c.fetch(url)) self.ppops_url = pipeline_url def _run_prerun_ops(self, module_time, pipeline_url, input_xml): """ Perform any operations necessary before the pipeline runs (e.g., download input table) and return filelist file """ self._cache_ppops(pipeline_url) post_ops = self.ppops['PreOps'] input_files = [] for op in post_ops: input_files += self._run_single_op(module_time, op, input_xml) filelist_file = os.path.join(self.options.stagingPath, 'filelist.txt') with open(filelist_file, 'w') as fo: for input_file in input_files: fo.write(input_file + '\n') return filelist_file def _run_postrun_ops(self, module_time, pipeline_url): """ Perform any operations necessary after the pipeline finished (e.g., upload result tables) and return created resources """ self._cache_ppops(pipeline_url) post_ops = self.ppops['PostOps'] created_resources = [] for op in post_ops: created_resources += self._run_single_op(module_time, op) return created_resources def _run_single_op(self, module_time, op, input_xml=None): """ Perform single pre/post operation and return list of files or resources generated """ # replace special placeholders if 'id' in op and op['id'] == '@INPUT': op['id'] = input_xml.get('resource_uniq') res = [] if op['service'] == 'postblob': # upload image or table (check op['type']) mex_id = self.bqSession.mex.uri.split('/')[-1] dt = module_time.strftime('%Y%m%dT%H%M%S') final_output_file = "ModuleExecutions/Dream3D/%s_%s_%s.h5" % ( self.options.OutputPrefix, dt, mex_id) cl_model = etree.Element('resource', resource_type=op['type'], name=final_output_file) # module identifier (a descriptor to be found by the Dream3D model) etree.SubElement(cl_model, 'tag', name='module_identifier', value='Dream3D') # hdf filename etree.SubElement(cl_model, 'tag', name='OutputFile', value=final_output_file) #description etree.SubElement(cl_model, 'tag', name='description', value='output from Dream3D Module') # post blob output_file = os.path.join(self.options.stagingPath, op['filename']) resource = self.bqSession.postblob(output_file, xml=cl_model) resource_xml = etree.fromstring(resource) res += [resource_xml[0]] elif op['service'] == 'getblob': # download table table_file = os.path.join(self.options.stagingPath, op['filename']) hdf_url = self.bqSession.service_url('blob_service', path=op['id']) self.bqSession.fetchblob(hdf_url, path=table_file) res += [table_file] return res def _instantiate_pipeline(self, pipeline_url, params): """ instantiate dream.3d pipeline file with provided parameters """ pipeline_path = urlparse.urlsplit(pipeline_url).path.split('/') pipeline_uid = pipeline_path[1] if is_uniq_code( pipeline_path[1]) else pipeline_path[2] url = self.bqSession.service_url( 'pipeline', path='/'.join( [pipeline_uid] + ["setvar:%s|%s" % (tag, params[tag]) for tag in params] + ['exbsteps:dream3d']), query={'format': 'dream3d'}) pipeline = self.bqSession.c.fetch(url) if not pipeline: # bad pipeline return None, None out_pipeline_file = os.path.join(self.options.stagingPath, 'pipeline.json') out_error_file = os.path.join(self.options.stagingPath, 'dream3d_error.txt') with open(out_pipeline_file, 'w') as fo: fo.write(pipeline) return out_pipeline_file, out_error_file def teardown(self): """ Post the results to the mex xml. """ self.bqSession.update_mex('Returning results') outputTag = etree.Element('tag', name='outputs') for r_xml in self.output_resources: res_type = r_xml.get('type', None) or r_xml.get( 'resource_type', None) or r_xml.tag if res_type == 'detected shapes': # r_xml is a set of gobjects => append to output inside image tag image_resource = self.bqSession.fetchxml( self.options.InputFile) image_elem = etree.SubElement(outputTag, 'tag', name=image_resource.get('name'), type='image', value=image_resource.get('uri')) image_elem.append(r_xml) else: # r_xml is some other resource (e.g., image or table) => append reference to output etree.SubElement(outputTag, 'tag', name='output_table' if res_type == 'table' else 'output_image', type=res_type, value=r_xml.get('uri', '')) self.bqSession.finish_mex(tags=[outputTag]) def collect_outputs(self): """ Perform reduce phase (i.e., examine final (top) mex and create any additional outputs based on submexes) THIS IS JUST AN EXAMPLE. """ # collect submex output hdf urls and add them to top mex outputs section top_mex = self.bqSession.fetchxml(self.options.mexURL, view='deep') outputTag = top_mex.xpath('/mex/tag[@name="outputs"]') if not outputTag: # no "outputs" tag in mex => add it now etree.SubElement(top_mex, 'tag', name='outputs') top_mex = self.bqSession.postxml(url=top_mex.get('uri'), xml=top_mex, view='deep') outputTag = top_mex.xpath('/mex/tag[@name="outputs"]') outputTag = outputTag[0] output_hdfs = top_mex.xpath( '/mex/mex/tag[@name="outputs"]/tag[@name="output_hdf"]/@value') etree.SubElement(outputTag, 'tag', name='all_outputs', value=';'.join( [ohdf.split('/')[-1] for ohdf in output_hdfs])) self.bqSession.postxml(url=outputTag.get('uri'), xml=outputTag) def main(self): """ The main function that runs everything """ log.info('sysargv : %s' % sys.argv) parser = OptionParser() parser.add_option('--mex_url', dest="mexURL") parser.add_option('--module_dir', dest="modulePath") parser.add_option('--staging_path', dest="stagingPath") parser.add_option('--bisque_token', dest="token") parser.add_option('--user', dest="user") parser.add_option('--pwd', dest="pwd") parser.add_option('--root', dest="root") # for the reduce phase: create output dataset of output HDFs (in this case, mexURL is top mex) parser.add_option('--entrypoint', dest="entrypoint") (options, args) = parser.parse_args() fh = logging.FileHandler('phase_%s.log' % (options.entrypoint or 'main'), mode='a') fh.setLevel(logging.DEBUG) formatter = logging.Formatter( '[%(asctime)s] %(levelname)8s --- %(message)s ' + '(%(filename)s:%(lineno)s)', datefmt='%Y-%m-%d %H:%M:%S') fh.setFormatter(formatter) log.addHandler(fh) try: #pull out the mex if not options.mexURL: options.mexURL = sys.argv[1] if not options.token: options.token = sys.argv[2] except IndexError: #no argv were set pass if not options.stagingPath: options.stagingPath = '' log.info('\n\nPARAMS : %s \n\n Options: %s' % (args, options)) self.options = options if self.validate_input(): #initalizes if user and password are provided if (self.options.user and self.options.pwd and self.options.root): self.bqSession = BQSession().init_local( self.options.user, self.options.pwd, bisque_root=self.options.root) self.options.mexURL = self.bqSession.mex.uri #initalizes if mex and mex token is provided elif (self.options.mexURL and self.options.token): self.bqSession = BQSession().init_mex(self.options.mexURL, self.options.token) else: raise Dream3DError( 'Insufficient options or arguments to start this module') if not self.options.entrypoint: # NOT a reduce phase => perform regular run processing try: self.setup() except Exception as e: log.exception("Exception during setup") self.bqSession.fail_mex(msg="Exception during setup: %s" % str(e)) return try: self.run() except (Exception, Dream3DError) as e: log.exception("Exception during run") self.bqSession.fail_mex(msg="Exception during run: %s" % str(e)) return try: self.teardown() except (Exception, Dream3DError) as e: log.exception("Exception during teardown") self.bqSession.fail_mex( msg="Exception during teardown: %s" % str(e)) return else: # in a reduce phase => run reduce code if self.options.entrypoint != 'collect_outputs': self.bqSession.fail_mex( msg="Unknown Dream3D entrypoint: %s" % self.options.entrypoint) return try: self.collect_outputs() except (Exception, Dream3DError) as e: log.exception("Exception during collect_outputs") self.bqSession.fail_mex( msg="Exception during collect_outputs: %s" % str(e)) return self.bqSession.close()
class WorkflowRunner(object): def mex_parameter_parser(self, mex_xml): """ Parses input of the xml and add it to options attribute (unless already set) @param: mex_xml """ # inputs are all non-"pipeline params" under "inputs" and all params under "pipeline_params" mex_inputs = mex_xml.xpath( 'tag[@name="inputs"]/tag[@name!="workflow_parameters"] | tag[@name="inputs"]/tag[@name="workflow_parameters"]/tag' ) if mex_inputs: for tag in mex_inputs: if tag.tag == 'tag' and tag.get( 'type', '') != 'system-input': #skip system input values if not getattr(self.options, tag.get('name', ''), None): log.debug('Set options with %s as %s' % (tag.get('name', ''), tag.get('value', ''))) setattr(self.options, tag.get('name', ''), tag.get('value', '')) else: log.debug('WorkflowRunner: No Inputs Found on MEX!') def validate_input(self): """ Check to see if a mex with token or user with password was provided. @return True is returned if validation credention was provided else False is returned """ if (self.options.mexURL and self.options.token): #run module through engine service return True if (self.options.user and self.options.pwd and self.options.root): #run module locally (note: to test module) return True log.debug( 'WorkflowRunner: Insufficient options or arguments to start this module' ) return False def setup(self): """ Fetches the mex and appends input_configurations to the option attribute """ self.bqSession.update_mex('Initializing...') self.mex_parameter_parser(self.bqSession.mex.xmltree) self.output_resources = [] def run(self): # retrieve tags self.bqSession.update_mex('Extracting properties') # set up initial parameters pipeline_params = self.bqSession.mex.xmltree.xpath( 'tag[@name="inputs"]/tag[@name="workflow_parameters"]/tag') self.global_vars = {'server_url': self.bqSession.c.root} for tag in pipeline_params: self.global_vars[tag.get('name', '')] = getattr(self.options, tag.get('name', '')) pipeline = self._read_pipeline(pipeline_url=self.options.pipeline_url) if not pipeline: raise WFError("trying to run incompatible workflow") # want error notification? error_mail = pipeline['__Header__'].get('Error_mail') try: # Run the workflow self.output_resources = [ ET.Element('tag', name='initial state', value=cgi.escape(str(self.global_vars))) ] for step_id in xrange(len(pipeline) - 1): curr_step = pipeline.get(str(step_id)) if curr_step is None: raise WFError("workflow step %s missing" % step_id) step_label = curr_step['__Label__'] service_name = self._prepare_param( curr_step['__Meta__']['Service']) method = self._prepare_param(curr_step['__Meta__']['Method']) path = self._prepare_param(curr_step['__Meta__']['Path']) extras = {} for meta_param in curr_step['__Meta__']: if meta_param.lower() not in ['service', 'method', 'path']: extras[meta_param.lower( )] = curr_step['__Meta__'][meta_param] input_map = {} output_map = {} for param in curr_step['Parameters']: if 'Inputs' in param: input_map = param['Inputs'] if 'Outputs' in param: output_map = param['Outputs'] res = self.run_single_step(step_id, step_label, service_name, method, path, input_map, output_map, **extras) self.output_resources.append( ET.Element('tag', name='state after step %s' % step_id, value=cgi.escape(str(self.global_vars)))) if isinstance(res, ET._Element): self.output_resources.append( ET.Element('tag', name='reply from step %s' % step_id, value=cgi.escape(ET.tostring(res)))) except Exception as exc: if error_mail is not None: input_map = { "recipients": error_mail, "subject": "Workflow failed", "__xmlbody__": str(exc) } self.run_single_step_direct(step_id='FAIL HANDLER', service_name='notify', method='POSTXML', path='email', input_map=input_map, output_map={}) raise def _read_pipeline(self, pipeline_url): """ read workflow json doc """ pipeline_path = urlparse.urlsplit(pipeline_url).path.split('/') pipeline_uid = pipeline_path[1] if is_uniq_code( pipeline_path[1]) else pipeline_path[2] url = self.bqSession.service_url('pipeline', path=pipeline_uid, query={'format': 'json'}) pipeline = self.bqSession.c.fetch(url) if not pipeline: # bad pipeline return None, None try: res_pipeline = json.loads(pipeline) except ValueError: # not json return None, None return res_pipeline def run_single_step(self, step_id, step_label, service_name, method, path, input_map, output_map, **kw): # update status self.bqSession.update_mex("Executing step %s: %s" % (str(step_id), step_label)) while True: res = self.run_single_step_direct(step_id, service_name, method, path, input_map, output_map) poll_cond = kw.get('poll_cond') if poll_cond is None: break else: # need to check output and maybe repeat cond_res = self._prepare_param(poll_cond, res) if cond_res in [True, 'True', 1, '1']: log.debug("poll condition returned %s, exit polling" % cond_res) break else: log.debug("poll condition returned %s, continue polling" % cond_res) poll_sleep = float(kw.get('poll_interval', 10)) time.sleep(poll_sleep) return res def run_single_step_direct(self, step_id, service_name, method, path, input_map, output_map): # prepare inputs try: prep_input_map = self._prepare_params(input_map) except Exception as exc: msg = "Step %s (%s %s/%s) failed. Reason: %s" % ( step_id, method, service_name, path, exc) log.exception(msg) raise WFError(msg) # request service try: if method.upper() == 'GETXML': params = prep_input_map url = urlparse.urljoin( self.bqSession.service_map[service_name], path) res = self.bqSession.fetchxml(url=url, **params) elif method.upper() == 'POSTXML': params = { key: val for (key, val) in prep_input_map.iteritems() if key not in ['__xmlbody__'] } url = urlparse.urljoin( self.bqSession.service_map[service_name], path) xml = prep_input_map.get('__xmlbody__') res = self.bqSession.postxml(url=url, xml=xml, **params) elif method.upper() == 'GETBLOB': params = prep_input_map url = urlparse.urljoin( self.bqSession.service_map[service_name], path) res = self.bqSession.fetchblob(url=url, **params) elif method.upper() == 'POSTBLOB': params = { key: val for (key, val) in prep_input_map.iteritems() if key not in ['__xmlbody__'] } if service_name != 'import': raise WFError( "POSTBLOB method used for service other than import") xml = prep_input_map.get('__xmlbody__') res = self.bqSession.postblob(xml=xml, **params) else: raise WFError("Unknown method %s" % method) except Exception as exc: msg = "Step %s (%s %s/%s) failed. Reason: %s" % ( step_id, method, service_name, path, exc) log.exception(msg) raise WFError(msg) # prepare outputs try: prep_output_map = self._prepare_params(output_map, res) except Exception as exc: msg = "Step %s (%s %s/%s) failed. Reason: %s" % ( step_id, method, service_name, path, exc) log.exception(msg) raise WFError(msg) # store outputs self.global_vars.update(prep_output_map) return res def _prepare_params(self, param_map, doc=None): res = {} for single_input in param_map: val = param_map[single_input] val = self._prepare_param(val, doc) res[single_input] = val return res def _prepare_param(self, val, doc=None): run_xpath = False if val.startswith('xpath:'): run_xpath = True val = val[len('xpath:'):] # expand variables etc log.debug("Mako expand %s with %s" % (val, str(self.global_vars))) template = Template(val) val = template.render(**self.global_vars) # run xpath if requested if run_xpath: if doc is None or not isinstance(doc, ET._Element): raise WFError("no result XML") valx = doc.xpath(val) if len(valx) < 1: msg = "xpath '%s' result empty for %s" % (val, ET.tostring(doc)) log.error(msg) raise WFError(msg) val = valx[0] return val def teardown(self): """ Post the results to the mex xml. """ self.bqSession.update_mex('Returning results') outputTagOuter = ET.Element('tag', name='outputs') outputTag = ET.SubElement(outputTagOuter, 'tag', name='summary') for r_xml in self.output_resources: res_type = r_xml.get('type', None) or r_xml.get( 'resource_type', None) or r_xml.tag if res_type == 'tag': # simple tag => append to output as is r_xml.tag = 'tag' outputTag.append(r_xml) else: # r_xml is some other resource (e.g., image or table) => append reference to output ET.SubElement(outputTag, 'tag', name='output_table' if res_type == 'table' else 'output_image', type=res_type, value=r_xml.get('uri', '')) self.bqSession.finish_mex(tags=[outputTagOuter]) def main(self): """ The main function that runs everything """ log.info('sysargv : %s' % sys.argv) parser = OptionParser() parser.add_option('--mex_url', dest="mexURL") parser.add_option('--module_dir', dest="modulePath") parser.add_option('--staging_path', dest="stagingPath") parser.add_option('--bisque_token', dest="token") parser.add_option('--user', dest="user") parser.add_option('--pwd', dest="pwd") parser.add_option('--root', dest="root") parser.add_option('--entrypoint', dest="entrypoint") (options, args) = parser.parse_args() fh = logging.FileHandler('phase_%s.log' % (options.entrypoint or 'main'), mode='a') fh.setLevel(logging.DEBUG) formatter = logging.Formatter( '[%(asctime)s] %(levelname)8s --- %(message)s ' + '(%(filename)s:%(lineno)s)', datefmt='%Y-%m-%d %H:%M:%S') fh.setFormatter(formatter) log.addHandler(fh) try: #pull out the mex if not options.mexURL: options.mexURL = sys.argv[1] if not options.token: options.token = sys.argv[2] except IndexError: #no argv were set pass if not options.stagingPath: options.stagingPath = '/module/workdir' log.info('\n\nPARAMS : %s \n\n Options: %s' % (args, options)) self.options = options if self.validate_input(): #initalizes if user and password are provided if (self.options.user and self.options.pwd and self.options.root): self.bqSession = BQSession().init_local( self.options.user, self.options.pwd, bisque_root=self.options.root) self.options.mexURL = self.bqSession.mex.uri #initalizes if mex and mex token is provided elif (self.options.mexURL and self.options.token): self.bqSession = BQSession().init_mex(self.options.mexURL, self.options.token) else: raise WFError( 'Insufficient options or arguments to start this workflow') try: self.setup() except Exception as e: log.exception("Exception during setup") self.bqSession.fail_mex(msg="Exception during setup: %s" % str(e)) return try: self.run() except (Exception, WFError) as e: log.exception("Exception during run") self.bqSession.fail_mex(msg="Exception during run: %s" % str(e)) return try: self.teardown() except (Exception, WFError) as e: log.exception("Exception during teardown") self.bqSession.fail_mex(msg="Exception during teardown: %s" % str(e)) return self.bqSession.close()
class Dream3D(object): """ Dream3D Module """ def mex_parameter_parser(self, mex_xml): """ Parses input of the xml and add it to Dream3D's options attribute (unless already set) @param: mex_xml """ # inputs are all non-"pipeline params" under "inputs" and all params under "pipeline_params" mex_inputs = mex_xml.xpath( 'tag[@name="inputs"]/tag[@name!="pipeline_params"] | tag[@name="inputs"]/tag[@name="pipeline_params"]/tag' ) if mex_inputs: for tag in mex_inputs: if tag.tag == 'tag' and tag.get( 'type', '') != 'system-input': #skip system input values if not getattr(self.options, tag.get('name', ''), None): log.debug('Set options with %s as %s' % (tag.get('name', ''), tag.get('value', ''))) setattr(self.options, tag.get('name', ''), tag.get('value', '')) else: log.debug('Dream3D: No Inputs Found on MEX!') def validate_input(self): """ Check to see if a mex with token or user with password was provided. @return True is returned if validation credention was provided else False is returned """ if (self.options.mexURL and self.options.token): #run module through engine service return True if (self.options.user and self.options.pwd and self.options.root): #run module locally (note: to test module) return True log.debug( 'Dream3D: Insufficient options or arguments to start this module') return False def setup(self): """ Fetches the mex and appends input_configurations to the option attribute of Dream3D """ self.bqSession.update_mex('Initializing...') self.mex_parameter_parser(self.bqSession.mex.xmltree) self.output_file = None def run(self): """ The core of the Dream3D runner """ #retrieve tags self.bqSession.update_mex('Extracting properties') #type check hdf_resource = self.bqSession.fetchxml(self.options.InputFile, view='deep,clean') if (hdf_resource.tag != 'resource' or hdf_resource.get('resource_type', '') != 'table' ) and hdf_resource.tag != 'table': raise Dream3DError("trying to run Dream3D on non-table resource") hdf_url = self.bqSession.service_url( 'blob_service', path=hdf_resource.get('resource_uniq')) self.bqSession.fetchblob(hdf_url, path=os.path.join(self.options.stagingPath, 'input.h5')) hdf_input_file = os.path.join(self.options.stagingPath, 'input.h5') hdf_output_file = os.path.join(self.options.stagingPath, 'output.h5') # create pipeline with correct parameters pipeline_params = self.bqSession.mex.xmltree.xpath( 'tag[@name="inputs"]/tag[@name="pipeline_params"]/tag') params = {} for tag in pipeline_params: params[tag.get('name', '')] = getattr(self.options, tag.get('name', '')) pipeline_file, err_file = self._instantiate_pipeline( pipeline_url=self.options.pipeline_url, input_file=hdf_input_file, output_file=hdf_output_file, params=params) # run Dream3D on the pipeline self.bqSession.update_mex('Running Dream3D') log.debug('run Dream3D on %s', pipeline_file) res = 1 with open(err_file, 'w') as fo: # res = 0 #!!! TESTING # open(hdf_output_file, 'a').close() res = subprocess.call( ['/build/Bin/PipelineRunner', '-p', pipeline_file], stderr=fo, stdout=fo) log.debug("Dream3D returned: %s", str(res)) if res > 0: err_msg = 'pipeline execution failed\n' with open(err_file, 'r') as fo: err_msg += ''.join(fo.readlines()) raise Dream3DError(err_msg) self.output_file = hdf_output_file def _instantiate_pipeline(self, pipeline_url, input_file, output_file, params): """instantiate pipeline json file with provided parameters""" pipeline_resource = self.bqSession.fetchxml(pipeline_url, view='short') out_pipeline_file = os.path.join(self.options.stagingPath, 'pipeline.json') out_error_file = os.path.join(self.options.stagingPath, 'dream3d_error.txt') pipeline_url = self.bqSession.service_url( 'blob_service', path=pipeline_resource.get('resource_uniq')) self.bqSession.fetchblob(pipeline_url, path=os.path.join(self.options.stagingPath, 'pipeline_uninit.json')) pipeline_file = os.path.join(self.options.stagingPath, 'pipeline_uninit.json') with open(pipeline_file, 'r') as fi: pipeline = json.load(fi) # replace all placeholders in pipeline template _replace_placeholders(pipeline, input_file, output_file, params) # write out pipeline to provided file with open(out_pipeline_file, 'w') as fo: json.dump(pipeline, fo) return out_pipeline_file, out_error_file def teardown(self): """ Post the results to the mex xml. """ #save the HDF output and upload it to the data service with all the meta data self.bqSession.update_mex('Returning results') log.debug('Storing HDF output') #constructing and storing HDF file mex_id = self.bqSession.mex.uri.split('/')[-1] dt = datetime.now().strftime('%Y%m%dT%H%M%S') final_output_file = "ModuleExecutions/Dream3D/%s_%s_%s.h5" % ( self.options.OutputPrefix, dt, mex_id) #does not accept no name on the resource cl_model = etree.Element('resource', resource_type='table', name=final_output_file) #module identifier (a descriptor to be found by the Dream3D model) etree.SubElement(cl_model, 'tag', name='module_identifier', value='Dream3D') #hdf filename etree.SubElement(cl_model, 'tag', name='OutputFile', value=final_output_file) #pipeline param #etree.SubElement(cl_model, 'tag', name='RefFrameZDir', value=self.options.RefFrameZDir) #input hdf url #etree.SubElement(cl_model, 'tag', name='InputFile', type='link', value=self.options.InputFile) #input pipeline #etree.SubElement(cl_model, 'tag', name='pipeline_url', type='link', value=self.options.pipeline_url) #description etree.SubElement(cl_model, 'tag', name='description', value='HDF output file from Dream3D Module') #storing the HDF file in blobservice log.debug('before postblob') #!!! r = self.bqSession.postblob(self.output_file, xml=cl_model) r_xml = etree.fromstring(r) outputTag = etree.Element('tag', name='outputs') etree.SubElement(outputTag, 'tag', name='output_hdf', type='table', value=r_xml[0].get('uri', '')) self.bqSession.finish_mex(tags=[outputTag]) def collect_outputs(self): """ Perform reduce phase (i.e., examine final (top) mex and create any additional outputs based on submexes) THIS IS JUST AN EXAMPLE. """ # collect submex output hdf urls and add them to top mex outputs section top_mex = self.bqSession.fetchxml(self.options.mexURL, view='deep') outputTag = top_mex.xpath('/mex/tag[@name="outputs"]')[0] output_hdfs = top_mex.xpath( '/mex/mex/tag[@name="outputs"]/tag[@name="output_hdf"]/@value') etree.SubElement(outputTag, 'tag', name='all_outputs', value=';'.join( [id.split('/')[-1] for id in output_hdfs])) self.bqSession.postxml(url=outputTag.get('uri'), xml=outputTag) def main(self): """ The main function that runs everything """ log.info('sysargv : %s' % sys.argv) parser = OptionParser() parser.add_option('--mex_url', dest="mexURL") parser.add_option('--module_dir', dest="modulePath") parser.add_option('--staging_path', dest="stagingPath") parser.add_option('--bisque_token', dest="token") parser.add_option('--user', dest="user") parser.add_option('--pwd', dest="pwd") parser.add_option('--root', dest="root") # for the reduce phase: create output dataset of output HDFs (in this case, mexURL is top mex) parser.add_option('--entrypoint', dest="entrypoint") (options, args) = parser.parse_args() fh = logging.FileHandler('phase_%s.log' % (options.entrypoint or 'main'), mode='a') fh.setLevel(logging.DEBUG) formatter = logging.Formatter( '[%(asctime)s] %(levelname)8s --- %(message)s ' + '(%(filename)s:%(lineno)s)', datefmt='%Y-%m-%d %H:%M:%S') fh.setFormatter(formatter) log.addHandler(fh) try: #pull out the mex if not options.mexURL: options.mexURL = sys.argv[1] if not options.token: options.token = sys.argv[2] except IndexError: #no argv were set pass if not options.stagingPath: options.stagingPath = '' log.info('\n\nPARAMS : %s \n\n Options: %s' % (args, options)) self.options = options if self.validate_input(): #initalizes if user and password are provided if (self.options.user and self.options.pwd and self.options.root): self.bqSession = BQSession().init_local( self.options.user, self.options.pwd, bisque_root=self.options.root) self.options.mexURL = self.bqSession.mex.uri #initalizes if mex and mex token is provided elif (self.options.mexURL and self.options.token): self.bqSession = BQSession().init_mex(self.options.mexURL, self.options.token) else: raise Dream3DError( 'Insufficient options or arguments to start this module') if not self.options.entrypoint: # NOT a reduce phase => perform regular run processing try: self.setup() except Exception as e: log.exception("Exception during setup") self.bqSession.fail_mex(msg="Exception during setup: %s" % str(e)) return try: self.run() except (Exception, Dream3DError) as e: log.exception("Exception during run") self.bqSession.fail_mex(msg="Exception during run: %s" % str(e)) return try: self.teardown() except (Exception, Dream3DError) as e: log.exception("Exception during teardown") self.bqSession.fail_mex( msg="Exception during teardown: %s" % str(e)) return else: # in a reduce phase => run reduce code if self.options.entrypoint != 'collect_outputs': self.bqSession.fail_mex( msg="Unknown Dream3D entrypoint: %s" % self.options.entrypoint) return try: self.collect_outputs() except (Exception, Dream3DError) as e: log.exception("Exception during collect_outputs") self.bqSession.fail_mex( msg="Exception during collect_outputs: %s" % str(e)) return self.bqSession.close()
class CellProfiler(object): """ CellProfiler Module """ def mex_parameter_parser(self, mex_xml): """ Parses input of the xml and add it to CellProfiler's options attribute (unless already set) @param: mex_xml """ # inputs are all non-"pipeline params" under "inputs" and all params under "pipeline_params" mex_inputs = mex_xml.xpath( 'tag[@name="inputs"]/tag[@name!="pipeline_params"] | tag[@name="inputs"]/tag[@name="pipeline_params"]/tag' ) if mex_inputs: for tag in mex_inputs: if tag.tag == 'tag' and tag.get( 'type', '') != 'system-input': #skip system input values if not getattr(self.options, tag.get('name', ''), None): log.debug('Set options with %s as %s' % (tag.get('name', ''), tag.get('value', ''))) setattr(self.options, tag.get('name', ''), tag.get('value', '')) else: log.debug('CellProfiler: No Inputs Found on MEX!') def validate_input(self): """ Check to see if a mex with token or user with password was provided. @return True is returned if validation credention was provided else False is returned """ if (self.options.mexURL and self.options.token): #run module through engine service return True if (self.options.user and self.options.pwd and self.options.root): #run module locally (note: to test module) return True log.debug( 'CellProfiler: Insufficient options or arguments to start this module' ) return False def setup(self): """ Fetches the mex and appends input_configurations to the option attribute of CellProfiler """ self.bqSession.update_mex('Initializing...') self.mex_parameter_parser(self.bqSession.mex.xmltree) self.output_resources = [] self.ppops = None self.ppops_url = None def run(self): """ The core of the CellProfiler runner """ module_time = datetime.now() #retrieve tags self.bqSession.update_mex('Extracting properties') #type check image_resource = self.bqSession.fetchxml(self.options.InputFile) if image_resource.tag != 'image': raise CPError("trying to run CellProfiler on non-image resource") # run prerun operations filelist_file = self._run_prerun_ops( module_time, pipeline_url=self.options.pipeline_url, input_xml=image_resource) # create pipeline with correct parameters pipeline_params = self.bqSession.mex.xmltree.xpath( 'tag[@name="inputs"]/tag[@name="pipeline_params"]/tag') params = {} for tag in pipeline_params: params[tag.get('name', '')] = getattr(self.options, tag.get('name', '')) pipeline_file, err_file = self._instantiate_pipeline( pipeline_url=self.options.pipeline_url, params=params) if not pipeline_file: raise CPError("trying to run incompatible CellProfiler pipeline") # run CellProfiler on the pipeline self.bqSession.update_mex('Running CellProfiler') log.debug('run CellProfiler on %s', pipeline_file) res = 1 with open(err_file, 'w') as fo: res = subprocess.call([ 'python', '/module/CellProfiler/CellProfiler.py', '-c', '-r', '-i', self.options.stagingPath, '-o', self.options.stagingPath, '-p', pipeline_file, '--file-list', filelist_file ], stderr=fo, stdout=fo) log.debug("CellProfiler returned: %s", str(res)) if res > 0: err_msg = 'pipeline execution failed\n' with open(err_file, 'r') as fo: err_msg += ''.join(fo.readlines()) if len(err_msg) > 1024: err_msg = err_msg[:512] + '...' + err_msg[-512:] raise CPError(err_msg) # run postrun operations self.output_resources = self._run_postrun_ops( module_time, pipeline_url=self.options.pipeline_url) def _cache_ppops(self, pipeline_url): if not self.ppops or self.ppops_url != pipeline_url: pipeline_path = urlparse.urlsplit(pipeline_url).path.split('/') pipeline_uid = pipeline_path[1] if is_uniq_code( pipeline_path[1]) else pipeline_path[2] url = self.bqSession.service_url( 'pipeline', path='/'.join([pipeline_uid] + ['ppops:cellprofiler'])) self.ppops = json.loads(self.bqSession.c.fetch(url)) self.ppops_url = pipeline_url def _run_prerun_ops(self, module_time, pipeline_url, input_xml): """ Perform any operations necessary before the pipeline runs (e.g., extract image channels) and return filelist file """ self._cache_ppops(pipeline_url) post_ops = self.ppops['PreOps'] input_files = [] for op in post_ops: input_files += self._run_single_op(module_time, op, input_xml) filelist_file = os.path.join(self.options.stagingPath, 'filelist.txt') with open(filelist_file, 'w') as fo: for input_file in input_files: fo.write(input_file + '\n') return filelist_file def _run_postrun_ops(self, module_time, pipeline_url): """ Perform any operations necessary after the pipeline finished (e.g., upload result tables) and return created resources """ self._cache_ppops(pipeline_url) post_ops = self.ppops['PostOps'] created_resources = [] for op in post_ops: created_resources += self._run_single_op(module_time, op) return created_resources def _run_single_op(self, module_time, op, input_xml=None): """ Perform single pre/post operation and return list of files or resources generated """ # replace special placeholders if 'id' in op and op['id'] == '@INPUT': op['id'] = input_xml.get('resource_uniq') res = [] if op['service'] == 'image_service': # perform image_service operation log.debug("RUNOP %s" % str(op)) url = self.bqSession.service_url('image_service', path=op['id'] + op['ops']) # TODO: don't read image into memory!!! image_data = self.bqSession.c.fetch(url) image_file = os.path.join(self.options.stagingPath, op['filename']) with open(image_file, 'w') as fo: fo.write(image_data) res += [image_file] elif op['service'] == 'postblob': # upload image or table (check op['type']) dt = module_time.strftime('%Y%m%dT%H%M%S') final_output_file = "ModuleExecutions/CellProfiler/%s/%s" % ( dt, op['name']) cl_model = etree.Element('resource', resource_type=op['type'], name=final_output_file) # module identifier (a descriptor to be found by the CellProfiler model) etree.SubElement(cl_model, 'tag', name='module_identifier', value='CellProfiler') # hdf filename etree.SubElement(cl_model, 'tag', name='OutputFile', value=final_output_file) #description etree.SubElement(cl_model, 'tag', name='description', value='output from CellProfiler Module') # post blob output_file = os.path.join(self.options.stagingPath, op['filename']) resource = self.bqSession.postblob(output_file, xml=cl_model) resource_xml = etree.fromstring(resource) res += [resource_xml[0]] elif op['service'] == 'postellipse': # add ellipse gobject to mex # Read object measurements from csv and write to gobjects (header, records) = self._readCSV( os.path.join(self.options.stagingPath, op['filename'])) if header is not None: parentGObject = etree.Element('gobject', type='detected shapes', name='detected shapes') for i in range(len(records)): shape = self._get_ellipse_elem( name=str(i), header=header, record=records[i], x=op['x_coord'], y=op['y_coord'], label=op['label'], color=op['color'], orientation=op['orientation'], major_axis=op['major_axis'], minor_axis=op['minor_axis']) if shape: parentGObject.append(shape) res += [parentGObject] return res def _instantiate_pipeline(self, pipeline_url, params): """ instantiate cellprofiler pipeline file with provided parameters """ pipeline_path = urlparse.urlsplit(pipeline_url).path.split('/') pipeline_uid = pipeline_path[1] if is_uniq_code( pipeline_path[1]) else pipeline_path[2] url = self.bqSession.service_url( 'pipeline', path='/'.join( [pipeline_uid] + ["setvar:%s|%s" % (tag, params[tag]) for tag in params] + ['exbsteps:cellprofiler']), query={'format': 'cellprofiler'}) pipeline = self.bqSession.c.fetch(url) if not pipeline: # bad pipeline return None, None out_pipeline_file = os.path.join(self.options.stagingPath, 'pipeline.cp') out_error_file = os.path.join(self.options.stagingPath, 'cp_error.txt') with open(out_pipeline_file, 'w') as fo: fo.write(pipeline) return out_pipeline_file, out_error_file def teardown(self): """ Post the results to the mex xml. """ self.bqSession.update_mex('Returning results') outputTag = etree.Element('tag', name='outputs') for r_xml in self.output_resources: res_type = r_xml.get('type', None) or r_xml.get( 'resource_type', None) or r_xml.tag if res_type == 'detected shapes': # r_xml is a set of gobjects => append to output inside image tag image_resource = self.bqSession.fetchxml( self.options.InputFile) image_elem = etree.SubElement(outputTag, 'tag', name=image_resource.get('name'), type='image', value=image_resource.get('uri')) image_elem.append(r_xml) else: # r_xml is some other resource (e.g., image or table) => append reference to output etree.SubElement(outputTag, 'tag', name='output_table' if res_type == 'table' else 'output_image', type=res_type, value=r_xml.get('uri', '')) self.bqSession.finish_mex(tags=[outputTag]) def _get_ellipse_elem(self, name, **params): header = params.get('header') record = params.get('record') getValue = lambda x: float(record[header.index(x)]) shape = etree.Element('gobject', name=name, type=params.get('label')) res = etree.SubElement(shape, 'ellipse') try: # centroid x = getValue(params.get('x')) y = getValue(params.get('y')) theta = math.radians(getValue(params.get('orientation'))) etree.SubElement(res, 'vertex', x=str(x), y=str(y)) # major axis/minor axis endpoint coordinates a = 0.5 * getValue(params.get('major_axis')) b = 0.5 * getValue(params.get('minor_axis')) bX = round(x - b * math.sin(theta)) bY = round(y + b * math.cos(theta)) etree.SubElement(res, 'vertex', x=str(bX), y=str(bY)) aX = round(x + a * math.cos(theta)) aY = round(y + a * math.sin(theta)) etree.SubElement(res, 'vertex', x=str(aX), y=str(aY)) # other statistics #etree.SubElement(res, 'tag', name="Compactness", value=str(getValue('AreaShape_Compactness'))) #etree.SubElement(res, 'tag', name="Eccentricity", value=str(getValue('AreaShape_Eccentricity'))) #etree.SubElement(res, 'tag', name="FormFactor", value=str(getValue('AreaShape_FormFactor'))) #etree.SubElement(res, 'tag', name="Solidity", value=str(getValue('AreaShape_Solidity'))) etree.SubElement(res, 'tag', name='color', value=params.get('color', '#FF0000'), type='color') except KeyError: return None except ValueError: return None return shape def _readCSV(self, fileName): if os.path.exists(fileName) == False: return (None, None) records = [] handle = open(fileName, 'rb') csvHandle = csv.reader(handle) header = csvHandle.next() for row in csvHandle: records.append(row) handle.close() return (header, records) def main(self): """ The main function that runs everything """ log.info('sysargv : %s' % sys.argv) parser = OptionParser() parser.add_option('--mex_url', dest="mexURL") parser.add_option('--module_dir', dest="modulePath") parser.add_option('--staging_path', dest="stagingPath") parser.add_option('--bisque_token', dest="token") parser.add_option('--user', dest="user") parser.add_option('--pwd', dest="pwd") parser.add_option('--root', dest="root") parser.add_option('--entrypoint', dest="entrypoint") (options, args) = parser.parse_args() fh = logging.FileHandler('phase_%s.log' % (options.entrypoint or 'main'), mode='a') fh.setLevel(logging.DEBUG) formatter = logging.Formatter( '[%(asctime)s] %(levelname)8s --- %(message)s ' + '(%(filename)s:%(lineno)s)', datefmt='%Y-%m-%d %H:%M:%S') fh.setFormatter(formatter) log.addHandler(fh) try: #pull out the mex if not options.mexURL: options.mexURL = sys.argv[1] if not options.token: options.token = sys.argv[2] except IndexError: #no argv were set pass if not options.stagingPath: options.stagingPath = '/module/CellProfiler/workdir' log.info('\n\nPARAMS : %s \n\n Options: %s' % (args, options)) self.options = options if self.validate_input(): #initalizes if user and password are provided if (self.options.user and self.options.pwd and self.options.root): self.bqSession = BQSession().init_local( self.options.user, self.options.pwd, bisque_root=self.options.root) self.options.mexURL = self.bqSession.mex.uri #initalizes if mex and mex token is provided elif (self.options.mexURL and self.options.token): self.bqSession = BQSession().init_mex(self.options.mexURL, self.options.token) else: raise CPError( 'Insufficient options or arguments to start this module') if not self.options.entrypoint: # NOT a special phase => perform regular run processing try: self.setup() except Exception as e: log.exception("Exception during setup") self.bqSession.fail_mex(msg="Exception during setup: %s" % str(e)) return try: self.run() except (Exception, CPError) as e: log.exception("Exception during run") self.bqSession.fail_mex(msg="Exception during run: %s" % str(e)) return try: self.teardown() except (Exception, CPError) as e: log.exception("Exception during teardown") self.bqSession.fail_mex( msg="Exception during teardown: %s" % str(e)) return else: # in a special phase => run special code self.bqSession.fail_mex( msg="Unknown CellProfiler entrypoint: %s" % self.options.entrypoint) return self.bqSession.close()
class SkeletonPython(object): """ SkeletonPython Model """ def mex_parameter_parser(self, mex_xml): """ Parses input of the xml and add it to SkeletonPython Trainer's options attribute @param: mex_xml """ mex_inputs = mex_xml.xpath('tag[@name="inputs"]') if mex_inputs: for tag in mex_inputs[0]: if tag.tag == 'tag' and tag.attrib['type'] != 'system-input': log.debug('Set options with %s as %s'%(tag.attrib['name'],tag.attrib['value'])) setattr(self.options,tag.attrib['name'],tag.attrib['value']) else: log.debug('SkeletonPythonFS: No Inputs Found on MEX!') def validateInput(self): """ Parses input of the xml and add it to SkeletonPython's options attribute @param: mex_xml """ if (self.options.mexURL and self.options.token): #run module through engine service return True if (self.options.user and self.options.pwd and self.options.root): #run module locally (note: to test module) return True log.debug('SkeletonPython: Insufficient options or arguments to start this module') return False def setup(self): """ Fetches the mex, appends input_configurations to the option attribute of SkeletonPython and looks up the model on bisque to classify the provided resource. """ if (self.options.user and self.options.pwd and self.options.root): self.bqSession = BQSession().init_local( self.options.user, self.options.pwd, bisque_root=self.options.root) self.options.mexURL = self.bqSession.mex.uri # This is when the module actually runs on the server with a mexURL and an access token elif (self.options.mexURL and self.options.token): self.bqSession = BQSession().init_mex(self.options.mexURL, self.options.token) else: return # Parse the xml and construct the tree, also set options to proper values after parsing it (like image url) self.mex_parameter_parser(self.bqSession.mex.xmltree) log.debug('SkeletonPython: image URL: %s, mexURL: %s, stagingPath: %s, token: %s' % (self.options.image_url, self.options.mexURL, self.options.stagingPath, self.options.token)) def construct_vertices(self, child): annotation_type = 'bg' if 'foreground' in child.values(): annotation_type = 'fg' roi = [] log.debug("This is the child") vertices = child.getchildren()[0].getchildren() for vertex in vertices: values = vertex.values() roi.append({'x':int(float(values[2])), 'y':int(float(values[3]))}) self.rois[annotation_type].append(roi) log.debug(vertices) log.debug(len(vertices)) def show_structure(self, r_xml): for i, child in enumerate(r_xml.getchildren()): if "background" in child.values() or 'foreground' in child.values(): log.debug('Background/Foreground annotation') self.construct_vertices(child) else: self.show_structure(child) def run(self): """ The core of the SkeletonPython Module Requests features on the image provided. Classifies each tile and picks a majority among the tiles. """ self.rois = {'fg':[],'bg':[]} r_xml = self.bqSession.fetchxml(self.options.mexURL, view='deep') log.debug("Shols structura") self.show_structure(r_xml) log.debug(self.rois) image = self.bqSession.load(self.options.image_url) ip = image.pixels().format('tiff') pixels = ip.fetch() f = open('./temp.tif','wb') f.write(pixels) f.close() pickle.dump([self.rois,self.options.segmentImage,self.options.deepNetworkChoice,self.options.qualitySeg,self.options.deepSeg,self.options.mexURL,self.options.token], open('./data.p','wb')) pathToScript = './DeepTools/deep_script.sh' call([pathToScript]) def teardown(self): """ Posting results to the mex """ self.bqSession.update_mex('Returning results...') log.debug('Returning results...') prediction = "None-Module Failure" with open("./results.txt","r") as f: for line in f: if "PREDICTION_C:" in line: prediction_c = line if "CONFIDENCE_C:" in line: confidence_c = line[14:-1] classes = ["leaf","fruit","flower","stem","entire"] if self.options.deepNetworkChoice != 'None': for i,class_tag in enumerate(classes): prediction_c = prediction_c.replace(str(i),class_tag) outputTag = etree.Element('tag', name='outputs') outputSubTagImage = etree.SubElement(outputTag, 'tag', name='Final Image', value=self.options.image_url) print "Module will output image, {}".format(self.options.image_url) if not os.path.isfile("./contours.pkl"): print "Module will not segment image, (were foreground and background polyline annotations provided?)" if self.options.segmentImage != "False" and os.path.isfile("./contours.pkl"): [contours, t_scale] = pickle.load(open("./contours.pkl","rb")) gob = etree.SubElement (outputSubTagImage, 'gobject', name='Annotations', type='Annotations') polyseg = etree.SubElement(gob, 'polygon', name='SEG') etree.SubElement( polyseg, 'tag', name='color', value="#0000FF") opd = 0 output_sampling = 1+int(len(contours)/100) for j in range(len(contours)): if j % (output_sampling) == 0: opd += 1 etree.SubElement (polyseg, 'vertex', x=str(1+int(t_scale[1]*contours[j][1])), y=str(1+int(t_scale[0]*contours[j][0]))) log.debug(opd) if self.options.deepNetworkChoice != 'None': outputSubTagSummary = etree.SubElement(outputTag, 'tag', name='summary') etree.SubElement(outputSubTagSummary, 'tag',name='Model File', value=self.options.deepNetworkChoice) etree.SubElement(outputSubTagSummary, 'tag',name='Segment Image', value=self.options.segmentImage) etree.SubElement(outputSubTagSummary, 'tag',name='Class', value=str(prediction_c)) etree.SubElement(outputSubTagSummary, 'tag', name='Class Confidence', value=str(confidence_c)) self.bqSession.finish_mex(tags = [outputTag]) log.debug('FINISHED') self.bqSession.close() def main(self): """ The main function that runs everything """ print("DEBUG_INIT") log.debug('SkeletonPython is called with the following arguments') log.debug('sysargv : %s\n\n' % sys.argv ) parser = OptionParser() parser.add_option( '--image_url' , dest="image_url") parser.add_option( '--mex_url' , dest="mexURL") parser.add_option( '--module_dir' , dest="modulePath") parser.add_option( '--staging_path', dest="stagingPath") parser.add_option( '--bisque_token', dest="token") parser.add_option( '--user' , dest="user") parser.add_option( '--pwd' , dest="pwd") parser.add_option( '--root' , dest="root") (options, args) = parser.parse_args() # Set up the mexURL and token based on the arguments passed to the script try: #pull out the mex log.debug("options %s" % options) if not options.mexURL: options.mexURL = sys.argv[1] if not options.token: options.token = sys.argv[2] except IndexError: #no argv were set pass if not options.stagingPath: options.stagingPath = '' # Still don't have an imgurl, but it will be set up in self.setup() log.debug('\n\nPARAMS : %s \n\n Options: %s'%(args, options)) self.options = options if self.validateInput(): try: #run setup and retrieve mex variables self.setup() except Exception, e: log.exception("Exception during setup") self.bqSession.fail_mex(msg = "Exception during setup: %s" % str(e)) return try: #run module operation self.run() except SkeletonPythonError, e: log.exception("Exception during run") self.bqSession.fail_mex(msg = "Exception during run: %s" % str(e.message)) return except Exception, e: log.exception("Exception during run") self.bqSession.fail_mex(msg = "Exception during run: %s" % str(e)) return
class SkeletonPython(object): """ SkeletonPython Model """ def mex_parameter_parser(self, mex_xml): """ Parses input of the xml and add it to SkeletonPython Trainer's options attribute @param: mex_xml """ mex_inputs = mex_xml.xpath('tag[@name="inputs"]') if mex_inputs: for tag in mex_inputs[0]: if tag.tag == 'tag' and tag.attrib['type'] != 'system-input': log.debug('Set options with %s as %s' % (tag.attrib['name'], tag.attrib['value'])) setattr(self.options, tag.attrib['name'], tag.attrib['value']) else: log.debug('SkeletonPythonFS: No Inputs Found on MEX!') def validateInput(self): """ Parses input of the xml and add it to SkeletonPython's options attribute @param: mex_xml """ if (self.options.mexURL and self.options.token): #run module through engine service return True if (self.options.user and self.options.pwd and self.options.root): #run module locally (note: to test module) return True log.debug( 'SkeletonPython: Insufficient options or arguments to start this module' ) return False def setup(self): """ Fetches the mex, appends input_configurations to the option attribute of SkeletonPython and looks up the model on bisque to classify the provided resource. """ if (self.options.user and self.options.pwd and self.options.root): self.bqSession = BQSession().init_local( self.options.user, self.options.pwd, bisque_root=self.options.root) self.options.mexURL = self.bqSession.mex.uri # This is when the module actually runs on the server with a mexURL and an access token elif (self.options.mexURL and self.options.token): self.bqSession = BQSession().init_mex(self.options.mexURL, self.options.token) else: return # Parse the xml and construct the tree, also set options to proper values after parsing it (like image url) self.mex_parameter_parser(self.bqSession.mex.xmltree) log.debug( 'SkeletonPython: image URL: %s, mexURL: %s, stagingPath: %s, token: %s' % (self.options.image_url, self.options.mexURL, self.options.stagingPath, self.options.token)) def run(self): """ The core of the SkeletonPython Module Requests features on the image provided. Classifies each tile and picks a majority among the tiles. """ r_xml = self.bqSession.fetchxml(self.options.mexURL, view='deep') '''rectangles = r_xml.xpath('//tag[@name="inputs"]/tag[@name="image_url"]/gobject[@name="roi"]/rectangle') rois = [] rois_rectangles = [] for i in range(len(rectangles)): x1 = int(float(rectangles[i][0].attrib['x'])) y1 = int(float(rectangles[i][0].attrib['y'])) x2 = int(float(rectangles[i][1].attrib['x'])) y2 = int(float(rectangles[i][1].attrib['y'])) rois_rectangles.append([x1,y1,x2,y2]) polygons = r_xml.xpath('//tag[@name="inputs"]/tag[@name="image_url"]/gobject[@name="roi"]/polygon') rois_polygons = [] for i in range(len(polygons)): polygon = [] for j in range(len(polygons[i])): x = int(float(polygons[i][j].attrib['x'])) y = int(float(polygons[i][j].attrib['y'])) polygon.append([x,y]) rois_polygons.append(polygon) polylines = r_xml.xpath('//tag[@name="inputs"]/tag[@name="image_url"]/gobject[@name="roi"]/polyline') rois_polylines = [] for i in range(len(polylines)): polyline = [] for j in range(len(polylines[i])): x = int(float(polylines[i][j].attrib['x'])) y = int(float(polylines[i][j].attrib['y'])) polyline.append([x,y]) rois_polylines.append(polyline) rois.append(rois_polylines) rois.append(rois_polygons) rois.append(rois_rectangles)''' def teardown(self): """ Posting results to the mex """ self.bqSession.update_mex('Returning results...') '''log.debug('Returning results...') prediction = "None-Module Failure" with open("./results.txt","r") as f: for line in f: if "PREDICTION_C:" in line: prediction_c = line if "CONFIDENCE_C:" in line: confidence_c = line[14:-1] if "PREDICTION_T:" in line: prediction_t = line if "CONFIDENCE_T:" in line: confidence_t = line[14:-1] print line classes = ["flower","stem","fruit","entire","leaf"] classes_type = ["sheet","natural"] for i,class_tag in enumerate(classes): prediction_c = prediction_c.replace(str(i),class_tag) for i,class_tag in enumerate(classes_type): prediction_t = prediction_t.replace(str(i),class_tag) ''' outputTag = etree.Element('tag', name='outputs') #dda = etree.SubElement(outputTag, 'tag', name='mex_url', value=self.options.image_url) outputSubTagImage = etree.SubElement(outputTag, 'tag', name='OutputImage', value=self.options.image_url) '''gob = etree.SubElement (outputSubTagImage, 'gobject', name='Annotations', type='Annotations') xc = [100, 300, 200] yc = [100, 150, 200] polyseg = etree.SubElement(gob, 'polygon', name='SEG') etree.SubElement( polyseg, 'tag', name='color', value="#0000FF") for i in range(len(xc)): etree.SubElement (polyseg, 'vertex', x=str(1*int(yc[i])), y=str(1*int(xc[i]))) print outputTag''' '''print "Module will output image, {}".format(self.options.image_url) if not os.path.isfile("./contours.pkl"): print "Module will not segment image, (were foreground and background polyline annotations provided?)" if self.options.segmentImage != "False" and os.path.isfile("./contours.pkl"): [contours, t_scale] = pickle.load(open("./contours.pkl","rb")) xc = [] yc = [] for i, p in enumerate(contours): if i < len(contours)/2: xc.append(p) else: yc.append(p) gob = etree.SubElement (outputSubTagImage, 'gobject', name='Annotations', type='Annotations') polyseg = etree.SubElement(gob, 'polygon', name='SEG') etree.SubElement( polyseg, 'tag', name='color', value="#0000FF") print "TSCALE", t_scale for i in range(len(xc)): etree.SubElement (polyseg, 'vertex', x=str(t_scale[1]*int(yc[i])), y=str(t_scale[0]*int(xc[i]))) ###outputSubTagSummary = etree.SubElement(outputTag, 'tag', name='summary') #etree.SubElement(outputSubTagSummary, 'tag',name='Model File', value=self.options.deepNetworkChoice) #etree.SubElement(outputSubTagSummary, 'tag',name='Segment Image', value=self.options.segmentImage) ###etree.SubElement(outputSubTagSummary, 'tag',name='Class', value=str(prediction_c)) ###etree.SubElement(outputSubTagSummary, 'tag', name='Class Confidence', value=str(confidence_c)) #etree.SubElement(outputSubTagSummary, 'tag',name='Type', value=str(prediction_t)) #etree.SubElement(outputSubTagSummary, 'tag', name='Type Confidence', value=str(confidence_t)) ''' self.bqSession.finish_mex(tags=[outputTag]) log.debug('FINISHED') self.bqSession.close() def main(self): """ The main function that runs everything """ print("DEBUG_INIT") log.debug('SkeletonPython is called with the following arguments') log.debug('sysargv : %s\n\n' % sys.argv) parser = OptionParser() parser.add_option('--image_url', dest="image_url") parser.add_option('--mex_url', dest="mexURL") parser.add_option('--module_dir', dest="modulePath") parser.add_option('--staging_path', dest="stagingPath") parser.add_option('--bisque_token', dest="token") parser.add_option('--user', dest="user") parser.add_option('--pwd', dest="pwd") parser.add_option('--root', dest="root") (options, args) = parser.parse_args() # Set up the mexURL and token based on the arguments passed to the script try: #pull out the mex log.debug("options %s" % options) if not options.mexURL: options.mexURL = sys.argv[1] if not options.token: options.token = sys.argv[2] except IndexError: #no argv were set pass if not options.stagingPath: options.stagingPath = '' # Still don't have an imgurl, but it will be set up in self.setup() log.debug('\n\nPARAMS : %s \n\n Options: %s' % (args, options)) self.options = options if self.validateInput(): try: #run setup and retrieve mex variables self.setup() except Exception, e: log.exception("Exception during setup") self.bqSession.fail_mex(msg="Exception during setup: %s" % str(e)) return try: #run module operation self.run() except SkeletonPythonError, e: log.exception("Exception during run") self.bqSession.fail_mex(msg="Exception during run: %s" % str(e.message)) return except Exception, e: log.exception("Exception during run") self.bqSession.fail_mex(msg="Exception during run: %s" % str(e)) return
class PythonScriptWrapper(object): def preprocess(self, bq): """ Pre-process the images """ log.info('Pre-Process Options: %s' % (self.options)) """ 1. Get the resource image 2. call hist.py with bq, log, resource_url, seeds, threshold ( bq, log, **self.options.__dict__ ) """ image = bq.load(self.options.resource_url) dt = self.getstrtime() self.tiff_file = os.path.join(self.options.stagingPath, TIFF_IMAGE_PATH, dt + '-' + image.name) log.info("process image as %s" % (self.tiff_file)) log.info("image meta: %s" % (image)) ip = image.pixels().format('tiff') meta = image.pixels().meta().fetch() #meta = ET.XML(meta) meta = bq.factory.string2etree(meta) z = meta.findall('.//tag[@name="image_num_z"]') z = len(z) and z[0].get('value') zplanes = int(z) #if int(self.options.seed_count) >= zplanes: #raise Exception("Seed out of bounds. Please input a valid seed less than %s" % (zplanes)) # log.info('INVALID SEED BRUH %s' % (zplanes)) with open(self.tiff_file, 'wb') as f: f.write(ip.fetch()) log.info('Executing Histogram match') hist_match.main(testing_data_dir, hist_data_dir) log.info('Completed Histogram match') return def predict(self, bq): """ Infer the probability map for the image """ log.info('Executing Inference') """ 1. call predict.py with bq, log, resource_url, seeds, threshold ( bq, log, **self.options.__dict__ ) main(model_path, cell_hist_datadir, prob_map_datadir) """ predict.main(model_path, hist_data_dir, prob_map_datadir) log.info('Completed Inference') return def postprocess(self, bq): """ Post-Process for the image. This will select seed slice index and provide a black threshold for mask creation """ log.info('Executing Post-process: %s' % (self.options)) #seeds_slice_id=int(self.options.seed_count) black_threshold = float(self.options.threshold) min_distance = float(self.options.min_dist) label_threshold = float(self.options.label_threshd) #log.info('MINIMUM DISTANCE : %f, LABELS THRESHOLD: %f, BLACK THRESHOLD: %f' %(min_distance, label_threshold, black_threshold)) output_files, adj_table, points, cell_vol, coordinates, center = postprocessing.main( bq, prob_map_datadir, results_outdir, testing_data_dir, min_distance, label_threshold, black_threshold) #outtable_xml_adj_table = table_service.store_array(adj_table, name='adj_table') #outtable_xml_points = table_service.store_array(points, name='points') #outtable_xml_cell_vol = table_service.store_array(cell_vol, name='cell_vol') log.info('Output files: %s' % str(output_files)) return output_files, adj_table, points, cell_vol, coordinates, center def getstrtime(self): # format timestamp ts = time.gmtime() ts_str = time.strftime("%Y-%m-%dT%H-%M-%S", ts) return ts_str def uploadimgservice(self, bq, files): """ Upload mask to image_service upon post process """ mex_id = bq.mex.uri.split('/')[-1] filename = os.path.basename(files[0]) log.info('Up Mex: %s' % (mex_id)) log.info('Up File: %s' % (filename)) resource = etree.Element('image', name='ModuleExecutions/CellSegment3D/' + filename) t = etree.SubElement(resource, 'tag', name="datetime", value=self.getstrtime()) log.info('Creating upload xml data: %s ' % str(etree.tostring(resource, pretty_print=True))) filepath = files[ 0] # os.path.join("ModuleExecutions","CellSegment3D", filename) # use import service to /import/transfer activating import service r = etree.XML(bq.postblob(filepath, xml=resource)).find('./') if r is None or r.get('uri') is None: bq.fail_mex(msg="Exception during upload results") else: log.info('Uploaded ID: %s, URL: %s' % (r.get('resource_uniq'), r.get('uri'))) bq.update_mex('Uploaded ID: %s, URL: %s' % (r.get('resource_uniq'), r.get('uri'))) self.furl = r.get('uri') self.fname = r.get('name') resource.set('value', self.furl) return resource def uploadtableservice(self, bq, files): """ Upload mask to image_service upon post process """ mex_id = bq.mex.uri.split('/')[-1] filename = os.path.basename(files) log.info('Up Mex: %s' % (mex_id)) log.info('Up File: %s' % (filename)) resource = etree.Element('table', name='ModuleExecutions/CellSegment3D/' + filename) t = etree.SubElement(resource, 'tag', name="datetime", value=self.getstrtime()) log.info('Creating upload xml data: %s ' % str(etree.tostring(resource, pretty_print=True))) filepath = files # os.path.join("ModuleExecutions","CellSegment3D", filename) # use import service to /import/transfer activating import service r = etree.XML(bq.postblob(filepath, xml=resource)).find('./') if r is None or r.get('uri') is None: bq.fail_mex(msg="Exception during upload results") else: log.info('Uploaded ID: %s, URL: %s' % (r.get('resource_uniq'), r.get('uri'))) bq.update_mex('Uploaded ID: %s, URL: %s' % (r.get('resource_uniq'), r.get('uri'))) self.furl = r.get('uri') self.fname = r.get('name') resource.set('value', self.furl) return resource def run(self): """ Run Python script """ bq = self.bqSession # table_service = bq.service ('table') # call scripts try: bq.update_mex('Pre-process the images') self.preprocess(bq) except (Exception, ScriptError) as e: log.exception("Exception during preprocess") bq.fail_mex(msg="Exception during pre-process: %s" % str(e)) return try: bq.update_mex('Infer the images') self.predict(bq) except (Exception, ScriptError) as e: log.exception("Exception during inference") bq.fail_mex(msg="Exception during inference: %s" % str(e)) return try: bq.update_mex('Post process the images') self.outfiles, self.outtable_xml_adj_table, self.outtable_xml_points, self.outtable_xml_cell_vol, self.outtable_xml_coordinates, self.outtable_xml_center = self.postprocess( bq) except (Exception, ScriptError) as e: log.exception("Exception during post-process") bq.fail_mex(msg="Exception during post-process: %s" % str(e)) return try: bq.update_mex('Uploading Mask result') self.resimage = self.uploadimgservice(bq, self.outfiles) bq.update_mex('Uploading Table result') self.restable = self.uploadtableservice( bq, 'source/hdf/PlantCellSegmentation.h5') except (Exception, ScriptError) as e: log.exception("Exception during upload result") bq.fail_mex(msg="Exception during upload result: %s" % str(e)) return log.info('Completed the workflow: %s' % (self.resimage.get('value'))) out_imgxml = """<tag name="Segmentation" type="image" value="%s"> <template> <tag name="label" value="Output image" /> </template> </tag>""" % (str(self.resimage.get('value'))) # format timestamp ts = time.gmtime() ts_str = time.strftime("%Y-%m-%d %H:%M:%S", ts) # outputs = predict( bq, log, **self.options.__dict__ ) #outtable_xml = table_service.store_array(maxMisorient, name='maxMisorientData') out_xml = """<tag name="Metadata"> <tag name="Adjacency Table" type="string" value="%s"/> <tag name="Three-way Conjuction Points" type="string" value="%s"/> <tag name="Cell Volume" type="string" value="%s"/> <tag name="Surface Coordinates" type="string" value="%s"/> <tag name="Cell Center" type="string" value="%s"/> <tag name="Output Table" type="resource" value="%s"/> </tag>""" % ( str(self.outtable_xml_adj_table), str(self.outtable_xml_points), str(self.outtable_xml_cell_vol), str( self.outtable_xml_coordinates), str( self.outtable_xml_center), self.restable.get('value')) outputs = [out_imgxml, out_xml] log.debug(outputs) # save output back to BisQue for output in outputs: self.output_resources.append(output) def setup(self): """ Pre-run initialization """ self.output_resources.append(output) def setup(self): """ Pre-run initialization """ self.bqSession.update_mex('Initializing...') self.mex_parameter_parser(self.bqSession.mex.xmltree) self.output_resources = [] def teardown(self): """ Post the results to the mex xml """ self.bqSession.update_mex('Returning results') outputTag = etree.Element('tag', name='outputs') for r_xml in self.output_resources: if isinstance(r_xml, basestring): r_xml = etree.fromstring(r_xml) res_type = r_xml.get('type', None) or r_xml.get( 'resource_type', None) or r_xml.tag # append reference to output if res_type in ['table', 'image']: outputTag.append(r_xml) #etree.SubElement(outputTag, 'tag', name='output_table' if res_type=='table' else 'output_image', type=res_type, value=r_xml.get('uri','')) else: outputTag.append(r_xml) #etree.SubElement(outputTag, r_xml.tag, name=r_xml.get('name', '_'), type=r_xml.get('type', 'string'), value=r_xml.get('value', '')) log.debug('Output Mex results: %s' % (etree.tostring(outputTag, pretty_print=True))) self.bqSession.finish_mex(tags=[outputTag]) def mex_parameter_parser(self, mex_xml): """ Parses input of the xml and add it to options attribute (unless already set) @param: mex_xml """ # inputs are all non-"script_params" under "inputs" and all params under "script_params" mex_inputs = mex_xml.xpath( 'tag[@name="inputs"]/tag[@name!="script_params"] | tag[@name="inputs"]/tag[@name="script_params"]/tag' ) if mex_inputs: for tag in mex_inputs: if tag.tag == 'tag' and tag.get( 'type', '') != 'system-input': #skip system input values if not getattr(self.options, tag.get('name', ''), None): log.debug('Set options with %s as %s' % (tag.get('name', ''), tag.get('value', ''))) setattr(self.options, tag.get('name', ''), tag.get('value', '')) else: log.debug('No Inputs Found on MEX!') def validate_input(self): """ Check to see if a mex with token or user with password was provided. @return True is returned if validation credention was provided else False is returned """ if (self.options.mexURL and self.options.token): #run module through engine service return True if (self.options.user and self.options.pwd and self.options.root): #run module locally (note: to test module) return True log.debug('Insufficient options or arguments to start this module') return False def main(self): parser = optparse.OptionParser() parser.add_option('--mex_url', dest="mexURL") parser.add_option('--module_dir', dest="modulePath") parser.add_option('--staging_path', dest="stagingPath") parser.add_option('--bisque_token', dest="token") parser.add_option('--user', dest="user") parser.add_option('--pwd', dest="pwd") parser.add_option('--root', dest="root") (options, args) = parser.parse_args() # Logging initializations fh = logging.FileHandler('scriptrun.log', mode='a') fh.setLevel(logging.DEBUG) formatter = logging.Formatter( '[%(asctime)s] %(levelname)8s --- %(message)s ' + '(%(filename)s:%(lineno)s)', datefmt='%Y-%m-%d %H:%M:%S') fh.setFormatter(formatter) log.addHandler(fh) try: #pull out the mex if not options.mexURL: options.mexURL = sys.argv[-2] if not options.token: options.token = sys.argv[-1] except IndexError: #no argv were set pass if not options.stagingPath: options.stagingPath = '' # Options configuration log.debug('PARAMS : %s Options: %s' % (args, options)) self.options = options if self.validate_input(): #initalizes if user and password are provided if (self.options.user and self.options.pwd and self.options.root): self.bqSession = BQSession().init_local( self.options.user, self.options.pwd, bisque_root=self.options.root) self.options.mexURL = self.bqSession.mex.uri #initalizes if mex and mex token is provided elif (self.options.mexURL and self.options.token): self.bqSession = BQSession().init_mex(self.options.mexURL, self.options.token) else: raise ScriptError( 'Insufficient options or arguments to start this module') # Setup the mex and sessions try: self.setup() except Exception as e: log.exception("Exception during setup") self.bqSession.fail_mex(msg="Exception during setup: %s" % str(e)) return # Execute the module functionality try: self.run() except (Exception, ScriptError) as e: log.exception("Exception during run") self.bqSession.fail_mex(msg="Exception during run: %s" % str(e)) return try: self.teardown() except (Exception, ScriptError) as e: log.exception("Exception during teardown") self.bqSession.fail_mex(msg="Exception during teardown: %s" % str(e)) return self.bqSession.close() log.debug('Session Close')
class PythonScriptWrapper(object): def run(self): """ Run Python script """ bq = self.bqSession # call script outputs = predict(bq, log, **self.options.__dict__) # save output back to BisQue for output in outputs: self.output_resources.append(output) def setup(self): """ Pre-run initialization """ self.bqSession.update_mex('Initializing...') self.mex_parameter_parser(self.bqSession.mex.xmltree) self.output_resources = [] def teardown(self): """ Post the results to the mex xml """ self.bqSession.update_mex('Returning results') outputTag = etree.Element('tag', name='outputs') for r_xml in self.output_resources: if isinstance(r_xml, basestring): r_xml = etree.fromstring(r_xml) res_type = r_xml.get('type', None) or r_xml.get( 'resource_type', None) or r_xml.tag # append reference to output if res_type in ['table', 'image']: outputTag.append(r_xml) #etree.SubElement(outputTag, 'tag', name='output_table' if res_type=='table' else 'output_image', type=res_type, value=r_xml.get('uri','')) else: outputTag.append(r_xml) #etree.SubElement(outputTag, r_xml.tag, name=r_xml.get('name', '_'), type=r_xml.get('type', 'string'), value=r_xml.get('value', '')) self.bqSession.finish_mex(tags=[outputTag]) def mex_parameter_parser(self, mex_xml): """ Parses input of the xml and add it to options attribute (unless already set) @param: mex_xml """ # inputs are all non-"script_params" under "inputs" and all params under "script_params" mex_inputs = mex_xml.xpath( 'tag[@name="inputs"]/tag[@name!="script_params"] | tag[@name="inputs"]/tag[@name="script_params"]/tag' ) if mex_inputs: for tag in mex_inputs: if tag.tag == 'tag' and tag.get( 'type', '') != 'system-input': #skip system input values if not getattr(self.options, tag.get('name', ''), None): log.debug('Set options with %s as %s' % (tag.get('name', ''), tag.get('value', ''))) setattr(self.options, tag.get('name', ''), tag.get('value', '')) else: log.debug('No Inputs Found on MEX!') def validate_input(self): """ Check to see if a mex with token or user with password was provided. @return True is returned if validation credention was provided else False is returned """ if (self.options.mexURL and self.options.token): #run module through engine service return True if (self.options.user and self.options.pwd and self.options.root): #run module locally (note: to test module) return True log.debug('Insufficient options or arguments to start this module') return False def main(self): parser = optparse.OptionParser() parser.add_option('--mex_url', dest="mexURL") parser.add_option('--module_dir', dest="modulePath") parser.add_option('--staging_path', dest="stagingPath") parser.add_option('--bisque_token', dest="token") parser.add_option('--user', dest="user") parser.add_option('--pwd', dest="pwd") parser.add_option('--root', dest="root") (options, args) = parser.parse_args() fh = logging.FileHandler('scriptrun.log', mode='a') fh.setLevel(logging.DEBUG) formatter = logging.Formatter( '[%(asctime)s] %(levelname)8s --- %(message)s ' + '(%(filename)s:%(lineno)s)', datefmt='%Y-%m-%d %H:%M:%S') fh.setFormatter(formatter) log.addHandler(fh) try: #pull out the mex if not options.mexURL: options.mexURL = sys.argv[-2] if not options.token: options.token = sys.argv[-1] except IndexError: #no argv were set pass if not options.stagingPath: options.stagingPath = '' log.info('\n\nPARAMS : %s \n\n Options: %s' % (args, options)) self.options = options if self.validate_input(): #initalizes if user and password are provided if (self.options.user and self.options.pwd and self.options.root): self.bqSession = BQSession().init_local( self.options.user, self.options.pwd, bisque_root=self.options.root) self.options.mexURL = self.bqSession.mex.uri #initalizes if mex and mex token is provided elif (self.options.mexURL and self.options.token): self.bqSession = BQSession().init_mex(self.options.mexURL, self.options.token) else: raise ScriptError( 'Insufficient options or arguments to start this module') try: self.setup() except Exception as e: log.exception("Exception during setup") self.bqSession.fail_mex(msg="Exception during setup: %s" % str(e)) return try: self.run() except (Exception, ScriptError) as e: log.exception("Exception during run") self.bqSession.fail_mex(msg="Exception during run: %s" % str(e)) return try: self.teardown() except (Exception, ScriptError) as e: log.exception("Exception during teardown") self.bqSession.fail_mex(msg="Exception during teardown: %s" % str(e)) return self.bqSession.close()
class PythonScriptWrapper(object): def preprocess(self, bq): log.info('Options: %s' % (self.options)) """ 1. Get the resource image """ image = bq.load(self.options.resource_url) dt = self.getstrtime() self.img_name = dt + '-' + image.name self.in_file = os.path.join(self.options.stagingPath, IMAGE_PATH, self.img_name) log.info("process image as %s" % (self.in_file)) log.info("image meta: %s" % (image)) ip = image.pixels() #.format('jpg') with open(self.in_file, 'wb') as f: f.write(ip.fetch()) return def detect(self, bq): """ 2. Infer the mask for the image & write file """ log.info('Executing Inference') image, r = detect(self.in_file) log.info('Completed Inference') self.out_file = os.path.join(self.options.stagingPath, "mask_" + self.img_name) fsave(image, r, outfile=self.out_file) return def getstrtime(self): # format timestamp ts = time.gmtime() ts_str = time.strftime("%Y-%m-%dT%H-%M-%S", ts) return ts_str def uploadimgservice(self, bq, out_file): """ 3. Upload mask to image_service upon post process """ mex_id = bq.mex.uri.split('/')[-1] filename = os.path.basename(out_file) log.info('Up Mex: %s' % (mex_id)) log.info('Up File: %s' % (filename)) resource = etree.Element('image', name='ModuleExecutions/MaskRCNN/' + filename) t = etree.SubElement(resource, 'tag', name="datetime", value=self.getstrtime()) log.info('Creating upload xml data: %s ' % str(etree.tostring(resource, pretty_print=True))) filepath = out_file r = etree.XML(bq.postblob(filepath, xml=resource)).find('./') if r is None or r.get('uri') is None: bq.fail_mex(msg="Exception during upload results") else: log.info('Uploaded ID: %s, URL: %s' % (r.get('resource_uniq'), r.get('uri'))) bq.update_mex('Uploaded ID: %s, URL: %s' % (r.get('resource_uniq'), r.get('uri'))) self.furl = r.get('uri') self.fname = r.get('name') resource.set('value', self.furl) return resource def run(self): """ Run Python script """ bq = self.bqSession # call scripts try: bq.update_mex('Pre-process the images') self.preprocess(bq) except (Exception, ScriptError) as e: log.exception("Exception during preprocess") bq.fail_mex(msg="Exception during pre-process: %s" % str(e)) return try: bq.update_mex('Infer the images') self.detect(bq) except (Exception, ScriptError) as e: log.exception("Exception during inference") bq.fail_mex(msg="Exception during inference: %s" % str(e)) return try: bq.update_mex('Uploading mask result') self.resimage = self.uploadimgservice(bq, self.out_file) except (Exception, ScriptError) as e: log.exception("Exception during upload result") bq.fail_mex(msg="Exception during upload result: %s" % str(e)) return log.info('Completed the workflow: %s' % (self.resimage.get('value'))) out_imgxml = """<tag name="Segmentation" type="image" value="%s"> <template> <tag name="label" value="Output image" /> </template> </tag>""" % (str(self.resimage.get('value'))) # format timestamp ts = time.gmtime() ts_str = time.strftime("%Y-%m-%d %H:%M:%S", ts) # outputs = predict( bq, log, **self.options.__dict__ ) out_xml = """<tag name="Metadata"> <tag name="mask_name" type="string" value="%s"/> <tag name="mask_url" type="string" value="%s"/> </tag>""" % (str( self.resimage.get('name')), str(self.resimage.get('value'))) outputs = [out_imgxml, out_xml] log.debug(outputs) # save output back to BisQue for output in outputs: self.output_resources.append(output) def setup(self): """ Pre-run initialization """ self.output_resources.append(output) def setup(self): """ Pre-run initialization """ self.bqSession.update_mex('Initializing...') self.mex_parameter_parser(self.bqSession.mex.xmltree) self.output_resources = [] def teardown(self): """ Post the results to the mex xml """ self.bqSession.update_mex('Returning results') outputTag = etree.Element('tag', name='outputs') for r_xml in self.output_resources: if isinstance(r_xml, str): r_xml = etree.fromstring(r_xml) res_type = r_xml.get('type', None) or r_xml.get( 'resource_type', None) or r_xml.tag # append reference to output if res_type in ['table', 'image']: outputTag.append(r_xml) else: outputTag.append(r_xml) log.debug('Output Mex results: %s' % (etree.tostring(outputTag, pretty_print=True))) self.bqSession.finish_mex(tags=[outputTag]) def mex_parameter_parser(self, mex_xml): """ Parses input of the xml and add it to options attribute (unless already set) @param: mex_xml """ # inputs are all non-"script_params" under "inputs" and all params under "script_params" mex_inputs = mex_xml.xpath( 'tag[@name="inputs"]/tag[@name!="script_params"] | tag[@name="inputs"]/tag[@name="script_params"]/tag' ) if mex_inputs: for tag in mex_inputs: if tag.tag == 'tag' and tag.get( 'type', '') != 'system-input': #skip system input values if not getattr(self.options, tag.get('name', ''), None): log.debug('Set options with %s as %s' % (tag.get('name', ''), tag.get('value', ''))) setattr(self.options, tag.get('name', ''), tag.get('value', '')) else: log.debug('No Inputs Found on MEX!') def validate_input(self): """ Check to see if a mex with token or user with password was provided. @return True is returned if validation credention was provided else False is returned """ if (self.options.mexURL and self.options.token): #run module through engine service return True if (self.options.user and self.options.pwd and self.options.root): #run module locally (note: to test module) return True log.debug('Insufficient options or arguments to start this module') return False def main(self): parser = optparse.OptionParser() parser.add_option('--mex_url', dest="mexURL") parser.add_option('--module_dir', dest="modulePath") parser.add_option('--staging_path', dest="stagingPath") parser.add_option('--bisque_token', dest="token") parser.add_option('--user', dest="user") parser.add_option('--pwd', dest="pwd") parser.add_option('--root', dest="root") (options, args) = parser.parse_args() # Logging initializations fh = logging.FileHandler('scriptrun.log', mode='a') fh.setLevel(logging.DEBUG) formatter = logging.Formatter( '[%(asctime)s] %(levelname)8s --- %(message)s ' + '(%(filename)s:%(lineno)s)', datefmt='%Y-%m-%d %H:%M:%S') fh.setFormatter(formatter) log.addHandler(fh) try: #pull out the mex if not options.mexURL: options.mexURL = sys.argv[-2] if not options.token: options.token = sys.argv[-1] except IndexError: #no argv were set pass if not options.stagingPath: options.stagingPath = '' # Options configuration log.debug('PARAMS : %s Options: %s' % (args, options)) self.options = options if self.validate_input(): #initalizes if user and password are provided if (self.options.user and self.options.pwd and self.options.root): self.bqSession = BQSession().init_local( self.options.user, self.options.pwd, bisque_root=self.options.root) self.options.mexURL = self.bqSession.mex.uri #initalizes if mex and mex token is provided elif (self.options.mexURL and self.options.token): self.bqSession = BQSession().init_mex(self.options.mexURL, self.options.token) else: raise ScriptError( 'Insufficient options or arguments to start this module') # Setup the mex and sessions try: self.setup() except Exception as e: log.exception("Exception during setup") self.bqSession.fail_mex(msg="Exception during setup: %s" % str(e)) return # Execute the module functionality try: self.run() except (Exception, ScriptError) as e: log.exception("Exception during run") self.bqSession.fail_mex(msg="Exception during run: %s" % str(e)) return try: self.teardown() except (Exception, ScriptError) as e: log.exception("Exception during teardown") self.bqSession.fail_mex(msg="Exception during teardown: %s" % str(e)) return self.bqSession.close() log.debug('Session Close')
class Botanicam(object): """ Botanicam Model """ def mex_parameter_parser(self, mex_xml): """ Parses input of the xml and add it to BotanicamTrainer's options attribute @param: mex_xml """ mex_inputs = mex_xml.xpath('tag[@name="inputs"]') if mex_inputs: for tag in mex_inputs[0]: if tag.tag == 'tag' and tag.attrib['type'] != 'system-input': log.debug('Set options with %s as %s' % (tag.attrib['name'], tag.attrib['value'])) setattr(self.options, tag.attrib['name'], tag.attrib['value']) else: log.debug('BotanicamFS: No Inputs Found on MEX!') def validateInput(self): """ Parses input of the xml and add it to Botanicam's options attribute @param: mex_xml """ if (self.options.mexURL and self.options.token): #run module through engine service return True if (self.options.user and self.options.pwd and self.options.root): #run module locally (note: to test module) return True log.debug( 'Botanicam: Insufficient options or arguments to start this module' ) return False def setup(self): """ Fetches the mex, appends input_configurations to the option attribute of Botanicam and looks up the model on bisque to classify the provided resource. """ log.debug('Initializing Mex...') if (self.options.user and self.options.pwd and self.options.root): self.bqSession = BQSession().init_local( self.options.user, self.options.pwd, bisque_root=self.options.root) self.options.mexURL = self.bqSession.mex.uri elif (self.options.mexURL and self.options.token): self.bqSession = BQSession().init_mex(self.options.mexURL, self.options.token) else: return self.mex_parameter_parser(self.bqSession.mex.xmltree) #finds and opens model file self.bqSession.update_mex('Initializing Classification Model...') log.debug('Forming Feature Requests...') #no options currently #combo = mex_xml.xpath('tag[@name="plant_part"]/@value')[0] combo = 'bush' if combo: if combo == 'bush': MODEL_QUERY[ 'tag_query'] = '"module_identifier":"Botanicam" AND "Classification Method":"Bush Descriptor"' elif combo == 'leaf': MODEL_QUERY[ 'tag_query'] = '"module_identifier":"Botanicam" AND "Classification Method":"Leaf Descriptor"' else: raise BotanicamError( 'The incorrect model type was found -> Model Type: %s' % combo) else: raise BotanicamError('No model type was choosen') query_xml = self.bqSession.fetchxml('/data_service/file', **MODEL_QUERY) self.options.model_url = None if len(query_xml) > 0: try: model_url = query_xml[0].attrib['uri'] self.options.model_url = model_url log.debug('Fetching Model @ %s' % model_url) self.model_xml = self.bqSession.fetchxml(model_url, view='deep') self.model_path = os.path.join(self.options.stagingPath, 'model') model = self.bqSession.load(model_url) model_url = self.bqSession.service_url( 'blob_service', path=model.resource_uniq) self.bqSession.fetchblob(model_url, path=self.model_path + '.zip') with zipfile.ZipFile(self.model_path + '.zip') as dirzip: dirzip.extractall(self.model_path) except BQCommError: raise BotanicamError( 'Model file was not found! Ask admin to set the correct model file' ) else: #run demo classifier model store in the module raise BotanicamError( 'No model file was found. Ask your admin to train a new model with \ the Botanicam Trainer.') self.bqSession.update_mex('Initialized...') log.debug( 'Botanicam: image URL: %s, mexURL: %s, stagingPath: %s, token: %s' % (self.options.image_url, self.options.mexURL, self.options.stagingPath, self.options.token)) def run(self): """ The core of the Botanicam Module Requests features on the image provided. Classifies each tile and picks a majority among the tiles. """ #parse requests self.bqSession.update_mex('Calculating Features...') log.debug('Forming Feature Requests...') #get rectanle gobjects for roi r_xml = self.bqSession.fetchxml(self.options.mexURL, view='deep') rectangles = r_xml.xpath( '//tag[@name="inputs"]/tag[@name="image_url"]/gobject[@name="roi"]/rectangle' ) image_xml = self.bqSession.fetchxml(self.options.image_url) image_url = self.bqSession.service_url( 'image_service', path=image_xml.attrib['resource_uniq']) if rectangles: #On chooses the first rectangle #construct operation node x1 = int(float(rectangles[0][0].attrib['x'])) y1 = int(float(rectangles[0][0].attrib['y'])) x2 = int(float(rectangles[0][1].attrib['x'])) y2 = int(float(rectangles[0][1].attrib['y'])) log.debug('Adding Crop: roi=%s,%s,%s,%s' % (x1, y1, x2, y2)) image_url = self.bqSession.c.prepare_url(image_url, roi='%s,%s,%s,%s' % (x1, y1, x2, y2)) try: feature_vectors = extract_bush_feature(self.bqSession, image_url) except FeatureError as e: raise BotanicamError(str(e)) #parse features self.bqSession.update_mex('Classifying Results...') log.debug('Classifying Results...') results = [] pca = joblib.load(os.path.join(self.model_path, 'pca_model')) clf = joblib.load(os.path.join(self.model_path, 'svm_model')) for f in feature_vectors: f_norm = pca.transform(f) results.append(int(clf.predict(f_norm))) class_count = np.bincount(np.array(results)) self.class_number = np.argmax(class_count) self.confidence = float( class_count[self.class_number]) / np.sum(class_count) log.debug('Found Class %s' % str(self.class_number)) def teardown(self): """ Posting results to the mex """ self.bqSession.update_mex('Returning results...') log.debug('Returning results...') tag_list = self.model_xml.xpath( 'tag[@name="Classes"]/tag[@value="%s"]' % str(self.class_number))[0] outputTag = etree.Element('tag', name='outputs') outputSubTag = etree.SubElement(outputTag, 'tag', name='summary') if self.options.model_url: etree.SubElement(outputSubTag, 'tag', name='Model File', value=self.options.model_url, type='url') else: etree.SubElement(outputSubTag, 'tag', name='Model File', value='Internal Model File') etree.SubElement(outputSubTag, 'tag', name='Class', value=str(self.class_number)) query = [] for t in tag_list: etree.SubElement(outputSubTag, 'tag', name=t.attrib['name'], value=t.attrib['value']) query.append('"%s":"%s"' % (t.attrib['name'], t.attrib['value'])) query = ' & '.join(query) etree.SubElement(outputSubTag, 'tag', name='confidence', value=str(self.confidence)) etree.SubElement(outputTag, 'tag', name='similar_images', value=query, type='browser') self.bqSession.finish_mex(tags=[outputTag]) log.debug('FINISHED') self.bqSession.close() def main(self): """ The main function that runs everything """ log.debug('sysargv : %s' % sys.argv) parser = OptionParser() parser.add_option('--image_url', dest="image_url") parser.add_option('--mex_url', dest="mexURL") parser.add_option('--module_dir', dest="modulePath") parser.add_option('--staging_path', dest="stagingPath") parser.add_option('--bisque_token', dest="token") parser.add_option('--user', dest="user") parser.add_option('--pwd', dest="pwd") parser.add_option('--root', dest="root") (options, args) = parser.parse_args() try: #pull out the mex if not options.mexURL: options.mexURL = sys.argv[1] if not options.token: options.token = sys.argv[2] except IndexError: #no argv were set pass if not options.stagingPath: options.stagingPath = '' log.debug('\n\nPARAMS : %s \n\n Options: %s' % (args, options)) self.options = options if self.validateInput(): try: #run setup and retrieve mex variables self.setup() except Exception, e: log.exception("Exception during setup") self.bqSession.fail_mex(msg="Exception during setup: %s" % str(e)) return try: #run module operation self.run() except BotanicamError, e: log.exception("Exception during run") self.bqSession.fail_mex(msg="Exception during run: %s" % str(e.message)) return except Exception, e: log.exception("Exception during run") self.bqSession.fail_mex(msg="Exception during run: %s" % str(e)) return
class ImageJ(object): """ ImageJ Module """ def mex_parameter_parser(self, mex_xml): """ Parses input of the xml and add it to ImageJ's options attribute (unless already set) @param: mex_xml """ # inputs are all non-"pipeline params" under "inputs" and all params under "pipeline_params" mex_inputs = mex_xml.xpath( 'tag[@name="inputs"]/tag[@name!="pipeline_params"] | tag[@name="inputs"]/tag[@name="pipeline_params"]/tag' ) if mex_inputs: for tag in mex_inputs: if tag.tag == 'tag' and tag.get( 'type', '') != 'system-input': #skip system input values if not getattr(self.options, tag.get('name', ''), None): log.debug('Set options with %s as %s' % (tag.get('name', ''), tag.get('value', ''))) setattr(self.options, tag.get('name', ''), tag.get('value', '')) else: log.debug('ImageJ: No Inputs Found on MEX!') def validate_input(self): """ Check to see if a mex with token or user with password was provided. @return True is returned if validation credention was provided else False is returned """ if (self.options.mexURL and self.options.token): #run module through engine service return True if (self.options.user and self.options.pwd and self.options.root): #run module locally (note: to test module) return True log.debug( 'ImageJ: Insufficient options or arguments to start this module') return False def setup(self): """ Fetches the mex and appends input_configurations to the option attribute of ImageJ """ self.bqSession.update_mex('Initializing...') self.mex_parameter_parser(self.bqSession.mex.xmltree) self.output_resources = [] self.ppops = None self.ppops_url = None def run(self): """ The core of the ImageJ runner """ module_time = datetime.now() #retrieve tags self.bqSession.update_mex('Extracting properties') #type check image_resource = self.bqSession.fetchxml(self.options.InputFile) if image_resource.tag != 'image': raise IJError("trying to run ImageJ on non-image resource") # run prerun operations filelist = self._run_prerun_ops(module_time, pipeline_url=self.options.pipeline_url, input_xml=image_resource) # create pipeline with correct parameters pipeline_params = self.bqSession.mex.xmltree.xpath( 'tag[@name="inputs"]/tag[@name="pipeline_params"]/tag') params = {} for tag in pipeline_params: params[tag.get('name', '')] = getattr(self.options, tag.get('name', '')) pipeline_file, err_file = self._instantiate_pipeline( pipeline_url=self.options.pipeline_url, params=params) if not pipeline_file: raise IJError("trying to run incompatible ImageJ pipeline") # run ImageJ on the pipeline self.bqSession.update_mex('Running ImageJ') log.debug('run: ImageJ-linux64 -batch %s on image %s', pipeline_file, filelist) res = 1 with open(err_file, 'w') as fo: # start virtual X server vfb = subprocess.Popen(['Xvfb', ':0']) # TODO: wait for Xvfb to show up? time.sleep(1) res = subprocess.call( [ '/module/Fiji.app/ImageJ-linux64', # '--headless', '-macro', pipeline_file ], env={'DISPLAY': ':0.0'}, # use virtual X server stderr=fo, stdout=fo) # stop virtual X server vfb.terminate() log.debug("ImageJ returned: %s", str(res)) # TODO: detect error somehow!!! # if res > 0: # err_msg = 'pipeline execution failed\n' # with open(err_file, 'r') as fo: # err_msg += ''.join(fo.readlines()) # if len(err_msg) > 1024: # err_msg = err_msg[:512] + '...' + err_msg[-512:] # raise IJError(err_msg) # run postrun operations self.output_resources = self._run_postrun_ops( module_time, pipeline_url=self.options.pipeline_url, output_name='output_image') def _cache_ppops(self, pipeline_url): if not self.ppops or self.ppops_url != pipeline_url: pipeline_path = urlparse.urlsplit(pipeline_url).path.split('/') pipeline_uid = pipeline_path[1] if is_uniq_code( pipeline_path[1]) else pipeline_path[2] url = self.bqSession.service_url('pipeline', path='/'.join([pipeline_uid] + ['ppops:imagej'])) self.ppops = json.loads(self.bqSession.c.fetch(url)) self.ppops_url = pipeline_url def _run_prerun_ops(self, module_time, pipeline_url, input_xml): """ Perform any operations necessary before the pipeline runs (e.g., extract image channels) and return filelist """ self._cache_ppops(pipeline_url) pre_ops = self.ppops['PreOps'] input_files = [] for op in pre_ops: input_files += self._run_single_op(module_time, op, input_xml) filelist_file = os.path.join(self.options.stagingPath, 'filelist.txt') with open(filelist_file, 'w') as fo: for input_file in input_files: fo.write(input_file + '\n') return input_files def _run_postrun_ops(self, module_time, pipeline_url, output_name): """ Perform any operations necessary after the pipeline finished (e.g., upload result tables) and return created resources """ self._cache_ppops(pipeline_url) post_ops = self.ppops['PostOps'] created_resources = [] for op in post_ops: created_resources += self._run_single_op(module_time, op, output_name) return created_resources def _run_single_op(self, module_time, op, input_xml=None, output_name=None): """ Perform single pre/post operation and return list of files or resources generated """ # replace special placeholders if 'id' in op and op['id'] == '@INPUT': op['id'] = input_xml.get('resource_uniq') if 'name' in op and op['name'] == '@OUTPUT': op['name'] = output_name if 'filename' in op and op['filename'] == '@OUTPUT': op['filename'] = output_name + '.tif' res = [] if op['service'] == 'image_service': # perform image_service operation log.debug("RUNOP %s" % str(op)) url = self.bqSession.service_url('image_service', path=op['id'] + op['ops']) # TODO: don't read image into memory!!! image_data = self.bqSession.c.fetch(url) if image_data: image_file = os.path.join(self.options.stagingPath, op['filename']) with open(image_file, 'w') as fo: fo.write(image_data) res += [image_file] elif op['service'] == 'data_service': # perform data_service operation doc = self.bqSession.fetchxml(url=op['id'], view='deep,clean') csv_file = os.path.join(self.options.stagingPath, op['filename']) matches = doc.xpath(op['ops']) if len(matches) > 0: with open(csv_file, 'w') as fo: for match_id in xrange(0, len(matches)): match = matches[match_id] line = '\t'.join([ match.get(attr_name, '') for attr_name in op['attrs'] ]) + '\n' fo.write(line) res += [csv_file] elif op['service'] == 'postblob': filename = op['filename'] with open(op['name']) as namef: resname = namef.readlines()[0].rstrip('\n') # upload image or table (check op['type']) dt = module_time.strftime('%Y%m%dT%H%M%S') final_output_file = "ModuleExecutions/ImageJ/%s/%s" % (dt, resname) cl_model = etree.Element('resource', resource_type=op['type'], name=final_output_file) # module identifier (a descriptor to be found by the ImageJ model) etree.SubElement(cl_model, 'tag', name='module_identifier', value='ImageJ') # hdf filename etree.SubElement(cl_model, 'tag', name='OutputFile', value=final_output_file) #description etree.SubElement(cl_model, 'tag', name='description', value='output from ImageJ Module') # post blob output_file = os.path.join(self.options.stagingPath, filename) if os.path.isfile(output_file): resource = self.bqSession.postblob(output_file, xml=cl_model) resource_xml = etree.fromstring(resource) res += [resource_xml[0]] elif op['service'] == 'posttag': with open(op['name']) as namef: lines = namef.readlines() resname = lines[0].rstrip('\n') resval = lines[1].rstrip('\n') res += [etree.Element('tag', name=resname, value=resval)] elif op['service'] == 'postpolygon': # add polygon gobject to mex # Read object measurements from csv and write to gobjects (header, records) = self._readCSV( os.path.join(self.options.stagingPath, op['filename'])) if header is not None: parentGObject = etree.Element('gobject', type='detected shapes', name='detected shapes') xcoords = [] ycoords = [] for i in range(len(records)): curr_id = int(records[i][header.index(op['id_col'])]) xcoords.append( float(records[i][header.index(op['x_coord'])])) ycoords.append( float(records[i][header.index(op['y_coord'])])) if i == len(records) - 1 or curr_id != int( records[i + 1][header.index(op['id_col'])]): # new polygon starts => save current one xcoords, ycoords = _simplify_polygon(xcoords, ycoords) shape = self._get_polygon_elem(name=str(i), xcoords=xcoords, ycoords=ycoords, label=op['label'], color=op['color']) if shape: parentGObject.append(shape) xcoords = [] ycoords = [] res += [parentGObject] return res def _instantiate_pipeline(self, pipeline_url, params): """ instantiate ImageJ pipeline file with provided parameters """ pipeline_path = urlparse.urlsplit(pipeline_url).path.split('/') pipeline_uid = pipeline_path[1] if is_uniq_code( pipeline_path[1]) else pipeline_path[2] url = self.bqSession.service_url( 'pipeline', path='/'.join( [pipeline_uid] + ["setvar:%s|%s" % (tag, params[tag]) for tag in params] + ['exbsteps:imagej']), query={'format': 'imagej'}) pipeline = self.bqSession.c.fetch(url) if not pipeline: # bad pipeline return None, None out_pipeline_file = os.path.join(self.options.stagingPath, 'macro.ijm') out_error_file = os.path.join(self.options.stagingPath, 'ij_error.txt') with open(out_pipeline_file, 'w') as fo: fo.write(pipeline) return out_pipeline_file, out_error_file def teardown(self): """ Post the results to the mex xml. """ self.bqSession.update_mex('Returning results') outputTag = etree.Element('tag', name='outputs') for r_xml in self.output_resources: res_type = r_xml.get('type', None) or r_xml.get( 'resource_type', None) or r_xml.tag if res_type == 'detected shapes': # r_xml is a set of gobjects => append to output inside image tag image_resource = self.bqSession.fetchxml( self.options.InputFile) image_elem = etree.SubElement(outputTag, 'tag', name=image_resource.get('name'), type='image', value=image_resource.get('uri')) image_elem.append(r_xml) elif res_type == 'tag': # simple tag => append to output as is etree.SubElement(outputTag, 'tag', name=r_xml.get('name'), value=r_xml.get('value')) else: # r_xml is some other resource (e.g., image or table) => append reference to output etree.SubElement(outputTag, 'tag', name='output_table' if res_type == 'table' else 'output_image', type=res_type, value=r_xml.get('uri', '')) self.bqSession.finish_mex(tags=[outputTag]) def _get_polygon_elem(self, name, **params): shape = etree.Element('gobject', name=name, type=params.get('label')) res = etree.SubElement(shape, 'polygon') xcoords = params.get('xcoords') ycoords = params.get('ycoords') try: for i in range(len(xcoords)): x = xcoords[i] y = ycoords[i] etree.SubElement(res, 'vertex', x=str(x), y=str(y)) etree.SubElement(res, 'tag', name='color', value=params.get('color', '#FF0000'), type='color') except KeyError: return None except ValueError: return None return shape def _readCSV(self, fileName): if os.path.exists(fileName) == False: return (None, None) records = [] handle = open(fileName, 'rb') csvHandle = csv.reader(handle) header = csvHandle.next() for row in csvHandle: records.append(row) handle.close() return (header, records) def main(self): """ The main function that runs everything """ log.info('sysargv : %s' % sys.argv) parser = OptionParser() parser.add_option('--mex_url', dest="mexURL") parser.add_option('--module_dir', dest="modulePath") parser.add_option('--staging_path', dest="stagingPath") parser.add_option('--bisque_token', dest="token") parser.add_option('--user', dest="user") parser.add_option('--pwd', dest="pwd") parser.add_option('--root', dest="root") parser.add_option('--entrypoint', dest="entrypoint") (options, args) = parser.parse_args() fh = logging.FileHandler('phase_%s.log' % (options.entrypoint or 'main'), mode='a') fh.setLevel(logging.DEBUG) formatter = logging.Formatter( '[%(asctime)s] %(levelname)8s --- %(message)s ' + '(%(filename)s:%(lineno)s)', datefmt='%Y-%m-%d %H:%M:%S') fh.setFormatter(formatter) log.addHandler(fh) try: #pull out the mex if not options.mexURL: options.mexURL = sys.argv[1] if not options.token: options.token = sys.argv[2] except IndexError: #no argv were set pass if not options.stagingPath: options.stagingPath = '/module/workdir' log.info('\n\nPARAMS : %s \n\n Options: %s' % (args, options)) self.options = options if self.validate_input(): #initalizes if user and password are provided if (self.options.user and self.options.pwd and self.options.root): self.bqSession = BQSession().init_local( self.options.user, self.options.pwd, bisque_root=self.options.root) self.options.mexURL = self.bqSession.mex.uri #initalizes if mex and mex token is provided elif (self.options.mexURL and self.options.token): self.bqSession = BQSession().init_mex(self.options.mexURL, self.options.token) else: raise IJError( 'Insufficient options or arguments to start this module') if not self.options.entrypoint: # NOT a special phase => perform regular run processing try: self.setup() except Exception as e: log.exception("Exception during setup") self.bqSession.fail_mex(msg="Exception during setup: %s" % str(e)) return try: self.run() except (Exception, IJError) as e: log.exception("Exception during run") self.bqSession.fail_mex(msg="Exception during run: %s" % str(e)) return try: self.teardown() except (Exception, IJError) as e: log.exception("Exception during teardown") self.bqSession.fail_mex( msg="Exception during teardown: %s" % str(e)) return else: # in a special phase => run special code self.bqSession.fail_mex(msg="Unknown ImageJ entrypoint: %s" % self.options.entrypoint) return self.bqSession.close()
class PlanteomeDeepSegment(object): # + # __init__() # - def __init__(self): # entry message log.debug('{}.__init__()> message on entry'.format(MODULE_NAME)) # declare some variables and initialize them self.options = None self.bqSession = None self.rois = None self.message = None # get full path(s) to file(s) self.contours_file = os.path.abspath( os.path.expanduser(PICKLE_CONTOURS_FILE)) self.data_file = os.path.abspath(os.path.expanduser(PICKLE_DATA_FILE)) self.results_file = os.path.abspath( os.path.expanduser(TEXT_RESULTS_FILE)) self.tiff_file = os.path.abspath(os.path.expanduser(TIFF_IMAGE_FILE)) self.csv_leaf_file = os.path.abspath(os.path.expanduser(CSV_LEAF_FILE)) log.debug('{}.__init()> self.contours_file={}'.format( MODULE_NAME, self.contours_file)) log.debug('{}.__init()> self.data_file={}'.format( MODULE_NAME, self.data_file)) log.debug('{}.__init()> self.results_file={}'.format( MODULE_NAME, self.results_file)) log.debug('{}.__init()> self.tiff_file={}'.format( MODULE_NAME, self.tiff_file)) log.debug('{}.__init()> self.csv_leaf_file={}'.format( MODULE_NAME, self.csv_leaf_file)) # exit message log.debug('{}.__init__()> message on exit'.format(MODULE_NAME)) # + # hidden method: _mex_parameter_parser() # - def _mex_parameter_parser(self, mex_xml=None): # entry message log.debug( '{}._mex_parameter_parser()> message on entry, mex_xml={}'.format( MODULE_NAME, str(mex_xml))) if mex_xml is not None: mex_inputs = mex_xml.xpath('tag[@name="inputs"]') if mex_inputs: for tag in mex_inputs[0]: if tag.tag == 'tag' and tag.attrib[ 'type'] != 'system-input': _name = tag.attrib['name'].strip() _value = tag.attrib['value'].strip() log.debug( '{}._mex_parameter_parser()> setting self.options.{}={}' .format(MODULE_NAME, _name, _value)) setattr(self.options, _name, _value) log.debug( "{}._mex_parameter_parser()> set self.options.{}={}" .format(MODULE_NAME, _name, getattr(self.options, _name))) else: log.error('{}.mex_parameter_parser()> no inputs found on mex!'. format(MODULE_NAME)) else: self.message = '{}.mex_parameter_parser()> mex_xml is None'.format( MODULE_NAME) log.error(self.message) # exit message log.debug('{}.main()> message on exit, options={}'.format( MODULE_NAME, self.options)) # + # hidden method: _validate_input() # - def _validate_input(self): # entry message retval = False log.debug('{}._validate_input()> message on entry, retval={}'.format( MODULE_NAME, retval)) # run module through engine_service (default) if self.options.mexURL and self.options.token: retval = True # run module locally elif self.options.user and self.options.pwd and self.options.root: retval = True else: retval = False log.error( '{}.validate_input()> insufficient options or arguments to start this module' .format(MODULE_NAME)) # exit message log.debug('{}._validate_input()> message on exit, retval={}'.format( MODULE_NAME, retval)) return retval # + # hidden method: _construct_vertices() # - def _construct_vertices(self, child=None): # entry message vertices = None roi = [] log.debug( '{}._construct_vertices()> message on entry, child={}'.format( MODULE_NAME, str(child))) # get annotation type if child is not None: annotation_type = 'fg' if 'foreground' in child.values() else 'bg' # get vertices vertices = child.getchildren()[0].getchildren() for _vertex in vertices: _values = _vertex.values() roi.append({ 'x': int(float(_values[2])), 'y': int(float(_values[3])) }) log.debug( '{}._construct_vertices()> ROI: appending {} value with {}'. format(MODULE_NAME, annotation_type, str(roi))) self.rois[annotation_type].append(roi) # exit message log.debug( '{}._construct_vertices()> message on exit, vertices={}, length={}, rois={}' .format(MODULE_NAME, str(vertices), len(vertices), str(self.rois))) # + # hidden method: _show_structure() # - def _show_structure(self, r_xml=None): # entry message log.debug('{}._show_structure()> message on entry, r_xml={}'.format( MODULE_NAME, str(r_xml))) if r_xml is not None: for _i, _child in enumerate(r_xml.getchildren()): log.debug('{}._show_structure()> index={}, child={}'.format( MODULE_NAME, _i, str(_child))) log.debug('{}._show_structure()> index={}, values={}'.format( MODULE_NAME, _i, str(_child.values()))) if 'background' in _child.values( ) or 'foreground' in _child.values(): self._construct_vertices(_child) else: self._show_structure(_child) # exit message log.debug('{}._show_structure()> message on exit'.format(MODULE_NAME)) # + # method: setup() # - def setup(self): # entry message log.debug('{}.setup()> message on entry, options={}'.format( MODULE_NAME, self.options)) # run locally if self.options.user and self.options.pwd and self.options.root: log.debug( '{}.setup()> running locally with user={}, pwd={}, root={}'. format(MODULE_NAME, self.options.user, self.options.pwd, self.options.root)) self.bqSession = BQSession().init_local( self.options.user, self.options.pwd, bisque_root=self.options.root) self.options.mexURL = self.bqSession.mex.uri # run on the server with a mexURL and an access token elif self.options.mexURL and self.options.token: log.debug('{}.setup()> running on server with mexURL={}, token={}'. format(MODULE_NAME, self.options.mexURL, self.options.token)) self.bqSession = BQSession().init_mex(self.options.mexURL, self.options.token) # failed to connect to bisque else: self.bqSession = None self.message( '{}.setup()> failed to connect to bisque'.format(MODULE_NAME)) log.error(self.message) raise PlanteomeDeepSegmentError(self.message) # parse the xml and construct the tree, also set options to proper values after parsing it if self.bqSession is not None: self._mex_parameter_parser(self.bqSession.mex.xmltree) log.debug( '{}.setup()> image URL={}, mexURL={}, stagingPath={}, token={}' .format(MODULE_NAME, self.options.image_url, self.options.mexURL, self.options.stagingPath, self.options.token)) # exit message log.debug('{}.setup()> message on exit, options={}'.format( MODULE_NAME, self.options)) # + # method: run() # The core of the PlanteomeDeepSegment module. It requests features on the provided image, classifies each tile # and selects a majority amongst the tiles. # - def run(self): # entry message log.debug('{}.run()> message on entry, options={}'.format( MODULE_NAME, self.options)) self.rois = {'fg': [], 'bg': []} r_xml = self.bqSession.fetchxml(self.options.mexURL, view='deep') log.debug('{}.run()> Shols structura'.format(MODULE_NAME)) self._show_structure(r_xml) log.debug(self.rois) # dump image as .tiff image = self.bqSession.load(self.options.image_url) ip = image.pixels().format('tiff') with open(self.tiff_file, 'wb') as f: f.write(ip.fetch()) # pickle the data try: if self.rois and getattr(self.options, 'segmentImage') != '' and \ getattr(self.options, 'deepNetworkChoice') != '' and getattr(self.options, 'qualitySeg') != '' and \ getattr(self.options, 'deepSeg') != '' and getattr(self.options, 'mexURL') != '' and \ getattr(self.options, 'token') != '': log.debug('{}.run()> pickling data to {}'.format( MODULE_NAME, self.data_file)) pickle.dump([ self.rois, self.options.segmentImage, self.options.deepNetworkChoice, self.options.qualitySeg, self.options.deepSeg, self.options.mexURL, self.options.token ], open(self.data_file, 'wb')) except AttributeError as e: self.message('{}.run()> failed to pickle data, e={}'.format( MODULE_NAME, str(e))) log.error(self.message) # do something x = PlanteomeDeepSegmentLearning(self.contours_file, self.data_file, self.tiff_file, self.results_file) x.main() # exit message log.debug('{}.run()> message on exit, options={}'.format( MODULE_NAME, self.options)) # + # method: teardown() # - def teardown(self): # entry message log.debug('{}.teardown()> message on entry, options={}'.format( MODULE_NAME, self.options)) # set up tag(s) self.bqSession.update_mex('Returning results...') output_tag = eTree.Element('tag', name='outputs') output_sub_tag_image = eTree.SubElement(output_tag, 'tag', name='Final Image', value=self.options.image_url) output_sub_tag_summary = eTree.SubElement(output_tag, 'tag', name='summary') log.info('Module will output image {}'.format(self.options.image_url)) # segment the image (if required) if getattr(self.options, 'segmentImage', '') != '' and self.options.segmentImage.lower() == 'true' and \ os.path.isfile(self.contours_file): log.debug( '{}.teardown()> module will segment image from file {}'.format( MODULE_NAME, self.contours_file)) eTree.SubElement(output_sub_tag_summary, 'tag', name='Segment Image', value=self.options.segmentImage) [_contours, _t_scale] = pickle.load(open(self.contours_file, 'rb')) _gob = eTree.SubElement(output_sub_tag_image, 'gobject', name='Annotations', type='Annotations') _polyseg = eTree.SubElement(_gob, 'polygon', name='SEG') eTree.SubElement(_polyseg, 'tag', name='color', value="#0000FF") _opd = 0 _output_sampling = 1 + int(len(_contours) / 100) for _j in range(len(_contours)): if _j % _output_sampling == 0: _opd += 1 _x = str(1 + int(_t_scale[1] * _contours[_j][1])) _y = str(1 + int(_t_scale[0] * _contours[_j][0])) eTree.SubElement(_polyseg, 'vertex', x=_x, y=_y) log.debug('{}.teardown()> _opd={}'.format(MODULE_NAME, _opd)) else: log.info( 'Module will not segment image, (were foreground and background polyline annotations provided?)' ) # select deepNetworkChoice opts = getattr(self.options, 'deepNetworkChoice', '') eTree.SubElement(output_sub_tag_summary, 'tag', name='Model File', value=opts) if opts == '': log.error('{}.teardown()> deepNetworkChoice={}'.format( MODULE_NAME, self.options.deepNetworkChoice)) else: # simple classification if opts.split()[0].lower() == 'simple': # get prediction prediction_c = -1 confidence_c = 0.0 prediction_t = -1 confidence_t = 0.0 try: with open(self.results_file, 'r') as f: for _line in f: if _line.strip() != '': log.debug('{}.teardown()> _line={}'.format( MODULE_NAME, _line)) if 'PREDICTION_C:' in _line: prediction_c = int( _line.split(':')[1].strip()) if 'CONFIDENCE_C:' in _line: confidence_c = float( _line.split(':')[1].strip()) if 'PREDICTION_T:' in _line: prediction_t = int( _line.split(':')[1].strip()) if 'CONFIDENCE_T:' in _line: confidence_t = float( _line.split(':')[1].strip()) except IOError as e: self.message = '{}.teardown()> io error reading results, e={}'.format( MODULE_NAME, str(e)) log.error(self.message) finally: log.debug('{}.teardown()> prediction_c={}'.format( MODULE_NAME, prediction_c)) log.debug('{}.teardown()> confidence_c={}'.format( MODULE_NAME, confidence_c)) log.debug('{}.teardown()> prediction_t={}'.format( MODULE_NAME, prediction_t)) log.debug('{}.teardown()> confidence_t={}'.format( MODULE_NAME, confidence_t)) # annotate with prediction classes = [ 'Leaf (PO:0025034): http://browser.planteome.org/amigo/term/PO:0025034', 'Fruit (PO:0009001): http://browser.planteome.org/amigo/term/PO:0009001', 'Flower (PO:0009046): http://browser.planteome.org/amigo/term/PO:0009046', 'Stem (PO:0009047): http://browser.planteome.org/amigo/term/PO:0009047', 'Whole plant (PO:0000003): http://browser.planteome.org/amigo/term/PO:0000003 ' ] prediction_c = classes[prediction_c] if ( 0 <= prediction_c <= len(classes)) else 'unknown' eTree.SubElement(output_sub_tag_summary, 'tag', name='Class', value=prediction_c) if prediction_c.lower() != 'unknown': eTree.SubElement( output_sub_tag_summary, 'tag', type='link', name='Class link', value=prediction_c.split('):')[-1].strip()) eTree.SubElement(output_sub_tag_summary, 'tag', name='Class Confidence', value=str(confidence_c)) # leaf classification elif opts.split()[0].lower() == 'leaf': log.debug('{}.teardown()> leaf_targets{}'.format( MODULE_NAME, leaf_targets)) log.debug('{}.teardown()> leaf_targets_links{}'.format( MODULE_NAME, leaf_targets_links)) # map each leaf target to the corresponding PO term with open(self.csv_leaf_file) as cf: reader = csv.reader(cf, delimiter=',', quotechar='|') _cn = '' for _row in reader: _n = _row[0] if _row[0] != '' else 'undefined' _m = _row[1] if _row[1] != '' else 'undefined' # _c = _row[2] if _row[2] != '' else 'undefined' _t = _n.replace(' ', '').lower() # get the current name for _lc in leaf_keys_nospaces: if _t == _lc: _cn = leaf_keys_spaces[ leaf_keys_nospaces.index(_lc)] break # replace dictionary entry is mapping exists if _cn in leaf_targets: for _l in leaf_targets[_cn]: if _n == _l.replace(' ', ''): _i = leaf_targets[_cn].index(_l) leaf_targets_links[_cn][_i] = _m break # read result(s) with open(self.results_file, 'r') as f: class_list = [] for _i, _line in enumerate(f): # remove after introduction of the leaf classifier (below start with appends) if int(_line) == len(leaf_targets[leaf_keys[_i]]) - 1: _line = '0' class_list.append(_line) eTree.SubElement(output_sub_tag_summary, 'tag', name='{}-Name'.format( leaf_keys_proper_names[_i]), value=leaf_targets[leaf_keys[_i]][int( class_list[_i])]) if leaf_targets_links[leaf_keys[_i]][int( class_list[_i])] != 'undefined': eTree.SubElement( output_sub_tag_summary, 'tag', type='link', name='{}-Accession'.format( leaf_keys_proper_names[_i]), value= 'http://browser.planteome.org/amigo/term/{}'. format(leaf_targets_links[leaf_keys[_i]][int( class_list[_i])])) else: eTree.SubElement( output_sub_tag_summary, 'tag', name='{}-Accession'.format( leaf_keys_proper_names[_i]), value=leaf_targets_links[leaf_keys[_i]][int( class_list[_i])]) # update mex self.bqSession.finish_mex(tags=[output_tag]) self.bqSession.close() # exit message log.debug('{}.teardown()> message on exit, options={}'.format( MODULE_NAME, self.options)) # + # method: main() # - def main(self): # entry message log.debug('{}.main()> message on entry, args={}'.format( MODULE_NAME, sys.argv)) parser = OptionParser() parser.add_option('--image_url', dest="image_url") parser.add_option('--mex_url', dest="mexURL") parser.add_option('--module_dir', dest="modulePath") parser.add_option('--staging_path', dest="stagingPath") parser.add_option('--bisque_token', dest="token") parser.add_option('--user', dest="user") parser.add_option('--pwd', dest="pwd") parser.add_option('--root', dest="root") (options, args) = parser.parse_args() log.debug('{}.main()> options={}'.format(MODULE_NAME, options)) log.debug('{}.main()> args={}'.format(MODULE_NAME, args)) # set up the mexURL and token based on the arguments passed to the script try: if not options.mexURL: options.mexURL = sys.argv[1] if not options.token: options.token = sys.argv[2] if not options.stagingPath: options.stagingPath = '' except IndexError: pass finally: self.options = options log.debug('{}.main()> self.options={}'.format( MODULE_NAME, self.options)) # check input(s) if self._validate_input(): # noinspection PyBroadException try: # set up the module self.setup() except PlanteomeDeepSegmentError as e: self.message = '{}.main()> specific exception after setup(), e={}'.format( MODULE_NAME, str(e.errstr)) log.exception(self.message) self.bqSession.fail_mex(msg=self.message) raise PlanteomeDeepSegmentError(self.message) except Exception as e: self.message = '{}.main()> exception after setup(), e={}'.format( MODULE_NAME, str(e)) log.exception(self.message) self.bqSession.fail_mex(msg=self.message) raise PlanteomeDeepSegmentError(self.message) except: self.message = '{}.main()> error after setup()'.format( MODULE_NAME) log.exception(self.message) self.bqSession.fail_mex(msg=self.message) raise PlanteomeDeepSegmentError(self.message) # noinspection PyBroadException try: # run the module self.run() except PlanteomeDeepSegmentError as e: self.message = '{}.main()> specific exception after run(), e={}'.format( MODULE_NAME, str(e.errstr)) log.exception(self.message) self.bqSession.fail_mex(msg=self.message) raise PlanteomeDeepSegmentError(self.message) except Exception as e: self.message = '{}.main()> exception after run(), e={}'.format( MODULE_NAME, str(e)) log.exception(self.message) self.bqSession.fail_mex(msg=self.message) raise PlanteomeDeepSegmentError(self.message) except: self.message = '{}.main()> error after run()'.format( MODULE_NAME) log.exception(self.message) self.bqSession.fail_mex(msg=self.message) raise PlanteomeDeepSegmentError(self.message) # noinspection PyBroadException try: # tear down the module self.teardown() except PlanteomeDeepSegmentError as e: self.message = '{}.main()> specific exception after teardown(), e={}'.format( MODULE_NAME, str(e.errstr)) log.exception(self.message) self.bqSession.fail_mex(msg=self.message) raise PlanteomeDeepSegmentError(self.message) except Exception as e: self.message = '{}.main()> exception after teardown(), e={}'.format( MODULE_NAME, str(e)) log.exception(self.message) self.bqSession.fail_mex(msg=self.message) raise PlanteomeDeepSegmentError(self.message) except: self.message = '{}.main()> error after teardown()'.format( MODULE_NAME) log.exception(self.message) self.bqSession.fail_mex(msg=self.message) raise PlanteomeDeepSegmentError(self.message) else: self.message = '{}.main()> failed to validate instance'.format( MODULE_NAME) log.error(self.message) self.bqSession.fail_mex(msg=self.message) raise PlanteomeDeepSegmentError(self.message) # exit message log.debug('{}.main()> message on exit, args={}'.format( MODULE_NAME, sys.argv))