def ssgsea(data, gene_sets, outdir="ssGSEA_", sample_norm_method='rank', min_size=15, max_size=2000,
           permutation_num=0, weighted_score_type=0.25, scale=True, ascending=False, processes=1,
           figsize=(7,6), format='pdf', graph_num=20, no_plot=False, seed=None, verbose=False):
    """Run Gene Set Enrichment Analysis with single sample GSEA tool

    :param data: Expression table, pd.Series, pd.DataFrame, GCT file, or .rnk file format.
    :param gene_sets: Enrichr Library name or .gmt gene sets file or dict of gene sets. Same input with GSEA.
    :param outdir: Results output directory.
    :param str sample_norm_method: "Sample normalization method. Choose from {'rank', 'log', 'log_rank'}. Default: rank.

               1. 'rank': Rank your expression data, and transform by 10000*rank_dat/gene_numbers
               2. 'log' : Do not rank, but transform data by log(data + exp(1)), while data = data[data<1] =1.
               3. 'log_rank': Rank your expression data, and transform by log(10000*rank_dat/gene_numbers+ exp(1))
               4. 'custom': Do nothing, and use your own rank value to calculate enrichment score.

    see here: https://github.com/GSEA-MSigDB/ssGSEAProjection-gpmodule/blob/master/src/ssGSEAProjection.Library.R, line 86

    :param int min_size: Minimum allowed number of genes from gene set also the data set. Default: 15.
    :param int max_size: Maximum allowed number of genes from gene set also the data set. Default: 2000.
    :param int permutation_num: Number of permutations for significance computation. Default: 0.
    :param str weighted_score_type: Refer to :func:`algorithm.enrichment_score`. Default:0.25.
    :param bool scale: If True, normalize the scores by number of genes in the gene sets.
    :param bool ascending: Sorting order of rankings. Default: False.
    :param int processes: Number of Processes you are going to use. Default: 1.
    :param list figsize: Matplotlib figsize, accept a tuple or list, e.g. [width,height]. Default: [7,6].
    :param str format: Matplotlib figure format. Default: 'pdf'.
    :param int graph_num: Plot graphs for top sets of each phenotype.
    :param bool no_plot: If equals to True, no figure will be drawn. Default: False.
    :param seed: Random seed. expect an integer. Default:None.
    :param bool verbose: Bool, increase output verbosity, print out progress of your job, Default: False.

    :return: Return a ssGSEA obj.
             All results store to  a dictionary, access enrichment score by obj.resultsOnSamples,
             and normalized enrichment score by obj.res2d.
             if permutation_num > 0, additional results contain::

                 | {es: enrichment score,
                 |  nes: normalized enrichment score,
                 |  p: P-value,
                 |  fdr: FDR,
                 |  size: gene set size,
                 |  matched_size: genes matched to the data,
                 |  genes: gene names from the data set
                 |  ledge_genes: leading edge genes, if permutation_num >0}

    """

    ss = SingleSampleGSEA(data, gene_sets, outdir, sample_norm_method, min_size, max_size,
                          permutation_num, weighted_score_type, scale, ascending,
                          processes, figsize, format, graph_num, no_plot, seed, verbose)
    ss.run()
    return ss

def ssgsea ( data , gene_sets , outdir = "ssGSEA_" , sample_norm_method = 'rank' , min_size = 15 , max_size = 2000 , permutation_num = 0 , weighted_score_type = 0.25 , scale = True , ascending = False , processes = 1 , figsize = ( 7 , 6 ) , format = 'pdf' , graph_num = 20 , no_plot = False , seed = None , verbose = False ) : ss = SingleSampleGSEA ( data , gene_sets , outdir , sample_norm_method , min_size , max_size , permutation_num , weighted_score_type , scale , ascending , processes , figsize , format , graph_num , no_plot , seed , verbose ) ss . run ( ) return ss


def prerank(rnk, gene_sets, outdir='GSEA_Prerank', pheno_pos='Pos', pheno_neg='Neg',
            min_size=15, max_size=500, permutation_num=1000, weighted_score_type=1,
            ascending=False, processes=1, figsize=(6.5,6), format='pdf',
            graph_num=20, no_plot=False, seed=None, verbose=False):
    """ Run Gene Set Enrichment Analysis with pre-ranked correlation defined by user.

    :param rnk: pre-ranked correlation table or pandas DataFrame. Same input with ``GSEA`` .rnk file.
    :param gene_sets: Enrichr Library name or .gmt gene sets file or dict of gene sets. Same input with GSEA.
    :param outdir: results output directory.
    :param int permutation_num: Number of permutations for significance computation. Default: 1000.
    :param int min_size: Minimum allowed number of genes from gene set also the data set. Default: 15.
    :param int max_size: Maximum allowed number of genes from gene set also the data set. Defaults: 500.
    :param str weighted_score_type: Refer to :func:`algorithm.enrichment_score`. Default:1.
    :param bool ascending: Sorting order of rankings. Default: False.
    :param int processes: Number of Processes you are going to use. Default: 1.
    :param list figsize: Matplotlib figsize, accept a tuple or list, e.g. [width,height]. Default: [6.5,6].
    :param str format: Matplotlib figure format. Default: 'pdf'.
    :param int graph_num: Plot graphs for top sets of each phenotype.
    :param bool no_plot: If equals to True, no figure will be drawn. Default: False.
    :param seed: Random seed. expect an integer. Default:None.
    :param bool verbose: Bool, increase output verbosity, print out progress of your job, Default: False.

    :return: Return a Prerank obj. All results store to  a dictionary, obj.results,
             where contains::

                 | {es: enrichment score,
                 |  nes: normalized enrichment score,
                 |  p: P-value,
                 |  fdr: FDR,
                 |  size: gene set size,
                 |  matched_size: genes matched to the data,
                 |  genes: gene names from the data set
                 |  ledge_genes: leading edge genes}

    """
    pre = Prerank(rnk, gene_sets, outdir, pheno_pos, pheno_neg,
                  min_size, max_size, permutation_num, weighted_score_type,
                  ascending, processes, figsize, format, graph_num, no_plot, seed, verbose)
    pre.run()
    return pre

def prerank ( rnk , gene_sets , outdir = 'GSEA_Prerank' , pheno_pos = 'Pos' , pheno_neg = 'Neg' , min_size = 15 , max_size = 500 , permutation_num = 1000 , weighted_score_type = 1 , ascending = False , processes = 1 , figsize = ( 6.5 , 6 ) , format = 'pdf' , graph_num = 20 , no_plot = False , seed = None , verbose = False ) : pre = Prerank ( rnk , gene_sets , outdir , pheno_pos , pheno_neg , min_size , max_size , permutation_num , weighted_score_type , ascending , processes , figsize , format , graph_num , no_plot , seed , verbose ) pre . run ( ) return pre

Run Gene Set Enrichment Analysis with pre - ranked correlation defined by user .


def replot(indir, outdir='GSEA_Replot', weighted_score_type=1,
           min_size=3, max_size=1000, figsize=(6.5,6), graph_num=20, format='pdf', verbose=False):
    """The main function to reproduce GSEA desktop outputs.

    :param indir: GSEA desktop results directory. In the sub folder, you must contain edb file folder.
    :param outdir: Output directory.
    :param float weighted_score_type: weighted score type. choose from {0,1,1.5,2}. Default: 1.
    :param list figsize: Matplotlib output figure figsize. Default: [6.5,6].
    :param str format: Matplotlib output figure format. Default: 'pdf'.
    :param int min_size: Min size of input genes presented in Gene Sets. Default: 3.
    :param int max_size: Max size of input genes presented in Gene Sets. Default: 5000.
                     You are not encouraged to use min_size, or max_size argument in :func:`replot` function.
                     Because gmt file has already been filtered.
    :param verbose: Bool, increase output verbosity, print out progress of your job, Default: False.

    :return: Generate new figures with selected figure format. Default: 'pdf'.

    """
    rep = Replot(indir, outdir, weighted_score_type,
                 min_size, max_size, figsize, graph_num, format, verbose)
    rep.run()

    return

def replot ( indir , outdir = 'GSEA_Replot' , weighted_score_type = 1 , min_size = 3 , max_size = 1000 , figsize = ( 6.5 , 6 ) , graph_num = 20 , format = 'pdf' , verbose = False ) : rep = Replot ( indir , outdir , weighted_score_type , min_size , max_size , figsize , graph_num , format , verbose ) rep . run ( ) return

The main function to reproduce GSEA desktop outputs .


def _set_cores(self):
        """set cpu numbers to be used"""

        cpu_num = cpu_count()-1
        if self._processes > cpu_num:
            cores = cpu_num
        elif self._processes < 1:
            cores = 1
        else:
            cores = self._processes
        # have to be int if user input is float
        self._processes = int(cores)

def _set_cores ( self ) : cpu_num = cpu_count ( ) - 1 if self . _processes > cpu_num : cores = cpu_num elif self . _processes < 1 : cores = 1 else : cores = self . _processes self . _processes = int ( cores )


def load_gmt(self, gene_list, gmt):
        """load gene set dict"""

        if isinstance(gmt, dict):
            genesets_dict = gmt
        elif isinstance(gmt, str):
            genesets_dict = self.parse_gmt(gmt)
        else:
            raise Exception("Error parsing gmt parameter for gene sets")

        subsets = list(genesets_dict.keys())
        self.n_genesets = len(subsets)
        for subset in subsets:
            subset_list = genesets_dict.get(subset)
            if isinstance(subset_list, set):
                subset_list = list(subset_list)
                genesets_dict[subset] = subset_list
            tag_indicator = np.in1d(gene_list, subset_list, assume_unique=True)
            tag_len = tag_indicator.sum()
            if  self.min_size <= tag_len <= self.max_size: continue
            del genesets_dict[subset]

        filsets_num = len(subsets) - len(genesets_dict)
        self._logger.info("%04d gene_sets have been filtered out when max_size=%s and min_size=%s"%(filsets_num, self.max_size, self.min_size))

        if filsets_num == len(subsets):
            self._logger.error("No gene sets passed through filtering condition!!!, try new parameters again!\n" +\
                               "Note: check gene name, gmt file format, or filtering size." )
            sys.exit(0)

        self._gmtdct=genesets_dict
        return genesets_dict


def get_libraries(self, database=''):
        """return active enrichr library name.Offical API """

        lib_url='http://amp.pharm.mssm.edu/%sEnrichr/datasetStatistics'%database
        libs_json = json.loads(requests.get(lib_url).text)
        libs = [lib['libraryName'] for lib in libs_json['statistics']]
        return sorted(libs)

return active enrichr library name . Offical API


def _download_libraries(self, libname):
        """ download enrichr libraries."""
        self._logger.info("Downloading and generating Enrichr library gene sets......")
        s = retry(5)
        # queery string
        ENRICHR_URL = 'http://amp.pharm.mssm.edu/Enrichr/geneSetLibrary'
        query_string = '?mode=text&libraryName=%s'
        # get
        response = s.get( ENRICHR_URL + query_string % libname, timeout=None)
        if not response.ok:
            raise Exception('Error fetching enrichment results, check internet connection first.')
        # reformat to dict and save to disk
        mkdirs(DEFAULT_CACHE_PATH)
        genesets_dict = {}
        outname = "enrichr.%s.gmt"%libname
        gmtout = open(os.path.join(DEFAULT_CACHE_PATH, outname), "w")
        for line in response.iter_lines(chunk_size=1024, decode_unicode='utf-8'):
            line=line.strip()
            k = line.split("\t")[0]
            v = list(map(lambda x: x.split(",")[0], line.split("\t")[2:]))
            genesets_dict.update({ k: v})
            outline = "%s\t\t%s\n"%(k, "\t".join(v))
            gmtout.write(outline)
        gmtout.close()

        return genesets_dict


def _heatmat(self, df, classes, pheno_pos, pheno_neg):
        """only use for gsea heatmap"""
        width = len(classes) if len(classes) >= 6 else  5
        cls_booA =list(map(lambda x: True if x == pheno_pos else False, classes))
        cls_booB =list(map(lambda x: True if x == pheno_neg else False, classes))
        datA = df.loc[:, cls_booA]
        datB = df.loc[:, cls_booB]
        datAB=pd.concat([datA,datB], axis=1)
        self._width = width
        self.heatmat = datAB
        return

def _heatmat ( self , df , classes , pheno_pos , pheno_neg ) : width = len ( classes ) if len ( classes ) >= 6 else 5 cls_booA = list ( map ( lambda x : True if x == pheno_pos else False , classes ) ) cls_booB = list ( map ( lambda x : True if x == pheno_neg else False , classes ) ) datA = df . loc [ : , cls_booA ] datB = df . loc [ : , cls_booB ] datAB = pd . concat ( [ datA , datB ] , axis = 1 ) self . _width = width self . heatmat = datAB return


def _save_results(self, zipdata, outdir, module, gmt, rank_metric, permutation_type):
        """reformat gsea results, and save to txt"""

        res = OrderedDict()
        for gs, gseale, ind, RES in zipdata:
            rdict = OrderedDict()
            rdict['es'] = gseale[0]
            rdict['nes'] = gseale[1]
            rdict['pval'] = gseale[2]
            rdict['fdr'] = gseale[3]
            rdict['geneset_size'] = len(gmt[gs])
            rdict['matched_size'] = len(ind)
            #reformat gene list.
            _genes = rank_metric.index.values[ind]
            rdict['genes'] = ";".join([ str(g).strip() for g in _genes ])

            if self.module != 'ssgsea':
                # extract leading edge genes
                if rdict['es'] > 0:
                    # RES -> ndarray, ind -> list
                    idx = RES.argmax()
                    ldg_pos = list(filter(lambda x: x<= idx, ind))
                elif rdict['es'] < 0:
                    idx = RES.argmin()
                    ldg_pos = list(filter(lambda x: x >= idx, ind))
                else:
                    ldg_pos = ind # es == 0 ?
                rdict['ledge_genes'] = ';'.join(list(map(str,rank_metric.iloc[ldg_pos].index)))

            rdict['RES'] = RES
            rdict['hits_indices'] = ind
            # save to one odict
            res[gs] = rdict
        # save
        self.results  = res
        # save to dataframe
        res_df = pd.DataFrame.from_dict(res, orient='index')
        res_df.index.name = 'Term'
        res_df.drop(['RES','hits_indices'], axis=1, inplace=True)
        res_df.sort_values(by=['fdr','pval'], inplace=True)
        self.res2d = res_df

        if self._outdir is None: return
        out = os.path.join(outdir,'gseapy.{b}.{c}.report.csv'.format(b=module, c=permutation_type))
        if self.module == 'ssgsea':
            out = out.replace(".csv",".txt")
            with open(out, 'a') as f:
                f.write('# normalize enrichment scores by random permutation procedure (GSEA method)\n')
                f.write("# might not proper for publication\n")
                res_df.to_csv(f, sep='\t')
        else:
            res_df.to_csv(out)

        return


def load_data(self, cls_vec):
        """pre-processed the data frame.new filtering methods will be implement here.
        """
        # read data in
        if isinstance(self.data, pd.DataFrame) :
            exprs = self.data.copy()
            # handle index is gene_names
            if exprs.index.dtype == 'O':
                exprs = exprs.reset_index()
        elif os.path.isfile(self.data) :
            # GCT input format?
            if self.data.endswith("gct"):
                exprs = pd.read_csv(self.data, skiprows=1, comment='#',sep="\t")
            else:
                exprs = pd.read_csv(self.data, comment='#',sep="\t")
        else:
            raise Exception('Error parsing gene expression DataFrame!')

        #drop duplicated gene names
        if exprs.iloc[:,0].duplicated().sum() > 0:
            self._logger.warning("Warning: dropping duplicated gene names, only keep the first values")
            exprs.drop_duplicates(subset=exprs.columns[0], inplace=True) #drop duplicate gene_names.
        if exprs.isnull().any().sum() > 0:
            self._logger.warning("Warning: Input data contains NA, filled NA with 0")
            exprs.dropna(how='all', inplace=True) #drop rows with all NAs
            exprs = exprs.fillna(0)
        # set gene name as index
        exprs.set_index(keys=exprs.columns[0], inplace=True)
        # select numberic columns
        df = exprs.select_dtypes(include=[np.number])
        # drop any genes which std ==0
        df_std =  df.groupby(by=cls_vec, axis=1).std()
        df =  df[~df_std.isin([0]).any(axis=1)]
        df = df + 0.00001 # we don't like zeros!!!
        return df

pre - processed the data frame . new filtering methods will be implement here .


def run(self):
        """GSEA main procedure"""

        assert self.permutation_type in ["phenotype", "gene_set"]
        assert self.min_size <= self.max_size

        # Start Analysis
        self._logger.info("Parsing data files for GSEA.............................")
        # phenotype labels parsing
        phenoPos, phenoNeg, cls_vector = gsea_cls_parser(self.classes)
        # select correct expression genes and values.
        dat = self.load_data(cls_vector)
        # data frame must have length > 1
        assert len(dat) > 1
        # ranking metrics calculation.
        dat2 = ranking_metric(df=dat, method=self.method, pos=phenoPos, neg=phenoNeg,
                              classes=cls_vector, ascending=self.ascending)
        self.ranking = dat2
        # filtering out gene sets and build gene sets dictionary
        gmt = self.load_gmt(gene_list=dat2.index.values, gmt=self.gene_sets)

        self._logger.info("%04d gene_sets used for further statistical testing....."% len(gmt))
        self._logger.info("Start to run GSEA...Might take a while..................")
        # cpu numbers
        self._set_cores()
        # compute ES, NES, pval, FDR, RES
        dataset = dat if self.permutation_type =='phenotype' else dat2
        gsea_results,hit_ind,rank_ES, subsets = gsea_compute_tensor(data=dataset, gmt=gmt, n=self.permutation_num,
                                                             weighted_score_type=self.weighted_score_type,
                                                             permutation_type=self.permutation_type,
                                                             method=self.method,
                                                             pheno_pos=phenoPos, pheno_neg=phenoNeg,
                                                             classes=cls_vector, ascending=self.ascending,
                                                             processes=self._processes, seed=self.seed)

        self._logger.info("Start to generate GSEApy reports and figures............")
        res_zip = zip(subsets, list(gsea_results), hit_ind, rank_ES)
        self._save_results(zipdata=res_zip, outdir=self.outdir, module=self.module,
                                   gmt=gmt, rank_metric=dat2, permutation_type=self.permutation_type)

        # reorder datarame for heatmap
        self._heatmat(df=dat.loc[dat2.index], classes=cls_vector,
                      pheno_pos=phenoPos, pheno_neg=phenoNeg)
        # Plotting
        if not self._noplot:
            self._plotting(rank_metric=dat2, results=self.results,
                           graph_num=self.graph_num, outdir=self.outdir,
                           figsize=self.figsize, format=self.format,
                           pheno_pos=phenoPos, pheno_neg=phenoNeg)

        self._logger.info("Congratulations. GSEApy ran successfully.................\n")
        if self._outdir is None:
            self._tmpdir.cleanup()

        return


def run(self):
        """GSEA prerank workflow"""

        assert self.min_size <= self.max_size

        # parsing rankings
        dat2 = self._load_ranking(self.rnk)
        assert len(dat2) > 1

        # cpu numbers
        self._set_cores()
        # Start Analysis
        self._logger.info("Parsing data files for GSEA.............................")
        # filtering out gene sets and build gene sets dictionary
        gmt = self.load_gmt(gene_list=dat2.index.values, gmt=self.gene_sets)

        self._logger.info("%04d gene_sets used for further statistical testing....."% len(gmt))
        self._logger.info("Start to run GSEA...Might take a while..................")
        # compute ES, NES, pval, FDR, RES
        gsea_results, hit_ind,rank_ES, subsets = gsea_compute(data=dat2, n=self.permutation_num, gmt=gmt,
                                                              weighted_score_type=self.weighted_score_type,
                                                              permutation_type='gene_set', method=None,
                                                              pheno_pos=self.pheno_pos, pheno_neg=self.pheno_neg,
                                                              classes=None, ascending=self.ascending,
                                                              processes=self._processes, seed=self.seed)
        self._logger.info("Start to generate gseapy reports, and produce figures...")
        res_zip = zip(subsets, list(gsea_results), hit_ind, rank_ES)
        self._save_results(zipdata=res_zip, outdir=self.outdir, module=self.module,
                                   gmt=gmt, rank_metric=dat2, permutation_type="gene_sets")

        # Plotting
        if not self._noplot:
            self._plotting(rank_metric=dat2, results=self.results,
                           graph_num=self.graph_num, outdir=self.outdir,
                           figsize=self.figsize, format=self.format,
                           pheno_pos=self.pheno_pos, pheno_neg=self.pheno_neg)

        self._logger.info("Congratulations. GSEApy runs successfully................\n")
        if self._outdir is None:
            self._tmpdir.cleanup()

        return


def runSamplesPermu(self, df, gmt=None):
        """Single Sample GSEA workflow with permutation procedure"""

        assert self.min_size <= self.max_size
        mkdirs(self.outdir)
        self.resultsOnSamples = OrderedDict()
        outdir = self.outdir
        # iter throught each sample
        for name, ser in df.iteritems():
            self.outdir = os.path.join(outdir, str(name))
            self._logger.info("Run Sample: %s " % name)
            mkdirs(self.outdir)
            # sort ranking values from high to low or reverse
            dat2 = ser.sort_values(ascending=self.ascending)
            # reset integer index, or caused unwanted problems
            # df.reset_index(drop=True, inplace=True)

            # compute ES, NES, pval, FDR, RES
            gsea_results, hit_ind,rank_ES, subsets = gsea_compute(data=dat2, n=self.permutation_num, gmt=gmt,
                                                                  weighted_score_type=self.weighted_score_type,
                                                                  permutation_type='gene_set', method=None,
                                                                  pheno_pos='', pheno_neg='',
                                                                  classes=None, ascending=self.ascending,
                                                                  processes=self._processes,
                                                                  seed=self.seed, single=True, scale=self.scale)

            # write file
            res_zip = zip(subsets, list(gsea_results), hit_ind, rank_ES)
            self._save_results(zipdata=res_zip, outdir=self.outdir, module=self.module,
                                       gmt=gmt, rank_metric=dat2, permutation_type="gene_sets")
            self.resultsOnSamples[name] = self.res2d.es
            # plotting
            if self._noplot: continue
            self._logger.info("Plotting Sample: %s \n" % name)
            self._plotting(rank_metric=dat2, results=self.results,
                           graph_num=self.graph_num, outdir=self.outdir,
                           figsize=self.figsize, format=self.format)

        # save es, nes to file
        self._save(outdir)

        return

def
runSamplesPermu
(
self
,
df
,
gmt
=
)
:
assert
self
.
min_size
<=
self
.
max_size
mkdirs
(
self
.
outdir
)
self
.
resultsOnSamples
=
OrderedDict
(
)
outdir
=
self
.
outdir
for
name
,
ser
in
df
.
iteritems
(
)
:
self
.
outdir
=
os
.
path
.
join
(
outdir
,
str
(
name
)
)
self
.
_logger
.
info
(
"Run Sample: %s "
%
name
)
mkdirs
(
self
.
outdir
)
dat2
=
ser
.
sort_values
(
ascending
=
self
.
ascending
)
gsea_results
,
hit_ind
,
rank_ES
,
subsets
=
gsea_compute
(
data
=
dat2
,
n
=
self
.
permutation_num
,
gmt
=
gmt
,
weighted_score_type
=
self
.
weighted_score_type
,
permutation_type
=
'gene_set'
,
method
=
,
pheno_pos
=
''
,
pheno_neg
=
''
,
classes
=
,
ascending
=
self
.
ascending
,
processes
=
self
.
_processes
,
seed
=
self
.
seed
,
single
=
True
,
scale
=
self
.
scale
)
res_zip
=
zip
(
subsets
,
list
(
gsea_results
)
,
hit_ind
,
rank_ES
)
self
.
_save_results
(
zipdata
=
res_zip
,
outdir
=
self
.
outdir
,
module
=
self
.
module
,
gmt
=
gmt
,
rank_metric
=
dat2
,
permutation_type
=
"gene_sets"
)
self
.
resultsOnSamples
[
name
]
=
self
.
res2d
.
es
if
self
.
_noplot
:
continue
self
.
_logger
.
info
(
"Plotting Sample: %s \n"
%
name
)
self
.
_plotting
(
rank_metric
=
dat2
,
results
=
self
.
results
,
graph_num
=
self
.
graph_num
,
outdir
=
self
.
outdir
,
figsize
=
self
.
figsize
,
format
=
self
.
format
)
self
.
_save
(
outdir
)
return


def runSamples(self, df, gmt=None):
        """Single Sample GSEA workflow.
           multiprocessing utility on samples.
        """

        # df.index.values are gene_names
        # Save each sample results to odict
        self.resultsOnSamples = OrderedDict()
        outdir = self.outdir
        # run ssgsea for gct expression matrix
        #multi-threading
        subsets = sorted(gmt.keys())
        tempes=[]
        names=[]
        rankings=[]
        pool = Pool(processes=self._processes)
        for name, ser in df.iteritems():
            #prepare input
            dat = ser.sort_values(ascending=self.ascending)
            rankings.append(dat)
            names.append(name)
            genes_sorted, cor_vec = dat.index.values, dat.values
            rs = np.random.RandomState(self.seed)
            # apply_async
            tempes.append(pool.apply_async(enrichment_score_tensor,
                                           args=(genes_sorted, cor_vec, gmt,
                                               self.weighted_score_type,
                                               self.permutation_num, rs, True,
                                               self.scale)))
        pool.close()
        pool.join()
        # save results and plotting
        for i, temp in enumerate(tempes):
            name, rnk = names[i], rankings[i]
            self._logger.info("Calculate Enrichment Score for Sample: %s "%name)
            es, esnull, hit_ind, RES = temp.get()
            # create results subdir
            self.outdir= os.path.join(outdir, str(name))
            mkdirs(self.outdir)
            # save results
            self.resultsOnSamples[name] = pd.Series(data=es, index=subsets, name=name)
            # plotting
            if self._noplot: continue
            self._logger.info("Plotting Sample: %s \n" % name)
            for i, term in enumerate(subsets):
                term = term.replace('/','_').replace(":","_")
                outfile = '{0}/{1}.{2}.{3}'.format(self.outdir, term, self.module, self.format)
                gseaplot(rank_metric=rnk, term=term,
                         hits_indices=hit_ind[i], nes=es[i], pval=1, fdr=1,
                         RES=RES[i], pheno_pos='', pheno_neg='',
                         figsize=self.figsize, ofname=outfile)
        # save es, nes to file
        self._save(outdir)

        return

Single Sample GSEA workflow . multiprocessing utility on samples .


def _save(self, outdir):
        """save es and stats"""
        # save raw ES to one csv file
        samplesRawES = pd.DataFrame(self.resultsOnSamples)
        samplesRawES.index.name = 'Term|ES'
        # normalize enrichment scores by using the entire data set, as indicated
        # by Barbie et al., 2009, online methods, pg. 2
        samplesNES = samplesRawES / (samplesRawES.values.max() - samplesRawES.values.min())
        samplesNES = samplesNES.copy()
        samplesNES.index.rename('Term|NES', inplace=True)
        self.res2d = samplesNES
        self._logger.info("Congratulations. GSEApy runs successfully................\n")
        if self._outdir is None: return
        # write es
        outESfile = os.path.join(outdir, "gseapy.samples.raw.es.txt")
        with open(outESfile, 'a') as f:
            if self.scale:
                f.write('# scale the enrichment scores by number of genes in the gene sets\n')
                f.write('# this normalization has not effects on the final NES, ' + \
                        'as indicated by Barbie et al., 2009, online methods, pg. 2\n')
            else:
                f.write('# raw enrichment scores of all data\n')
                f.write('# no scale es by numbers of genes in the gene sets\n')
            samplesRawES.to_csv(f, sep='\t')

        outNESfile = os.path.join(outdir, "gseapy.samples.normalized.es.txt")
        with open(outNESfile, 'a') as f:
            f.write('# normalize enrichment scores by using the entire data set\n')
            f.write('# as indicated by Barbie et al., 2009, online methods, pg. 2\n')
            samplesNES.to_csv(f, sep='\t')
        return


def run(self):
        """main replot function"""
        assert self.min_size <= self.max_size
        assert self.fignum > 0
        import glob
        from bs4 import BeautifulSoup

        # parsing files.......
        try:
            results_path = glob.glob(self.indir+'*/edb/results.edb')[0]
            rank_path =  glob.glob(self.indir+'*/edb/*.rnk')[0]
            gene_set_path =  glob.glob(self.indir+'*/edb/gene_sets.gmt')[0]
        except IndexError as e:
            sys.stderr.write("Could not locate GSEA files in the given directory!")
            sys.exit(1)
        # extract sample names from .cls file
        cls_path = glob.glob(self.indir+'*/edb/*.cls')
        if cls_path:
            pos, neg, classes = gsea_cls_parser(cls_path[0])
        else:
            # logic for prerank results
            pos, neg = '',''
        # start reploting
        self.gene_sets=gene_set_path
        # obtain gene sets
        gene_set_dict = self.parse_gmt(gmt=gene_set_path)
        # obtain rank_metrics
        rank_metric = self._load_ranking(rank_path)
        correl_vector =  rank_metric.values
        gene_list = rank_metric.index.values
        # extract each enriment term in the results.edb files and plot.
        database = BeautifulSoup(open(results_path), features='xml')
        length = len(database.findAll('DTG'))
        fig_num = self.fignum if self.fignum <= length else length
        for idx in range(fig_num):
            # extract statistical resutls from results.edb file
            enrich_term, hit_ind, nes, pval, fdr= gsea_edb_parser(results_path, index=idx)
            gene_set = gene_set_dict.get(enrich_term)
            # calculate enrichment score
            RES = enrichment_score(gene_list=gene_list,
                                   correl_vector=correl_vector,
                                   gene_set=gene_set,
                                   weighted_score_type=self.weighted_score_type,
                                   nperm=0)[-1]
            # plotting
            term = enrich_term.replace('/','_').replace(":","_")
            outfile = '{0}/{1}.{2}.{3}'.format(self.outdir, term, self.module, self.format)
            gseaplot(rank_metric=rank_metric, term=enrich_term,
                         hits_indices=hit_ind, nes=nes, pval=pval, fdr=fdr,
                         RES=RES, pheno_pos=pos, pheno_neg=neg,
                         figsize=self.figsize, ofname=outfile)

        self._logger.info("Congratulations! Your plots have been reproduced successfully!\n")


def enrichr(gene_list, gene_sets, organism='human', description='',
            outdir='Enrichr', background='hsapiens_gene_ensembl', cutoff=0.05,
            format='pdf', figsize=(8,6), top_term=10, no_plot=False, verbose=False):
    """Enrichr API.

    :param gene_list: Flat file with list of genes, one gene id per row, or a python list object
    :param gene_sets: Enrichr Library to query. Required enrichr library name(s). Separate each name by comma.
    :param organism: Enrichr supported organism. Select from (human, mouse, yeast, fly, fish, worm).
                     see here for details: https://amp.pharm.mssm.edu/modEnrichr
    :param description: name of analysis. optional.
    :param outdir: Output file directory
    :param float cutoff: Adjusted P-value (benjamini-hochberg correction) cutoff. Default: 0.05
    :param int background: BioMart dataset name for retrieving background gene information.
                           This argument only works when gene_sets input is a gmt file or python dict.
                           You could also specify a number by yourself, e.g. total expressed genes number.
                           In this case, you will skip retrieving background infos from biomart.

    Use the code below to see valid background dataset names from BioMart.
    Here are example code:
    >>> from gseapy.parser import Biomart
    >>> bm = Biomart(verbose=False, host="asia.ensembl.org")
    >>> ## view validated marts
    >>> marts = bm.get_marts()
    >>> ## view validated dataset
    >>> datasets = bm.get_datasets(mart='ENSEMBL_MART_ENSEMBL')

    :param str format: Output figure format supported by matplotlib,('pdf','png','eps'...). Default: 'pdf'.
    :param list figsize: Matplotlib figsize, accept a tuple or list, e.g. (width,height). Default: (6.5,6).
    :param bool no_plot: If equals to True, no figure will be drawn. Default: False.
    :param bool verbose: Increase output verbosity, print out progress of your job, Default: False.

    :return: An Enrichr object, which obj.res2d stores your last query, obj.results stores your all queries.

    """
    enr = Enrichr(gene_list, gene_sets, organism, description, outdir,
                  cutoff, background, format, figsize, top_term, no_plot, verbose)
    enr.run()

    return enr

def enrichr ( gene_list , gene_sets , organism = 'human' , description = '' , outdir = 'Enrichr' , background = 'hsapiens_gene_ensembl' , cutoff = 0.05 , format = 'pdf' , figsize = ( 8 , 6 ) , top_term = 10 , no_plot = False , verbose = False ) : enr = Enrichr ( gene_list , gene_sets , organism , description , outdir , cutoff , background , format , figsize , top_term , no_plot , verbose ) enr . run ( ) return enr


def parse_genesets(self):
        """parse gene_sets input file type"""

        enrichr_library = self.get_libraries()
        if isinstance(self.gene_sets, list):
            gss = self.gene_sets
        elif isinstance(self.gene_sets, str):
            gss = [ g.strip() for g in self.gene_sets.strip().split(",") ]
        elif isinstance(self.gene_sets, dict):
            gss = [self.gene_sets]
        else:
            raise Exception("Error parsing enrichr libraries, please provided corrected one")

        # gss: a list contain .gmt, dict, enrichr_liraries.
        # now, convert .gmt to dict
        gss_exist = []
        for g in gss:
            if isinstance(g, dict):
                gss_exist.append(g)
                continue

            if isinstance(g, str):
                if g in enrichr_library:
                    gss_exist.append(g)
                    continue
                if g.lower().endswith(".gmt") and os.path.exists(g):
                    self._logger.info("User Defined gene sets is given: %s"%g)
                    with open(g) as genesets:
                        g_dict = { line.strip().split("\t")[0]: line.strip().split("\t")[2:]
                                        for line in genesets.readlines() }
                    gss_exist.append(g_dict)
        return gss_exist


def parse_genelists(self):
        """parse gene list"""
        if isinstance(self.gene_list, list):
            genes = self.gene_list
        elif isinstance(self.gene_list, pd.DataFrame):
            # input type is bed file
            if self.gene_list.shape[1] >=3:
                genes= self.gene_list.iloc[:,:3].apply(lambda x: "\t".join([str(i) for i in x]), axis=1).tolist()
            # input type with weight values
            elif self.gene_list.shape[1] == 2:
               genes= self.gene_list.apply(lambda x: ",".join([str(i) for i in x]), axis=1).tolist()
            else:
               genes = self.gene_list.squeeze().tolist()
        elif isinstance(self.gene_list, pd.Series):
            genes = self.gene_list.squeeze().tolist()
        else:
            # get gene lists or bed file, or gene list with weighted values.
            genes=[]
            with open(self.gene_list) as f:
                for gene in f:
                    genes.append(gene.strip())

        self._isezid = all(map(self._is_entrez_id, genes))
        if self._isezid:
            self._gls = set(map(int, self._gls))
        else:
            self._gls = genes

        return '\n'.join(genes)

def parse_genelists ( self ) : if isinstance ( self . gene_list , list ) : genes = self . gene_list elif isinstance ( self . gene_list , pd . DataFrame ) : if self . gene_list . shape [ 1 ] >= 3 : genes = self . gene_list . iloc [ : , : 3 ] . apply ( lambda x : "\t" . join ( [ str ( i ) for i in x ] ) , axis = 1 ) . tolist ( ) elif self . gene_list . shape [ 1 ] == 2 : genes = self . gene_list . apply ( lambda x : "," . join ( [ str ( i ) for i in x ] ) , axis = 1 ) . tolist ( ) else : genes = self . gene_list . squeeze ( ) . tolist ( ) elif isinstance ( self . gene_list , pd . Series ) : genes = self . gene_list . squeeze ( ) . tolist ( ) else : genes = [ ] with open ( self . gene_list ) as f : for gene in f : genes . append ( gene . strip ( ) ) self . _isezid = all ( map ( self . _is_entrez_id , genes ) ) if self . _isezid : self . _gls = set ( map ( int , self . _gls ) ) else : self . _gls = genes return '\n' . join ( genes )


def send_genes(self, gene_list, url):
        """ send gene list to enrichr server"""
        payload = {
          'list': (None, gene_list),
          'description': (None, self.descriptions)
           }
        # response
        response = requests.post(url, files=payload)
        if not response.ok:
            raise Exception('Error analyzing gene list')
        sleep(1)
        job_id = json.loads(response.text)

        return job_id

def
send_genes
(
self
,
gene_list
,
url
)
:
payload
=
{
'list'
:
(
,
gene_list
)
,
'description'
:
(
,
self
.
descriptions
)
}
response
=
requests
.
post
(
url
,
files
=
payload
)
if
not
response
.
ok
:
raise
Exception
(
'Error analyzing gene list'
)
sleep
(
1
)
job_id
=
json
.
loads
(
response
.
text
)
return
job_id


def check_genes(self, gene_list, usr_list_id):
        '''
        Compare the genes sent and received to get successfully recognized genes
        '''
        response = requests.get('http://amp.pharm.mssm.edu/Enrichr/view?userListId=%s' % usr_list_id)
        if not response.ok:
            raise Exception('Error getting gene list back')
        returnedL = json.loads(response.text)["genes"]
        returnedN = sum([1 for gene in gene_list if gene in returnedL])
        self._logger.info('{} genes successfully recognized by Enrichr'.format(returnedN))


def get_background(self):
        """get background gene"""

        # input is a file
        if os.path.isfile(self.background):
            with open(self.background) as b:
                bg2 = b.readlines()
            bg = [g.strip() for g in bg2]
            return set(bg)

        # package included data
        DB_FILE = resource_filename("gseapy", "data/{}.background.genes.txt".format(self.background))
        filename = os.path.join(DEFAULT_CACHE_PATH, "{}.background.genes.txt".format(self.background))
        if os.path.exists(filename):
            df = pd.read_csv(filename,sep="\t")
        elif os.path.exists(DB_FILE):
            df = pd.read_csv(DB_FILE,sep="\t")
        else:
            # background is a biomart database name
            self._logger.warning("Downloading %s for the first time. It might take a couple of miniutes."%self.background)
            bm = Biomart()
            df = bm.query(dataset=self.background)
            df.dropna(subset=['go_id'], inplace=True)
        self._logger.info("using all annotated genes with GO_ID as background genes")
        df.dropna(subset=['entrezgene'], inplace=True)
        # input id type: entrez or gene_name
        if self._isezid:
            bg = df['entrezgene'].astype(int)
        else:
            bg = df['external_gene_name']

        return set(bg)


def run(self):
        """run enrichr for one sample gene list but multi-libraries"""

        # set organism
        self.get_organism()
        # read input file
        genes_list = self.parse_genelists()
        gss = self.parse_genesets()
        # if gmt
        self._logger.info("Connecting to Enrichr Server to get latest library names")
        if len(gss) < 1:
            sys.stderr.write("Not validated Enrichr library name provided\n")
            sys.stdout.write("Hint: use get_library_name() to view full list of supported names")
            sys.exit(1)
        self.results = pd.DataFrame()

        for g in gss:
            if isinstance(g, dict):
                ## local mode
                res = self.enrich(g)
                shortID, self._gs = str(id(g)), "CUSTOM%s"%id(g)
                if res is None:
                    self._logger.info("No hits return, for gene set: Custom%s"%shortID)
                    continue
            else:
                ## online mode
                self._gs = str(g)
                self._logger.debug("Start Enrichr using library: %s" % (self._gs))
                self._logger.info('Analysis name: %s, Enrichr Library: %s' % (self.descriptions, self._gs))
                shortID, res = self.get_results(genes_list)
                # Remember gene set library used
            res.insert(0, "Gene_set", self._gs)
            # Append to master dataframe
            self.results = self.results.append(res, ignore_index=True, sort=True)
            self.res2d = res
            if self._outdir is None: continue
            self._logger.info('Save file of enrichment results: Job Id:' + str(shortID))
            outfile = "%s/%s.%s.%s.reports.txt" % (self.outdir, self._gs, self.descriptions, self.module)
            self.res2d.to_csv(outfile, index=False, encoding='utf-8', sep="\t")
            # plotting
            if not self.__no_plot:
                msg = barplot(df=res, cutoff=self.cutoff, figsize=self.figsize,
                              top_term=self.__top_term, color='salmon',
                              title=self._gs,
                              ofname=outfile.replace("txt", self.format))
                if msg is not None : self._logger.warning(msg)
            self._logger.info('Done.\n')
        # clean up tmpdir
        if self._outdir is None: self._tmpdir.cleanup()

        return

run enrichr for one sample gene list but multi - libraries


def cube(script, size=1.0, center=False, color=None):
    """Create a cube primitive

    Note that this is made of 6 quads, not triangles
    """

    """# Convert size to list if it isn't already
    if not isinstance(size, list):
        size = list(size)
    # If a single value was supplied use it for all 3 axes
    if len(size) == 1:
        size = [size[0], size[0], size[0]]"""
    size = util.make_list(size, 3)
    if script.ml_version == '1.3.4BETA':
        filter_name = 'Box'
    else:
        filter_name = 'Box/Cube'
    filter_xml = ''.join([
        '  <filter name="{}">\n'.format(filter_name),
        '    <Param name="size" ',
        'value="1.0" ',
        'description="Scale factor" ',
        'type="RichFloat" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    if isinstance(script, FilterScript):
        script.add_layer('Cube', change_layer=True)
    transform.scale(script, value=size)
    # Box is centered on origin at creation
    if not center:
        transform.translate(script, value=[size[0]/2, size[1]/2, size[2]/2])
    if color is not None:
        vert_color.function(script, color=color)
    return None

def
cube
(
script
,
size
=
1.0
,
center
=
False
,
color
=
)
:
size
=
util
.
make_list
(
size
,
3
)
if
script
.
ml_version
==
'1.3.4BETA'
:
filter_name
=
'Box'
else
:
filter_name
=
'Box/Cube'
filter_xml
=
''
.
join
(
[
'  <filter name="{}">\n'
.
format
(
filter_name
)
,
'    <Param name="size" '
,
'value="1.0" '
,
'description="Scale factor" '
,
'type="RichFloat" '
,
'/>\n'
,
'  </filter>\n'
]
)
util
.
write_filter
(
script
,
filter_xml
)
if
isinstance
(
script
,
FilterScript
)
:
script
.
add_layer
(
'Cube'
,
change_layer
=
True
)
transform
.
scale
(
script
,
value
=
size
)
if
not
center
:
transform
.
translate
(
script
,
value
=
[
size
[
0
]
/
2
,
size
[
1
]
/
2
,
size
[
2
]
/
2
]
)
if
color
is
not
:
vert_color
.
function
(
script
,
color
=
color
)
return


def icosphere(script, radius=1.0, diameter=None, subdivisions=3, color=None):
    """create an icosphere mesh

    radius Radius of the sphere
    # subdivisions = Subdivision level; Number of the recursive subdivision of the
    # surface. Default is 3 (a sphere approximation composed by 1280 faces).
    # Admitted values are in the range 0 (an icosahedron) to 8 (a 1.3 MegaTris
    # approximation of a sphere). Formula for number of faces: F=20*4^subdiv
    # color = specify a color name to apply vertex colors to the newly
    # created mesh"""
    if diameter is not None:
        radius = diameter / 2
    filter_xml = ''.join([
        '  <filter name="Sphere">\n',
        '    <Param name="radius" ',
        'value="%s" ' % radius,
        'description="Radius" ',
        'type="RichFloat" ',
        '/>\n',
        '    <Param name="subdiv" ',
        'value="%d" ' % subdivisions,
        'description="Subdiv. Level" ',
        'type="RichInt" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    if isinstance(script, FilterScript):
        script.add_layer('Sphere', change_layer=True)
    if color is not None:
        vert_color.function(script, color=color)
    return None

def
icosphere
(
script
,
radius
=
1.0
,
diameter
=
,
subdivisions
=
3
,
color
=
)
:
if
diameter
is
not
:
radius
=
diameter
/
2
filter_xml
=
''
.
join
(
[
'  <filter name="Sphere">\n'
,
'    <Param name="radius" '
,
'value="%s" '
%
radius
,
'description="Radius" '
,
'type="RichFloat" '
,
'/>\n'
,
'    <Param name="subdiv" '
,
'value="%d" '
%
subdivisions
,
'description="Subdiv. Level" '
,
'type="RichInt" '
,
'/>\n'
,
'  </filter>\n'
]
)
util
.
write_filter
(
script
,
filter_xml
)
if
isinstance
(
script
,
FilterScript
)
:
script
.
add_layer
(
'Sphere'
,
change_layer
=
True
)
if
color
is
not
:
vert_color
.
function
(
script
,
color
=
color
)
return


def torus(script, major_radius=3.0, minor_radius=1.0, inner_diameter=None,
          outer_diameter=None, major_segments=48, minor_segments=12,
          color=None):
    """Create a torus mesh

    Args:
        major_radius (float, (optional)): radius from the origin to the
            center of the cross sections
        minor_radius (float, (optional)): radius of the torus cross
            section
        inner_diameter (float, (optional)): inner diameter of torus. If
            both inner_diameter and outer_diameter are provided then
            these will override major_radius and minor_radius.,
        outer_diameter (float, (optional)): outer diameter of torus. If
            both inner_diameter and outer_diameter are provided then
            these will override major_radius and minor_radius.
        major_segments (int (optional)): number of segments for the main
            ring of the torus
        minor_segments (int (optional)): number of segments for the minor
            ring of the torus
        color (str (optional)): color name to apply vertex colors to the
            newly created mesh

    Returns:
        None

    """
    if inner_diameter is not None and outer_diameter is not None:
        major_radius = (inner_diameter + outer_diameter) / 4
        minor_radius = major_radius - inner_diameter / 2
        # Ref: inner_diameter = 2 * (major_radius - minor_radius)
        # Ref: outer_diameter = 2 * (major_radius + minor_radius)
    filter_xml = ''.join([
        '  <filter name="Torus">\n',
        '    <Param name="hRadius" ',
        'value="%s" ' % major_radius,
        'description="Horizontal Radius" ',
        'type="RichFloat" ',
        '/>\n',
        '    <Param name="vRadius" ',
        'value="%s" ' % minor_radius,
        'description="Vertical Radius" ',
        'type="RichFloat" ',
        '/>\n',
        '    <Param name="hSubdiv" ',
        'value="%d" ' % major_segments,
        'description="Horizontal Subdivision" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="vSubdiv" ',
        'value="%d" ' % minor_segments,
        'description="Vertical Subdivision" ',
        'type="RichInt" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    if isinstance(script, FilterScript):
        script.add_layer('Torus', change_layer=True)
    if color is not None:
        vert_color.function(script, color=color)
    return None

def
torus
(
script
,
major_radius
=
3.0
,
minor_radius
=
1.0
,
inner_diameter
=
,
outer_diameter
=
,
major_segments
=
48
,
minor_segments
=
12
,
color
=
)
:
if
inner_diameter
is
not
and
outer_diameter
is
not
:
major_radius
=
(
inner_diameter
+
outer_diameter
)
/
4
minor_radius
=
major_radius
inner_diameter
/
2
filter_xml
=
''
.
join
(
[
'  <filter name="Torus">\n'
,
'    <Param name="hRadius" '
,
'value="%s" '
%
major_radius
,
'description="Horizontal Radius" '
,
'type="RichFloat" '
,
'/>\n'
,
'    <Param name="vRadius" '
,
'value="%s" '
%
minor_radius
,
'description="Vertical Radius" '
,
'type="RichFloat" '
,
'/>\n'
,
'    <Param name="hSubdiv" '
,
'value="%d" '
%
major_segments
,
'description="Horizontal Subdivision" '
,
'type="RichInt" '
,
'/>\n'
,
'    <Param name="vSubdiv" '
,
'value="%d" '
%
minor_segments
,
'description="Vertical Subdivision" '
,
'type="RichInt" '
,
'/>\n'
,
'  </filter>\n'
]
)
util
.
write_filter
(
script
,
filter_xml
)
if
isinstance
(
script
,
FilterScript
)
:
script
.
add_layer
(
'Torus'
,
change_layer
=
True
)
if
color
is
not
:
vert_color
.
function
(
script
,
color
=
color
)
return


def plane_hires_edges(script, size=1.0, x_segments=1, y_segments=1,
                      center=False, color=None):
    """ Creates a plane with a specified number of vertices
    on it sides, but no vertices on the interior.

    Currently used to create a simpler bottom for cube_hires.

    """
    size = util.make_list(size, 2)

    grid(script, size=[x_segments + y_segments - 1, 1],
         x_segments=(x_segments + y_segments - 1), y_segments=1)
    if ml_script1.ml_version == '1.3.4BETA':
        and_val = 'and'
    else:
        and_val = '&&'

    if script.ml_version == '1.3.4BETA': # muparser version: 1.3.2
        # Deform left side
        transform.vert_function(
            script,
            x_func='if((y>0) and (x<%s),0,x)' % (y_segments),
            y_func='if((y>0) and (x<%s),(x+1)*%s,y)' % (
                y_segments, size[1] / y_segments))
        # Deform top
        transform.vert_function(
            script,
            x_func='if((y>0) and (x>=%s),(x-%s+1)*%s,x)' % (
                y_segments, y_segments, size[0] / x_segments),
            y_func='if((y>0) and (x>=%s),%s,y)' % (y_segments, size[1]))
        # Deform right side
        transform.vert_function(
            script,
            x_func='if((y<.00001) and (x>%s),%s,x)' % (
                x_segments, size[0]),
            y_func='if((y<.00001) and (x>%s),(x-%s)*%s,y)' % (
                x_segments, x_segments, size[1] / y_segments))
        # Deform bottom
        transform.vert_function(
            script,
            x_func='if((y<.00001) and (x<=%s) and (x>0),(x)*%s,x)' % (
                x_segments, size[0] / x_segments),
            y_func='if((y<.00001) and (x<=%s) and (x>0),0,y)' % (x_segments))
    else: # muparser version: 2.2.5
        # Deform left side
        transform.vert_function(
            script,
            x_func='((y>0) && (x<{yseg}) ? 0 : x)'.format(yseg=y_segments),
            y_func='((y>0) && (x<%s) ? (x+1)*%s : y)' % (
                y_segments, size[1] / y_segments))
        # Deform top
        transform.vert_function(
            script,
            x_func='((y>0) && (x>=%s) ? (x-%s+1)*%s : x)' % (
                y_segments, y_segments, size[0] / x_segments),
            y_func='((y>0) && (x>=%s) ? %s : y)' % (y_segments, size[1]))
        # Deform right side
        transform.vert_function(
            script,
            x_func='((y<.00001) && (x>%s) ? %s : x)' % (
                x_segments, size[0]),
            y_func='((y<.00001) && (x>%s) ? (x-%s)*%s : y)' % (
                x_segments, x_segments, size[1] / y_segments))
        # Deform bottom
        transform.vert_function(
            script,
            x_func='((y<.00001) && (x<=%s) && (x>0) ? (x)*%s : x)' % (
                x_segments, size[0] / x_segments),
            y_func='((y<.00001) && (x<=%s) && (x>0) ? 0 : y)' % (x_segments))
    if center:
        transform.translate(script, [-size[0] / 2, -size[1] / 2])
    if color is not None:
        vert_color.function(script, color=color)
    return None

Creates a plane with a specified number of vertices on it sides but no vertices on the interior .


def cube_hires(script, size=1.0, x_segments=1, y_segments=1, z_segments=1,
               simple_bottom=True, center=False, color=None):
    """Create a box with user defined number of segments in each direction.

    Grid spacing is the same as its dimensions (spacing = 1) and its
    thickness is one. Intended to be used for e.g. deforming using functions
    or a height map (lithopanes) and can be resized after creation.

    Warnings: function uses layers.join

    top_option
        0 open
        1 full
        2 simple
    bottom_option
        0 open
        1 full
        2 simple
    """
    """# Convert size to list if it isn't already
    if not isinstance(size, list):
        size = list(size)
    # If a single value was supplied use it for all 3 axes
    if len(size) == 1:
        size = [size[0], size[0], size[0]]"""
    size = util.make_list(size, 3)

    # Top
    grid(script,
         size,
         x_segments,
         y_segments)
    transform.translate(script, [0, 0, size[2]])

    # Bottom
    if simple_bottom:
        plane_hires_edges(
            script, size, x_segments, y_segments)
    else:
        layers.duplicate(script)
        transform.translate(script, [0, 0, -size[2]])
    # Rotate to correct normals
    transform.rotate(script, 'x', 180)
    transform.translate(script, [0, size[1], 0])

    # Sides
    cube_open_hires(
        script=script, size=size, x_segments=x_segments,
        y_segments=y_segments, z_segments=z_segments)

    # Join everything together
    layers.join(script)
    # Need some tolerance on merge_vert due to rounding errors
    clean.merge_vert(script, threshold=0.00002)
    if center:
        transform.translate(script, [-size[0] / 2, -size[1] / 2, -size[2] / 2])
    if color is not None:
        vert_color.function(script, color=color)
    return None

def cube_hires ( script , size = 1.0 , x_segments = 1 , y_segments = 1 , z_segments = 1 , simple_bottom = True , center = False , color = None ) : size = util . make_list ( size , 3 ) grid ( script , size , x_segments , y_segments ) transform . translate ( script , [ 0 , 0 , size [ 2 ] ] ) if simple_bottom : plane_hires_edges ( script , size , x_segments , y_segments ) else : layers . duplicate ( script ) transform . translate ( script , [ 0 , 0 , - size [ 2 ] ] ) transform . rotate ( script , 'x' , 180 ) transform . translate ( script , [ 0 , size [ 1 ] , 0 ] ) cube_open_hires ( script = script , size = size , x_segments = x_segments , y_segments = y_segments , z_segments = z_segments ) layers . join ( script ) clean . merge_vert ( script , threshold = 0.00002 ) if center : transform . translate ( script , [ - size [ 0 ] / 2 , - size [ 1 ] / 2 , - size [ 2 ] / 2 ] ) if color is not None : vert_color . function ( script , color = color ) return None

Create a box with user defined number of segments in each direction .


def color_values(color):
    """Read color_names.txt and find the red, green, and blue values
        for a named color.
    """
    # Get the directory where this script file is located:
    this_dir = os.path.dirname(
        os.path.realpath(
            inspect.getsourcefile(
                lambda: 0)))
    color_name_file = os.path.join(this_dir, 'color_names.txt')
    found = False
    for line in open(color_name_file, 'r'):
        line = line.rstrip()
        if color.lower() == line.split()[0]:
            #hex_color = line.split()[1]
            red = line.split()[2]
            green = line.split()[3]
            blue = line.split()[4]
            found = True
            break
    if not found:
        print('Color name "%s" not found, using default (white)' % color)
        red = 255
        green = 255
        blue = 255
    return red, green, blue

Read color_names . txt and find the red green and blue values for a named color .


def check_list(var, num_terms):
    """ Check if a variable is a list and is the correct length.

    If variable is not a list it will make it a list of the correct length with
    all terms identical.
    """
    if not isinstance(var, list):
        if isinstance(var, tuple):
            var = list(var)
        else:
            var = [var]
        for _ in range(1, num_terms):
            var.append(var[0])
    if len(var) != num_terms:
        print(
            '"%s" has the wrong number of terms; it needs %s. Exiting ...' %
            (var, num_terms))
        sys.exit(1)
    return var

Check if a variable is a list and is the correct length .


def make_list(var, num_terms=1):
    """ Make a variable a list if it is not already

    If variable is not a list it will make it a list of the correct length with
    all terms identical.
    """
    if not isinstance(var, list):
        if isinstance(var, tuple):
            var = list(var)
        else:
            var = [var]
    #if len(var) == 1:
            for _ in range(1, num_terms):
                var.append(var[0])
    return var

def make_list ( var , num_terms = 1 ) : if not isinstance ( var , list ) : if isinstance ( var , tuple ) : var = list ( var ) else : var = [ var ] for _ in range ( 1 , num_terms ) : var . append ( var [ 0 ] ) return var


def write_filter(script, filter_xml):
    """ Write filter to FilterScript object or filename

    Args:
        script (FilterScript object or filename str): the FilterScript object
            or script filename to write the filter to.
        filter_xml (str): the xml filter string

    """
    if isinstance(script, mlx.FilterScript):
        script.filters.append(filter_xml)
    elif isinstance(script, str):
        script_file = open(script, 'a')
        script_file.write(filter_xml)
        script_file.close()
    else:
        print(filter_xml)
    return None

def write_filter ( script , filter_xml ) : if isinstance ( script , mlx . FilterScript ) : script . filters . append ( filter_xml ) elif isinstance ( script , str ) : script_file = open ( script , 'a' ) script_file . write ( filter_xml ) script_file . close ( ) else : print ( filter_xml ) return None


def ls3loop(script, iterations=1, loop_weight=0, edge_threshold=0,
            selected=False):
    """ Apply LS3 Subdivision Surface algorithm using Loop's weights.

    This refinement method take normals into account.
    See: Boye', S. Guennebaud, G. & Schlick, C.
    "Least squares subdivision surfaces"
    Computer Graphics Forum, 2010.

    Alternatives weighting schemes are based on the paper:
    Barthe, L. & Kobbelt, L.
    "Subdivision scheme tuning around extraordinary vertices"
    Computer Aided Geometric Design, 2004, 21, 561-583.

    The current implementation of these schemes don't handle vertices of
    valence > 12

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        iterations (int): Number of times the model is subdivided.
        loop_weight (int): Change the weights used. Allow to optimize some
            behaviours in spite of others. Valid values are:
            0 - Loop (default)
            1 - Enhance regularity
            2 - Enhance continuity
        edge_threshold (float): All the edges longer than this threshold will
            be refined. Setting this value to zero will force a uniform
            refinement.
        selected (bool): If selected the filter is performed only on the
            selected faces.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Subdivision Surfaces: LS3 Loop">\n',
        '    <Param name="LoopWeight" ',
        'value="{:d}" '.format(loop_weight),
        'description="Weighting scheme" ',
        'enum_val0="Loop" ',
        'enum_val1="Enhance regularity" ',
        'enum_val2="Enhance continuity" ',
        'enum_cardinality="3" ',
        'type="RichEnum" ',
        '/>\n',
        '    <Param name="Iterations" ',
        'value="{:d}" '.format(iterations),
        'description="Iterations" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="Threshold" ',
        'value="{}" '.format(edge_threshold),
        'description="Edge Threshold" ',
        'min="0" ',
        'max="100" ',
        'type="RichAbsPerc" ',
        '/>\n',
        '    <Param name="Selected" ',
        'value="{}" '.format(str(selected).lower()),
        'description="Affect only selected faces" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

Apply LS3 Subdivision Surface algorithm using Loop s weights .


def merge_vert(script, threshold=0.0):
    """ Merge together all the vertices that are nearer than the specified
    threshold. Like a unify duplicate vertices but with some tolerance.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        threshold (float): Merging distance. All the vertices that are closer
            than this threshold are merged together. Use very small values,
            default is zero.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Merge Close Vertices">\n',
        '    <Param name="Threshold" ',
        'value="{}" '.format(threshold),
        'description="Merging distance" ',
        'min="0" ',
        'max="1" ',
        'type="RichAbsPerc" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

Merge together all the vertices that are nearer than the specified threshold . Like a unify duplicate vertices but with some tolerance .


def close_holes(script, hole_max_edge=30, selected=False,
                sel_new_face=True, self_intersection=True):
    """ Close holes smaller than a given threshold

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        hole_max_edge (int): The size is expressed as number of edges composing
            the hole boundary.
        selected (bool): Only the holes with at least one of the boundary faces
            selected are closed.
        sel_new_face (bool): After closing a hole the faces that have been
            created are left selected. Any previous selection is lost. Useful
            for example for smoothing or subdividing the newly created holes.
        self_intersection (bool): When closing an holes it tries to prevent the
            creation of faces that intersect faces adjacent to the boundary of
            the hole. It is an heuristic, non intersecting hole filling can be
            NP-complete.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Close Holes">\n',
        '    <Param name="maxholesize" ',
        'value="{:d}" '.format(hole_max_edge),
        'description="Max size to be closed" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="Selected" ',
        'value="{}" '.format(str(selected).lower()),
        'description="Close holes with selected faces" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="NewFaceSelected" ',
        'value="{}" '.format(str(sel_new_face).lower()),
        'description="Select the newly created faces" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="SelfIntersection" ',
        'value="{}" '.format(str(self_intersection).lower()),
        'description="Prevent creation of selfIntersecting faces" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None


def split_vert_on_nonmanifold_face(script, vert_displacement_ratio=0.0):
    """ Split non-manifold vertices until it becomes two-manifold.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        vert_displacement_ratio (float): When a vertex is split it is moved
            along the average vector going from its position to the centroid
            of the FF connected faces sharing it.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Split Vertexes Incident on Non Manifold Faces">\n',
        '    <Param name="VertDispRatio" ',
        'value="{}" '.format(vert_displacement_ratio),
        'description="Vertex Displacement Ratio" ',
        'type="RichFloat" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

Split non - manifold vertices until it becomes two - manifold .


def snap_mismatched_borders(script, edge_dist_ratio=0.01, unify_vert=True):
    """ Try to snap together adjacent borders that are slightly mismatched.

    This situation can happen on badly triangulated adjacent patches defined by
    high order surfaces. For each border vertex the filter snaps it onto the
    closest boundary edge only if it is closest of edge_legth*threshold. When
    vertex is snapped the corresponding face it split and a new vertex is
    created.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        edge_dist_ratio (float): Collapse edge when the edge / distance ratio
            is greater than this value. E.g. for default value 1000 two
            straight border edges are collapsed if the central vertex dist from
            the straight line composed by the two edges less than a 1/1000 of
            the sum of the edges length. Larger values enforce that only
            vertexes very close to the line are removed.
        unify_vert (bool): If true the snap vertices are welded together.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Snap Mismatched Borders">\n',
        '    <Param name="EdgeDistRatio" ',
        'value="{}" '.format(edge_dist_ratio),
        'description="Edge Distance Ratio" ',
        'type="RichFloat" ',
        '/>\n',
        '    <Param name="UnifyVertices" ',
        'value="{}" '.format(str(unify_vert).lower()),
        'description="UnifyVertices" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

Try to snap together adjacent borders that are slightly mismatched .


def translate(script, value=(0.0, 0.0, 0.0)):
    """An alternative translate implementation that uses a geometric function.
    This is more accurate than the built-in version."""
    # Convert value to list if it isn't already
    if not isinstance(value, list):
        value = list(value)
    vert_function(script,
             x_func='x+(%s)' % value[0],
             y_func='y+(%s)' % value[1],
             z_func='z+(%s)' % value[2])
    return None

def translate ( script , value = ( 0.0 , 0.0 , 0.0 ) ) : if not isinstance ( value , list ) : value = list ( value ) vert_function ( script , x_func = 'x+(%s)' % value [ 0 ] , y_func = 'y+(%s)' % value [ 1 ] , z_func = 'z+(%s)' % value [ 2 ] ) return None

An alternative translate implementation that uses a geometric function . This is more accurate than the built - in version .


def rotate(script, axis='z', angle=0.0):
    """An alternative rotate implementation that uses a geometric function.
    This is more accurate than the built-in version."""
    angle = math.radians(angle)
    if axis.lower() == 'x':
        vert_function(script,
                 x_func='x',
                 y_func='y*cos({angle})-z*sin({angle})'.format(angle=angle),
                 z_func='y*sin({angle})+z*cos({angle})'.format(angle=angle))
    elif axis.lower() == 'y':
        vert_function(script,
                 x_func='z*sin({angle})+x*cos({angle})'.format(angle=angle),
                 y_func='y',
                 z_func='z*cos({angle})-x*sin({angle})'.format(angle=angle))
    elif axis.lower() == 'z':
        vert_function(script,
                 x_func='x*cos({angle})-y*sin({angle})'.format(angle=angle),
                 y_func='x*sin({angle})+y*cos({angle})'.format(angle=angle),
                 z_func='z')
    else:
        print('Axis name is not valid; exiting ...')
        sys.exit(1)
    return None

def
rotate
(
script
,
axis
=
'z'
,
angle
=
0.0
)
:
angle
=
math
.
radians
(
angle
)
if
axis
.
lower
(
)
==
'x'
:
vert_function
(
script
,
x_func
=
'x'
,
y_func
=
'y*cos({angle})-z*sin({angle})'
.
format
(
angle
=
angle
)
,
z_func
=
'y*sin({angle})+z*cos({angle})'
.
format
(
angle
=
angle
)
)
elif
axis
.
lower
(
)
==
'y'
:
vert_function
(
script
,
x_func
=
'z*sin({angle})+x*cos({angle})'
.
format
(
angle
=
angle
)
,
y_func
=
'y'
,
z_func
=
'z*cos({angle})-x*sin({angle})'
.
format
(
angle
=
angle
)
)
elif
axis
.
lower
(
)
==
'z'
:
vert_function
(
script
,
x_func
=
'x*cos({angle})-y*sin({angle})'
.
format
(
angle
=
angle
)
,
y_func
=
'x*sin({angle})+y*cos({angle})'
.
format
(
angle
=
angle
)
,
z_func
=
'z'
)
else
:
print
(
'Axis name is not valid; exiting ...'
)
sys
.
exit
(
1
)
return

An alternative rotate implementation that uses a geometric function . This is more accurate than the built - in version .


def scale(script, value=1.0):
    """An alternative scale implementation that uses a geometric function.
    This is more accurate than the built-in version."""
    """# Convert value to list if it isn't already
    if not isinstance(value, list):
        value = list(value)
    # If a single value was supplied use it for all 3 axes
    if len(value) == 1:
        value = [value[0], value[0], value[0]]"""
    value = util.make_list(value, 3)
    vert_function(script,
             x_func='x*(%s)' % value[0],
             y_func='y*(%s)' % value[1],
             z_func='z*(%s)' % value[2])
    return None

def scale ( script , value = 1.0 ) : value = util . make_list ( value , 3 ) vert_function ( script , x_func = 'x*(%s)' % value [ 0 ] , y_func = 'y*(%s)' % value [ 1 ] , z_func = 'z*(%s)' % value [ 2 ] ) return None

An alternative scale implementation that uses a geometric function . This is more accurate than the built - in version .


def function_cyl_co(script, r_func='r', theta_func='theta', z_func='z'):
    """Geometric function using cylindrical coordinates.

    Define functions in Z up cylindrical coordinates, with radius 'r',
    angle 'theta', and height 'z'

    See "function" docs for additional usage info and accepted parameters.

    Args:
        r_func (str): function to generate new coordinates for radius
        theta_func (str): function to generate new coordinates for angle.
            0 degrees is on the +X axis.
        z_func (str): function to generate new coordinates for height

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """

    r = 'sqrt(x^2+y^2)'
    # In newer MeshLab atan2 is builtin to muparser
    if isinstance(script, FilterScript) and script.ml_version >= '2016.12':
        theta = 'atan2(y, x)'
    else:
        theta = mp_func.mp_atan2('y', 'x')

    # Use re matching to match whole word; this prevents matching
    # 'sqrt' and 'rint' when replacing 'r'
    r_func = re.sub(r"\br\b", r, r_func).replace('theta', theta)
    theta_func = re.sub(r"\br\b", r, theta_func).replace('theta', theta)
    z_func = re.sub(r"\br\b", r, z_func).replace('theta', theta)

    x_func = '(r)*cos(theta)'.replace('r', r_func).replace('theta', theta_func)
    y_func = '(r)*sin(theta)'.replace('r', r_func).replace('theta', theta_func)

    vert_function(script, x_func, y_func, z_func)
    return None

def
function_cyl_co
(
script
,
r_func
=
'r'
,
theta_func
=
'theta'
,
z_func
=
'z'
)
:
r
=
'sqrt(x^2+y^2)'
if
isinstance
(
script
,
FilterScript
)
and
script
.
ml_version
>=
'2016.12'
:
theta
=
'atan2(y, x)'
else
:
theta
=
mp_func
.
mp_atan2
(
'y'
,
'x'
)
r_func
=
re
.
sub
(
r"\br\b"
,
r
,
r_func
)
.
replace
(
'theta'
,
theta
)
theta_func
=
re
.
sub
(
r"\br\b"
,
r
,
theta_func
)
.
replace
(
'theta'
,
theta
)
z_func
=
re
.
sub
(
r"\br\b"
,
r
,
z_func
)
.
replace
(
'theta'
,
theta
)
x_func
=
'(r)*cos(theta)'
.
replace
(
'r'
,
r_func
)
.
replace
(
'theta'
,
theta_func
)
y_func
=
'(r)*sin(theta)'
.
replace
(
'r'
,
r_func
)
.
replace
(
'theta'
,
theta_func
)
vert_function
(
script
,
x_func
,
y_func
,
z_func
)
return

Geometric function using cylindrical coordinates .


def wrap2cylinder(script, radius=1, pitch=0, taper=0, pitch_func=None,
                  taper_func=None):
    """Deform mesh around cylinder of radius and axis z

    y = 0 will be on the surface of radius "radius"
    pitch != 0 will create a helix, with distance "pitch" traveled in z for each rotation
    taper = change in r over z. E.g. a value of 0.5 will shrink r by 0.5 for every z length of 1

    """
    """vert_function(s=s, x='(%s+y-taper)*sin(x/(%s+y))' % (radius, radius),
                     y='(%s+y)*cos(x/(%s+y))' % (radius, radius),
                     z='z-%s*x/(2*%s*(%s+y))' % (pitch, pi, radius))"""
    if pitch_func is None:
        pitch_func = '-(pitch)*x/(2*pi*(radius))'
    pitch_func = pitch_func.replace(
        'pitch', str(pitch)).replace(
            'pi', str(math.pi)).replace(
                'radius', str(radius))
    if taper_func is None:
        taper_func = '-(taper)*(pitch_func)'
    taper_func = taper_func.replace(
        'taper', str(taper)).replace(
            'pitch_func', str(pitch_func)).replace(
                'pi', str(math.pi))

    x_func = '(y+(radius)+(taper_func))*sin(x/(radius))'.replace(
        'radius', str(radius)).replace('taper_func', str(taper_func))
    y_func = '(y+(radius)+(taper_func))*cos(x/(radius))'.replace(
        'radius', str(radius)).replace('taper_func', str(taper_func))
    z_func = 'z+(pitch_func)'.replace('pitch_func', str(pitch_func))

    vert_function(script, x_func, y_func, z_func)
    return None


def bend(script, radius=1, pitch=0, taper=0, angle=0, straght_start=True,
         straght_end=False, radius_limit=None, outside_limit_end=True):
    """Bends mesh around cylinder of radius radius and axis z to a certain angle

    straight_ends: Only apply twist (pitch) over the area that is bent

    outside_limit_end (bool): should values outside of the bend radius_limit be considered part
        of the end (True) or the start (False)?
    """
    if radius_limit is None:
        radius_limit = 2 * radius
    # TODO: add limit so bend only applies over y<2*radius; add option to set
    # larger limit
    angle = math.radians(angle)
    segment = radius * angle
    """vert_function(s=s, x='if(x<%s and x>-%s, (%s+y)*sin(x/%s), (%s+y)*sin(%s/%s)+(x-%s)*cos(%s/%s))'
                        % (segment, segment,  radius, radius,  radius, segment, radius, segment, segment, radius),
                     y='if(x<%s*%s/2 and x>-%s*%s/2, (%s+y)*cos(x/%s), (%s+y)*cos(%s)-(x-%s*%s)*sin(%s))'
                        % (radius, angle, radius, angle, radius, radius, radius, angle/2, radius, angle/2, angle/2),"""
    pitch_func = '-(pitch)*x/(2*pi*(radius))'.replace(
        'pitch', str(pitch)).replace(
            'pi', str(math.pi)).replace(
                'radius', str(radius))
    taper_func = '(taper)*(pitch_func)'.replace(
        'taper', str(taper)).replace(
            'pitch_func', str(pitch_func)).replace(
                'pi', str(math.pi))
    # y<radius_limit

    if outside_limit_end:
        x_func = 'if(x<(segment) and y<(radius_limit), if(x>0, (y+(radius)+(taper_func))*sin(x/(radius)), x), (y+(radius)+(taper_func))*sin(angle)+(x-(segment))*cos(angle))'
    else:
        x_func = 'if(x<(segment), if(x>0 and y<(radius_limit), (y+(radius)+(taper_func))*sin(x/(radius)), x), if(y<(radius_limit), (y+(radius)+(taper_func))*sin(angle)+(x-(segment))*cos(angle), x))'

    x_func = x_func.replace(
        # x_func = 'if(x<segment, if(x>0, (y+radius)*sin(x/radius), x),
        # (y+radius)*sin(angle)-segment)'.replace(
        'segment', str(segment)).replace(
            'radius_limit', str(radius_limit)).replace(
                'radius', str(radius)).replace(
                    'taper_func', str(taper_func)).replace(
                        'angle', str(angle))

    if outside_limit_end:
        y_func = 'if(x<(segment) and y<(radius_limit), if(x>0, (y+(radius)+(taper_func))*cos(x/(radius))-(radius), y), (y+(radius)+(taper_func))*cos(angle)-(x-(segment))*sin(angle)-(radius))'
    else:
        y_func = 'if(x<(segment), if(x>0 and y<(radius_limit), (y+(radius)+(taper_func))*cos(x/(radius))-(radius), y), if(y<(radius_limit), (y+(radius)+(taper_func))*cos(angle)-(x-(segment))*sin(angle)-(radius), y))'

    y_func = y_func.replace(
        'segment', str(segment)).replace(
            'radius_limit', str(radius_limit)).replace(
                'radius', str(radius)).replace(
                    'taper_func', str(taper_func)).replace(
                        'angle', str(angle))

    if straght_start:
        start = 'z'
    else:
        start = 'z+(pitch_func)'
    if straght_end:
        end = 'z-(pitch)*(angle)/(2*pi)'
    else:
        end = 'z+(pitch_func)'

    if outside_limit_end:
        z_func = 'if(x<(segment) and y<(radius_limit), if(x>0, z+(pitch_func), (start)), (end))'
    else:
        z_func = 'if(x<(segment), if(x>0 and y<(radius_limit), z+(pitch_func), (start)), if(y<(radius_limit), (end), z))'
    z_func = z_func.replace(
        'start', str(start)).replace(
            'end', str(end)).replace(
                'segment', str(segment)).replace(
                    'radius_limit', str(radius_limit)).replace(
                        'radius', str(radius)).replace(
                            'angle', str(angle)).replace(
                                'pitch_func', str(pitch_func)).replace(
                                    'pitch', str(pitch)).replace(
                                        'pi', str(math.pi))

    """
    if straight_ends:
        z_func = 'if(x<segment, if(x>0, z+(pitch_func), z), z-pitch*angle/(2*pi))'.replace(
            'segment', str(segment)).replace(
            'radius', str(radius)).replace(
            'angle', str(angle)).replace(
            'pitch_func', str(pitch_func)).replace(
            'pitch', str(pitch)).replace(
            'pi', str(math.pi))
    else:
        #z_func = 'if(x<segment, z+(pitch_func), z-(taper)*(pitch)*(x)/(2*pi*(radius)))'.replace(
        #z_func = 'if(x<segment, z+(pitch_func), z+(pitch_func))'.replace(
        #bestz_func = 'if(x<segment, z+(pitch_func), z+(pitch_func)+(-(taper)*(pitch)*(x-segment)/(2*pi*(radius))))'.replace(
        #z_func = 'if(x<segment, z+(pitch_func), z+(pitch_func)+(-(taper)*(pitch)*x/(2*pi*(radius))))'.replace(
        #z_func = 'if(x<segment, z+(pitch_func), z+(pitch_func)+((taper)*pitch*angle/(2*pi)))'.replace(
        z_func = 'z+(pitch_func)'.replace(
            'radius', str(radius)).replace(
            'segment', str(segment)).replace(
            'angle', str(angle)).replace(
            'taper', str(taper)).replace(
            'pitch_func', str(pitch_func)).replace(
            'pitch', str(pitch)).replace(
            'pi', str(math.pi))
    """
    """
    x_func = 'if(x<%s, if(x>-%s, (%s+y)*sin(x/%s), (%s+y)*sin(-%s)+(x+%s)*cos(-%s)), (%s+y)*sin(%s)+(x-%s)*cos(%s))' % (
        segment, segment, radius, radius, radius, angle, segment, angle, radius, angle, segment, angle)
    y_func = 'if(x<%s, if(x>-%s, (%s+y)*cos(x/%s), (%s+y)*cos(-%s)-(x+%s)*sin(-%s)), (%s+y)*cos(%s)-(x-%s)*sin(%s))' % (
        segment, segment, radius, radius, radius, angle, segment, angle, radius, angle, segment, angle)
    if straight_ends:
        z_func = 'if(x<%s, if(x>-%s, z-%s*x/(2*%s*%s), z+%s*%s/(2*%s)), z-%s*%s/(2*%s))' % (
            segment, segment, pitch, math.pi, radius, pitch, angle, math.pi, pitch, angle, math.pi)
    else:
        z_func = 'z-%s*x/(2*%s*%s)' % (pitch, math.pi, radius)
    """
    vert_function(script, x_func=x_func, y_func=y_func, z_func=z_func)
    return None


def deform2curve(script, curve=mp_func.torus_knot('t'), step=0.001):
    """ Deform a mesh along a parametric curve function

    Provide a parametric curve function with z as the parameter. This will
    deform the xy cross section of the mesh along the curve as z increases.

    Source: http://blackpawn.com/texts/pqtorus/

    Methodology:
    T  = P' - P
    N1 = P' + P
    B  = T x N1
    N  = B x T

    newPoint = point.x*N + point.y*B

    """
    curve_step = []
    for idx, val in enumerate(curve):
        curve[idx] = val.replace('t', 'z')
        curve_step.append(val.replace('t', 'z+{}'.format(step)))

    tangent = mp_func.v_subtract(curve_step, curve)
    normal1 = mp_func.v_add(curve_step, curve)
    bee = mp_func.v_cross(tangent, normal1)
    normal = mp_func.v_cross(bee, tangent)
    bee = mp_func.v_normalize(bee)
    normal = mp_func.v_normalize(normal)

    new_point = mp_func.v_add(mp_func.v_multiply('x', normal), mp_func.v_multiply('y', bee))
    function = mp_func.v_add(curve, new_point)

    vert_function(script, x_func=function[0], y_func=function[1], z_func=function[2])
    return function

def deform2curve ( script , curve = mp_func . torus_knot ( 't' ) , step = 0.001 ) : curve_step = [ ] for idx , val in enumerate ( curve ) : curve [ idx ] = val . replace ( 't' , 'z' ) curve_step . append ( val . replace ( 't' , 'z+{}' . format ( step ) ) ) tangent = mp_func . v_subtract ( curve_step , curve ) normal1 = mp_func . v_add ( curve_step , curve ) bee = mp_func . v_cross ( tangent , normal1 ) normal = mp_func . v_cross ( bee , tangent ) bee = mp_func . v_normalize ( bee ) normal = mp_func . v_normalize ( normal ) new_point = mp_func . v_add ( mp_func . v_multiply ( 'x' , normal ) , mp_func . v_multiply ( 'y' , bee ) ) function = mp_func . v_add ( curve , new_point ) vert_function ( script , x_func = function [ 0 ] , y_func = function [ 1 ] , z_func = function [ 2 ] ) return function


def vc2tex(script, tex_name='TEMP3D_texture.png', tex_width=1024,
           tex_height=1024, overwrite_tex=False, assign_tex=False,
           fill_tex=True):
    """Transfer vertex colors to texture colors

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        tex_name (str): The texture file to be created
        tex_width (int): The texture width
        tex_height (int): The texture height
        overwrite_tex (bool): If current mesh has a texture will be overwritten (with provided texture dimension)
        assign_tex (bool): Assign the newly created texture
        fill_tex (bool): If enabled the unmapped texture space is colored using a pull push filling algorithm, if false is set to black
    """
    filter_xml = ''.join([
        '  <filter name="Vertex Color to Texture">\n',
        '    <Param name="textName" ',
        'value="%s" ' % tex_name,
        'description="Texture file" ',
        'type="RichString" ',
        '/>\n',
        '    <Param name="textW" ',
        'value="%d" ' % tex_width,
        'description="Texture width (px)" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="textH" ',
        'value="%d" ' % tex_height,
        'description="Texture height (px)" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="overwrite" ',
        'value="%s" ' % str(overwrite_tex).lower(),
        'description="Overwrite texture" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="assign" ',
        'value="%s" ' % str(assign_tex).lower(),
        'description="Assign Texture" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="pullpush" ',
        'value="%s" ' % str(fill_tex).lower(),
        'description="Fill texture" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None


def mesh2fc(script, all_visible_layers=False):
    """Transfer mesh colors to face colors

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        all_visible_layers (bool): If true the color mapping is applied to all the meshes
    """
    filter_xml = ''.join([
        '  <filter name="Transfer Color: Mesh to Face">\n',
        '    <Param name="allVisibleMesh" ',
        'value="%s" ' % str(all_visible_layers).lower(),
        'description="Apply to all Meshes" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None


def uniform_resampling(script, voxel=1.0, offset=0.0, merge_vert=True,
                       discretize=False, multisample=False, thicken=False):
    """ Create a new mesh that is a resampled version of the current one.

    The resampling is done by building a uniform volumetric representation
    where each voxel contains the signed distance from the original surface.
    The resampled surface is reconstructed using the marching cube algorithm
    over this volume.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        voxel (float): voxel (cell) size for resampling. Smaller cells give
            better precision at a higher computational cost. Remember that
            halving the cell size means that you build a volume 8 times larger.
        offset (float): offset amount of the created surface (i.e. distance of
            the created surface from the original one). If offset is zero, the
            created surface passes on the original mesh itself. Values greater
            than zero mean an external surface (offset), and lower than zero
            mean an internal surface (inset). In practice this value is the
            threshold passed to the Marching Cube algorithm to extract the
            isosurface from the distance field representation.
        merge_vert (bool): if True the mesh generated by MC will be cleaned by
            unifying vertices that are almost coincident.
        discretize (bool): if True the position of the intersected edge of the
            marching cube grid is not computed by linear interpolation, but it
            is placed in fixed middle position. As a consequence the resampled
            object will look severely aliased by a stairstep appearance. Useful
            only for simulating the output of 3D printing devices.
        multisample (bool): if True the distance field is more accurately
            compute by multisampling the volume (7 sample for each voxel). Much
            slower but less artifacts.
        thicken (bool): if True, you have to choose a non zero Offset and a
            double surface is built around the original surface, inside and
            outside. Is useful to convert thin floating surfaces into solid,
            thick meshes.

    Layer stack:
        Creates 1 new layer 'Offset mesh'
        Current layer is changed to new layer

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Uniform Mesh Resampling">\n',
        '    <Param name="CellSize" ',
        'value="{}" '.format(voxel),
        'description="Precision" ',
        'min="0" ',
        'max="100" ',
        'type="RichAbsPerc" ',
        '/>\n',
        '    <Param name="Offset" ',
        'value="{}" '.format(offset),
        'description="Offset" ',
        'min="-100" ',
        'max="100" ',
        'type="RichAbsPerc" ',
        '/>\n',
        '    <Param name="mergeCloseVert" ',
        'value="{}" '.format(str(merge_vert).lower()),
        'description="Clean Vertices" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="discretize" ',
        'value="{}" '.format(str(discretize).lower()),
        'description="Discretize" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="multisample" ',
        'value="{}" '.format(str(multisample).lower()),
        'description="Multisample" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="absDist" ',
        'value="{}" '.format(str(thicken).lower()),
        'description="Absolute Distance" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    if isinstance(script, FilterScript):
        script.add_layer('Offset mesh')
    return None

Create a new mesh that is a resampled version of the current one .


def surface_poisson_screened(script, visible_layer=False, depth=8,
                             full_depth=5, cg_depth=0, scale=1.1,
                             samples_per_node=1.5, point_weight=4.0,
                             iterations=8, confidence=False, pre_clean=False):
    """ This surface reconstruction algorithm creates watertight
        surfaces from oriented point sets.

    The filter uses the original code of Michael Kazhdan and Matthew Bolitho
    implementing the algorithm in the following paper:

    Michael Kazhdan, Hugues Hoppe,
    "Screened Poisson surface reconstruction"
    ACM Trans. Graphics, 32(3), 2013

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        visible_layer (bool): If True all the visible layers will be used for
            providing the points
        depth (int): This integer is the maximum depth of the tree that will
            be used for surface reconstruction. Running at depth d corresponds
            to solving on a voxel grid whose resolution is no larger than
            2^d x 2^d x 2^d. Note that since the reconstructor adapts the
            octree to the sampling density, the specified reconstruction depth
            is only an upper bound. The default value for this parameter is 8.
        full_depth (int): This integer specifies the depth beyond depth the
            octree will be adapted. At coarser depths, the octree will be
            complete, containing all 2^d x 2^d x 2^d nodes. The default value
            for this parameter is 5.
        cg_depth (int): This integer is the depth up to which a
            conjugate-gradients solver will be used to solve the linear system.
            Beyond this depth Gauss-Seidel relaxation will be used. The default
            value for this parameter is 0.
        scale (float): This floating point value specifies the ratio between
            the diameter of the cube used for reconstruction and the diameter
            of the samples' bounding cube.  The default value is 1.1.
        samples_per_node (float): This floating point value specifies the
            minimum number of sample points that should fall within an octree
            node as the octree construction is adapted to sampling density. For
            noise-free samples, small values in the range [1.0 - 5.0] can be
            used. For more noisy samples, larger values in the range
            [15.0 - 20.0] may be needed to provide a smoother, noise-reduced,
            reconstruction. The default value is 1.5.
        point_weight (float): This floating point value specifies the
            importance that interpolation of the point samples is given in the
            formulation of the screened Poisson equation. The results of the
            original (unscreened) Poisson Reconstruction can be obtained by
            setting this value to 0. The default value for this parameter is 4.
        iterations (int): This integer value specifies the number of
            Gauss-Seidel relaxations to be performed at each level of the
            hierarchy. The default value for this parameter is 8.
        confidence (bool): If True this tells the reconstructor to use the
            quality as confidence information; this is done by scaling the unit
            normals with the quality values. When the flag is not enabled, all
            normals are normalized to have unit-length prior to reconstruction.
        pre_clean (bool): If True will force a cleaning pre-pass on the data
            removing all unreferenced vertices or vertices with null normals.

    Layer stack:
        Creates 1 new layer 'Poisson mesh'
        Current layer is not changed

    MeshLab versions:
        2016.12
    """
    filter_xml = ''.join([
        '  <xmlfilter name="Screened Poisson Surface Reconstruction">\n',
        '    <xmlparam name="cgDepth" value="{:d}"/>\n'.format(cg_depth),
        '    <xmlparam name="confidence" value="{}"/>\n'.format(str(confidence).lower()),
        '    <xmlparam name="depth" value="{:d}"/>\n'.format(depth),
        '    <xmlparam name="fullDepth" value="{:d}"/>\n'.format(full_depth),
        '    <xmlparam name="iters" value="{:d}"/>\n'.format(iterations),
        '    <xmlparam name="pointWeight" value="{}"/>\n'.format(point_weight),
        '    <xmlparam name="preClean" value="{}"/>\n'.format(str(pre_clean).lower()),
        '    <xmlparam name="samplesPerNode" value="{}"/>\n'.format(samples_per_node),
        '    <xmlparam name="scale" value="{}"/>\n'.format(scale),
        '    <xmlparam name="visibleLayer" value="{}"/>\n'.format(str(visible_layer).lower()),
        '  </xmlfilter>\n'])
    util.write_filter(script, filter_xml)
    if isinstance(script, FilterScript):
        script.add_layer('Poisson mesh', change_layer=False)
    return None

This surface reconstruction algorithm creates watertight surfaces from oriented point sets .


def voronoi(script, hole_num=50, target_layer=None, sample_layer=None, thickness=0.5, backward=True):
    """ Turn a model into a surface with Voronoi style holes in it

    References:
    http://meshlabstuff.blogspot.com/2009/03/creating-voronoi-sphere.html
    http://meshlabstuff.blogspot.com/2009/04/creating-voronoi-sphere-2.html

    Requires FilterScript object

    Args:
        script: the FilterScript object to write the filter to. Does not
            work with a script filename.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """

    if target_layer is None:
        target_layer = script.current_layer()
    if sample_layer is None:
        # Current layer is currently not changed after poisson_disk is run
        sampling.poisson_disk(script, sample_num=hole_num)
        sample_layer = script.last_layer()

    vert_color.voronoi(script, target_layer=target_layer, source_layer=sample_layer, backward=backward)
    select.vert_quality(script, min_quality=0.0, max_quality=thickness)
    if backward:
        select.invert(script)
    delete.selected(script)
    smooth.laplacian(script, iterations=3)

    return None

def voronoi ( script , hole_num = 50 , target_layer = None , sample_layer = None , thickness = 0.5 , backward = True ) : if target_layer is None : target_layer = script . current_layer ( ) if sample_layer is None : sampling . poisson_disk ( script , sample_num = hole_num ) sample_layer = script . last_layer ( ) vert_color . voronoi ( script , target_layer = target_layer , source_layer = sample_layer , backward = backward ) select . vert_quality ( script , min_quality = 0.0 , max_quality = thickness ) if backward : select . invert ( script ) delete . selected ( script ) smooth . laplacian ( script , iterations = 3 ) return None


def all(script, face=True, vert=True):
    """ Select all the faces of the current mesh

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        faces (bool): If True the filter will select all the faces.
        verts (bool): If True the filter will select all the vertices.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Select All">\n',
        '    <Param name="allFaces" ',
        'value="{}" '.format(str(face).lower()),
        'description="DSelect all Faces" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="allVerts" ',
        'value="{}" '.format(str(vert).lower()),
        'description="Select all Vertices" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def
all
(
script
,
face
=
True
,
vert
=
True
)
:
filter_xml
=
''
.
join
(
[
'  <filter name="Select All">\n'
,
'    <Param name="allFaces" '
,
'value="{}" '
.
format
(
str
(
face
)
.
lower
(
)
)
,
'description="DSelect all Faces" '
,
'type="RichBool" '
,
'/>\n'
,
'    <Param name="allVerts" '
,
'value="{}" '
.
format
(
str
(
vert
)
.
lower
(
)
)
,
'description="Select all Vertices" '
,
'type="RichBool" '
,
'/>\n'
,
'  </filter>\n'
]
)
util
.
write_filter
(
script
,
filter_xml
)
return