source: orange-bioinformatics/_bioinformatics/obiDicty.py @ 1679:acc78615f489

Revision 1679:acc78615f489, 57.5 KB checked in by markotoplak, 23 months ago (diff)

PIPAx: downloading data now uses buffers (removed reload=reload from calls in get_data).

Line 
1import sys, pprint, time, re
2from itertools import *
3import urllib
4import urllib2
5import socket
6import os
7from collections import defaultdict
8import pickle
9
10import orange
11from Orange.orng import orngServerFiles
12
13defaddress = "http://bcm.fri.uni-lj.si/microarray/api/index.php?"
14#defaddresspipa = "https://pipa.fri.uni-lj.si/pipa/script/api/orange.py?action="
15defaddresspipa = "https://pipa.fri.uni-lj.si/PIPA/PIPAapi/PIPAorange.py"
16defaddresspipa_coverage = "https://pipa.biolab.si/tbrowse/api/pipa.py"
17
18pipaparuser = "pipa_username"
19pipaparpass = "pipa_password"
20
21#utility functions - from Marko's mMisc.py
22
23def splitN(origsize, maxchunk):
24    """
25    Splits an integer into chunks of given size. Each created chunk
26    except possibly the last one is of maximum allowed size.
27    Chunks are returned in list.
28    """
29    l = [maxchunk]*(origsize/maxchunk)
30    a = origsize % maxchunk
31    if a > 0: 
32        l.append(a)
33    return l
34
35def split(l, maxchunk):
36    """
37    Splits list l into chunks of size maxchunk. Each created chunk
38    except possibly the last one is of maximum allowed size.
39    """
40    sizes = splitN(len(l), maxchunk)
41    out = []
42    tillNow = 0
43    for s in sizes:
44        out.append(l[tillNow:tillNow+s])
45        tillNow += s
46    return out       
47
48def lloc(l,n):
49    """
50    List location in list of list structure.
51    Enable the use of negative locations:
52    -1 is the last element, -2 second last...
53    """
54    if n < 0:
55        return len(l[0])+n
56    else:
57        return n
58
59def loc(l,n):
60    """
61    List location.
62    Enable the use of negative locations:
63    -1 is the last element, -2 second last...
64    """
65    if n < 0:
66        return len(l)+n
67    else:
68        return n
69
70def nth(l,n):
71    """
72    Returns only nth elemnt in a list.
73    """
74    n = lloc(l,n)
75    return [ a[n] for a in l ]
76
77def imnth(l, ns):
78    """
79    Return only columns as specified in ns. Returns an generator.
80    """
81    ns = [ lloc(l,n) for n in ns ]
82    for a in l:
83        yield [ a[n] for n in ns ]
84
85def flatten(l,r=0):
86    """
87    Flatten a python structure into a list. Leave strings alone.
88    """
89    if type(l) == type("a"):
90        return [ l ]
91    try: #if enumerable then flatten it's elements
92        rec = [ flatten(a,r=r+1) for a in l ]
93        ret = []
94        for a in rec:
95            ret = ret + a
96        return ret
97    except:
98        return [ l ]
99
100def mxrange(lr):
101    """
102    Multiple xranges. Can be used to traverse matrices.
103    This function is very slow due to unknown number of
104    parameters.
105
106    >>> mxrange([3,5])
107    [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
108    >>> mxrange([[3,5,1],[9,0,-3]])
109    [(3, 9), (3, 6), (3, 3), (4, 9), (4, 6), (4, 3)]
110    """
111    if len(lr) == 0:
112        yield ()
113    else:
114        #it can work with single numbers
115        index = lr[0]
116        if type(1) == type(index):
117            index = [ index ]
118        for a in range(*index):
119            for b in mxrange(lr[1:]):
120                yield tuple([a] + list(b))
121
122
123def issequencens(x):
124    """
125    Is x a sequence and not string ? We say it is if it has a __getitem__
126    method and it is not an instance of basestring.
127    """
128    return hasattr(x, '__getitem__') and not isinstance(x, basestring)
129
130#end utility functions
131
132import statc
133#median = statc.median
134
135def median(l):
136    if len(l) == 0:
137        return None
138    else:
139        return statc.median(l)
140
141
142
143socket.setdefaulttimeout(60)
144
145verbose = 0
146
147class HttpGetException(Exception): pass
148
149def replaceChars(address):
150    return address.replace(" ", "%20")
151
152def httpGet(address, *args, **kwargs):
153    if verbose: 
154        print address, args, kwargs,  " "
155    address = replaceChars(address)
156    t1 = time.time()
157    f = urllib2.urlopen(address, *args, **kwargs)
158    read = f.read()
159    if verbose:
160        print "bytes", len(read),
161    if verbose:
162        print time.time() - t1
163    return read
164
165def txt2ll(s, separ=' ', lineSepar='\n'):
166    return [ a.split(separ) for a in s.split(lineSepar) ]
167
168class AuthenticationError(Exception):
169    pass
170
171class DBInterface(object):
172 
173    def __init__(self, address):
174        self.address = address
175
176    def raw(self, request, data=None, tryN=3):
177        if verbose:
178            print "tryN", tryN
179
180        def try_down():
181            try:
182                if data == None:
183                    return httpGet(self.address + request)
184                else:
185                    return httpGet(self.address + request, data=urllib.urlencode(data))
186            except urllib2.HTTPError, e:
187                if e.code == 403:
188                    raise AuthenticationError()
189                else:
190                    raise e
191
192        if tryN > 1:
193            try:
194                return try_down()
195            except IOError:
196                return self.raw(request, data=data, tryN=tryN-1)
197        else:
198            return try_down()
199
200    def get(self, request, data=None, tryN=3):
201        rawf = self.raw(request, data)
202        if rawf == None:
203            print "rawf == None!"
204            raise Exception("Connection error when contacting " + self.address + request)
205        if rawf.startswith("error: authentication failed"):
206            raise AuthenticationError()
207        elif rawf[:1] == "<" or rawf[:5] == "error" or rawf.startswith("MOD_PYTHON ERROR"): #an error occurred - starting some html input
208            #TODO are there any other kinds of errors?
209            if tryN > 0:
210                if verbose:
211                    print "trying again"
212                return self.get(request, data=data, tryN=tryN-1)
213            else:
214                if verbose:
215                    print rafw[:1000]
216                raise Exception("Error with the database")
217
218        a = txt2ll(rawf, separ='\t')
219       
220        if a[-1][0] == "": #remove empty line on end
221            a = a[:-1]
222        return a
223
224def _test():
225    import doctest
226    doctest.testmod()
227
228def splitTableOnColumn(ll,n):
229    omap = {}
230    n = lloc(ll,n)
231    for l in ll:
232        cell = omap.get(l[n], [])
233        cell.append(l)
234        omap[l[n]] = cell
235    return omap
236
237def neededColumns(legend, want):
238    return [ legend.index(a) for a in want ]
239
240def onlyColumns(ll, legend, want):
241    return list(imnth(ll, neededColumns(legend, want)))
242
243def ll2dic(ll, key=0, value=1):
244    """
245    Converts LL to map. Key is key position, value value position
246    """
247    names = nth(ll, lloc(ll, key))
248    if len(names) == len(set(names)):
249        return dict(list(imnth(ll, [ lloc(ll, key),  lloc(ll, value) ] )))
250    else:
251        raise Exception("all keys are not unique")
252
253def dic2ll(dic):
254
255    columns = sorted(dic.values()[0].keys())
256    ll = []
257    for key, d in dic.items():
258        ll.append([ key ] + [ d[a] for a in columns ])
259    return columns,ll
260
261def allUnique(els):
262    return len(els) == len(set(els))
263
264def reverseDic(d):
265    """
266    Create a reverse dictionary if a unique reverse is possible.
267    """
268    if allUnique(d.values()):
269        return dict([ (b,a) for a,b in d.items() ])
270
271def chainLookup(a, dics, force=[]):
272    """
273    Goes through a list of dictionaries. On each step try to
274    transform current key to value. The values becomes the key
275    for next search If unsuccessfull, query the next
276    dictionary with current key. Force last translation.
277    """
278    force = [ loc(dics, i) for i in force ]
279    for i,dic in enumerate(dics):
280        if i in force: #force specified translations
281            a = dic[a]
282        else:
283            if a in dic:
284                a = dic[a]
285    return a
286
287class DBCommon(object):
288
289    def fromBuffer(self, addr):
290        return self.buffer.get(self.address + addr)
291
292    def toBuffer(self, addr, cont, version, autocommit=True):
293        if self.buffer:
294            return self.buffer.add(self.address + addr, cont, version=version, autocommit=autocommit)
295
296    def bufferCommit(self):
297        if self.buffer:
298            self.buffer.commit()
299
300    def bufferFun(self, bufkey, bufver, reload, fn, *args, **kwargs):
301        """
302        If bufkey is already present in buffer, return its contents.
303        If not, run function with arguments and save its result
304        into the buffer.
305        """
306        if self.inBuffer(bufkey) == bufver and reload == False:
307            res = self.fromBuffer(bufkey)
308        else:
309            res = fn(*args, **kwargs)
310            self.toBuffer(bufkey, res, bufver)
311        return res
312
313    def sq(self, s1, data=None, buffer=True, bufadd="", bufname=None, bufver="0", reload=False, bufferkey=None, db=None):
314        if db == None:
315            db = self.db
316        if buffer:
317            bufkey = bufadd + (bufname if bufname != None else s1)
318            if bufferkey != None:
319                bufkey = bufferkey(bufkey, data)
320            res = self.bufferFun(bufkey, bufver, reload, db.get, s1, data=data)
321        else:
322            res = db.get(s1, data=data)
323        return res[1:],res[0]
324
325    def inBuffer(self, addr):
326        if self.buffer:
327            return self.buffer.contains(self.address + addr)
328        else:
329            return False
330
331    def dictionarize(self, ids, fn, *args, **kwargs):
332        """
333        Creates a dictionary from id: function result.
334        Callback for each part done.
335        """
336        callback = kwargs.pop("callback", None)
337        odic = {}
338        for a,b in izip(ids, fn(*args, **kwargs)):
339            odic[a] = b
340            if callback: callback()
341        return odic
342
343    def downloadMulti_bufcommand_replace_multi(self, command, data=None, chunk=100, bufferkey=None, transformfn=None):
344        """
345        Get function which gives buffer address for an id and a function
346        which replaces $MULTI$.
347        """
348
349        def bufferkey1(command, data):
350            if transformfn:
351                return "TRANS " + command
352            else:
353                return command
354
355        def replace_multi(command, data, repl):
356            return command.replace("$MULTI$", repl),\
357                dict((a,b.replace("$MULTI$", repl)) for a,b in sorted(data.items())) if data != None else None
358
359        if bufferkey == None:
360            bufferkey=bufferkey1
361
362        bufcommand = lambda x, c=command, d=data: bufferkey(*replace_multi(c, d, x))
363        return bufcommand, replace_multi
364
365    def downloadMulti(self, command, ids, data=None, chunk=100, transformfn=None, bufferkey=None, separatefn=None, bufreload=False, bufver="0"):
366        """
367        Downloads multiple results at once.
368        Results in the same order as in ids.
369
370        Bufferkey transforms command and data into buffer key.
371        bufver is a function returning buffer version for a given id. if
372            a string is given, use it for all ids
373        """
374
375        sids = split(ids,chunk)
376   
377        bufverfn = None
378        if isinstance(bufver, basestring):
379            bufverfn = lambda x: bufver
380        else:
381            bufverfn = bufver
382
383        bufcommand, replace_multi = self.downloadMulti_bufcommand_replace_multi(command, data=data, chunk=chunk, bufferkey=bufferkey, transformfn=transformfn)
384
385        for i,sidp in enumerate(sids):
386
387            buffered = []
388            unbuffered = []
389       
390            for a in sidp:
391                if self.inBuffer(bufcommand(a)) == bufverfn(a) and bufreload == False:
392                    buffered.append(a)
393                else:
394                    unbuffered.append(a)
395
396            res = []
397            legend = []
398
399            if len(unbuffered) > 0:
400                com1, d1 = replace_multi(command, data, ",".join(unbuffered))
401                res, legend = self.sq(com1, data=d1, buffer=False) #get unbuffered part
402            else:
403                # get legend from buffer also
404                legend = self.fromBuffer(bufcommand(buffered[0]))[0]
405
406            #split on different values of the first column - first attribute
407
408            if not separatefn:
409                antss = splitTableOnColumn(res, 0)
410            else:
411                legend, antss = separatefn([legend]+res)
412
413            #if transform before saving is requested, do it
414            if transformfn:
415                nantss = {}
416                nlegend = None
417                for a,b in antss.items():
418                    nb, nlegend = transformfn(b, legend)
419                    nantss[a] = nb
420                legend = nlegend
421                antss = nantss
422 
423            #here save buffer
424            for a,b in antss.items():
425                self.toBuffer(bufcommand(a), [ legend ] + b, bufverfn(a), autocommit=False)
426            self.bufferCommit()
427           
428
429            #get buffered from the buffer
430            antssb = dict([ (b, self.fromBuffer(bufcommand(b))[1:]) for b in buffered ])
431            antss.update(antssb)
432
433            #put results in order
434            tl = []
435            for ci in sidp:
436                yield antss[ci], legend
437
438    def exampleTables(self, ids, chipsm=None, spotmap={}, callback=None, exclude_constant_labels=False, annots={}, chipfn=None, allowed_labels=None):
439        """
440        Create example tables from chip readings, spot mappings and
441        group specifications.
442
443        group is the output from "sortAnnotations" function.
444        spotmap is a dictionary of { spotid: gene }
445        chipsm is a dictionary of chip readings
446
447        Callback: number of chipids + 2
448        """
449
450        if verbose:
451            print "Creating example table"
452
453        if callback: callback()
454
455        amap = {}
456        amapnext = 0
457
458        togen = []
459
460        groupnames = []
461        groupvals = []
462        groupannots = []
463
464        if chipsm == None:
465            chipdl = chipfn(ids)
466
467        for chipid in ids:
468
469            if chipsm != None:
470                chipdata = chipsm[chipid]
471            else:
472                chipdata = chipdl.next()
473
474            if callback: callback()
475
476            #add to current position mapping
477            repeats = {}
478            for id,_ in chipdata:
479                rep = repeats.get(id, 0)
480                repeats[id] = rep+1
481                key = (id, rep)
482                if key not in amap:
483                    amap[key] = amapnext
484                    amapnext += 1
485
486            vals = [ None ] * len(amap)
487
488            repeats = {}
489            for id,v in chipdata:
490                rep = repeats.get(id, 0)
491                repeats[id] = rep+1
492                key = (id, rep)
493                putind = amap[key]
494                vals[putind] = v
495            groupvals.append(vals)
496
497            groupnames.append(chipid) 
498
499            newannots = [['id', str(chipid)]] #add chipid to annotations
500            if annots:
501                newannots += annots[chipid]
502            groupannots.append(newannots)
503
504        togen = (groupnames, groupvals, groupannots)
505       
506        if callback: callback()
507
508        ddb = [ None ]*len(amap)
509        for (a,rep),pos in amap.items():
510            if len(spotmap):
511                ddb[pos] = spotmap.get(a, "#"+a)
512            else:
513                ddb[pos] = a
514
515        #this is sorted position mapping: key -> sortedind
516        posMap = dict( (k,i) for i,k in enumerate(sorted(amap.keys())) )
517        revmap = dict( ( (i,k) for k,i in amap.items() ) )
518        #permutation[i] holds target of current [i]
519        permutation = [ posMap[revmap[i]] for i in range(len(amap)) ]
520
521        def enlength(a, tlen):
522            """ Adds Nones to the end of the list """
523            if len(a) < tlen:
524                return a + [ "None" ]*(tlen-len(a))
525            else:
526                return a
527
528        def enlengthl(l, tlen):
529            return [ enlength(a, tlen) for a in l ]
530   
531        groupnames, groupvals, groupannots = togen
532
533        et = createExampleTable(groupnames, 
534            enlengthl(groupvals, len(ddb)),
535            groupannots, ddb, exclude_constant_labels=exclude_constant_labels, permutation=permutation, allowed_labels=allowed_labels)
536
537        if callback: callback()
538
539        return et
540
541def bufferkeypipa(command, data):
542    """ Do not save password to the buffer! """
543    command = command + " v5" #add version
544    if data != None:
545        data = data.copy()
546        if pipaparpass in data:
547            data.pop(pipaparpass)
548        return command + " " +  urllib.urlencode(sorted(data.items()))
549    else:
550        return command
551
552class PIPA(DBCommon):
553
554    def __init__(self, address=defaddresspipa, buffer=None, username=None, password=None):
555        self.address = address
556        self.db=DBInterface(address)
557        self.db_coverage=DBInterface(defaddresspipa_coverage) #temporary
558        self.buffer = buffer
559        self.username = None
560        if username != None:
561            self.username = username
562            self.password = password
563
564    def add_auth(self, data=None):
565        if self.username == None:
566            return data
567        authdic = { pipaparuser: self.username, pipaparpass: self.password }
568        if data != None:
569            authdic.update(data)
570        return authdic
571
572    def annotations(self, reload=False, bufver="0"):
573        """
574        Returns a dictionary of (id: dictionary of annotations).
575        """
576        #res, legend = self.sq("mapping_list", data=self.add_auth(), reload=reload, bufferkey=bufferkeypipa, bufver=bufver)
577        res, legend = self.sq("", data=self.add_auth({"action": "expression_list"}), reload=reload, bufferkey=bufferkeypipa, bufver=bufver)
578        return dict( (sa[0], dict(zip(legend[1:], sa[1:]))) for sa in res )
579
580    def chips(self, ids, ctype, reload=False, bufver="0"):
581        def separatefn(res):
582            #each one is own rown
583            #genes are in the first row
584            #remove unknown
585            genes = res[0][1:]
586            cids = nth(res,0)[1:]
587
588            antss = {}
589            for i,cid in enumerate(cids):
590                row = i+1
591                vals = res[row][1:]
592                antss[cid] = [ list(a) for a in zip(genes, vals) if a[1] != "?" ]
593            return ['gene_id', 'value'], antss
594
595        download_command = "gene_expression"
596        datadict = {"action": download_command, "ids":"$MULTI$", 'transpose':'1'}
597        if ctype != None:
598            datadict["type"] = ctype
599        antss = self.downloadMulti("", ids, data=self.add_auth(datadict), chunk=10, separatefn=separatefn, bufferkey=bufferkeypipa, bufreload=reload, bufver=bufver)
600        for a,legend in antss:
601            yield a
602
603    def coverage(self, id_, genome, chromosome, reload=False, bufver="0", raw=False):
604        data = self.add_auth({"action": "coverage_strand", "chr_name": str(chromosome), 
605            "pixel_size": "1", "genome": genome, "sample_id": str(id_), "raw": str(raw).lower()})
606
607        def comp():
608
609            d = dict(data)
610
611            def get():
612                #sq returns [ "all lines after first", "firstline"] - take first column!
613                #do not use buffering on this stage!
614                a = self.sq("", data=d, reload=reload, buffer=False, bufferkey=bufferkeypipa, bufver=bufver, db=self.db_coverage)[1][0].strip("[]")
615                return map(int, a.split(", "))
616               
617            d["strand"] = "plus"
618            plus = get()
619            d["strand"] = "minus"
620            minus = get()
621
622            import numpy
623            return numpy.array([plus, minus])
624       
625        bufkey =  bufferkeypipa("", data)
626        return self.bufferFun(bufkey, bufver, reload, comp)
627
628
629    def gene_expression_types(self, reload=False, bufver="0"):
630        #res, legend = self.sq("gene_expression_type", data={self.add_auth(), reload=reload, bufferkey=bufferkeypipa, bufver=bufver)
631        res, legend = self.sq("", data=self.add_auth({"action":"gene_expression_type"}), reload=reload, bufferkey=bufferkeypipa, bufver=bufver)
632        return sorted(tuple(a) for a in res)
633
634    def chips_keynaming(self, ctype):
635        keynamingfn,_ = self.downloadMulti_bufcommand_replace_multi("", data=self.add_auth({"action": "gene_expression", "ids":"$MULTI$", 'transpose':'1', 'type':ctype}), chunk=100, bufferkey=bufferkeypipa, transformfn=None)
636        return keynamingfn
637
638    def get_data(self, exclude_constant_labels=False, average=median, 
639        ids=None, callback=None, bufver="0", transform=None, ctype=None, allowed_labels=None):
640        """
641        Get data in a single example table with labels of individual attributes
642        set to annotations for query and post-processing
643        instructions.
644
645        Parameters:
646            average: function used for combining multiple reading of the same spot on
647                a chip. If None, no averaging is done. Fuction should take a list
648                of floats and return an "averaged" float.
649            ids: a list of chip ids. If absent, make a search
650            exclude_constant_labels: if a label has the same value in whole
651                example table, remove it
652            format: if short, use short format for chip download
653            ctype: expression type, from gene_expression_types
654
655        Defaults: Median averaging.
656        """
657
658        def optcb():
659            if callback: callback()
660
661        cbc = CallBack(1, optcb, callbacks=10)
662
663        if not ids:
664            return None
665
666        cbc.end()
667
668        #downloads annotations
669        cbc = CallBack(len(ids), optcb, callbacks=10)
670
671        readall = self.annotations()
672
673        read = {}
674        for a,b in readall.items():
675            read[a] = b.items()
676
677        cbc.end()
678
679        #till now downloads were small
680
681        import time
682        tstart = time.time()
683
684        #here download actually happens
685        chipfn = None
686
687        chipfn = lambda x: self.chips(x, ctype, bufver=bufver)
688       
689        if verbose:
690            print "DOWNLOAD TIME", time.time() - tstart
691
692        cbc = CallBack(len(ids)*2+len(ids)+1, optcb, callbacks=999-30)
693        et = self.exampleTables(ids, spotmap={}, callback=cbc, annots=read, exclude_constant_labels=exclude_constant_labels, chipfn=chipfn, allowed_labels=allowed_labels)
694        cbc.end()
695
696        cbc = CallBack(1, optcb, callbacks=10)
697
698        #transformation is performed prior to averaging
699        if transform != None:
700            transformValues(et, fn=transform) #in place transform
701            cbc()
702
703        #if average function is given, use it to join same spotids
704        if average != None:
705            et = averageAttributes(et, fn=average)
706            cbc()
707
708        cbc.end()
709
710        return et
711
712def bufferkeypipax(command, data):
713    """ Do not save password to the buffer! """
714    command = command + " v7" #add version
715    if data != None:
716        data = data.copy()
717        if pipaparpass in data:
718            data.pop(pipaparpass)
719        return command + " " +  urllib.urlencode(sorted(data.items()))
720    else:
721        return command
722
723
724class PIPAx(PIPA):
725    """`PIPAx <http://pipa.biolab.si/?page_id=23>` api interface.
726    """
727
728    API_ADDRESS = "https://pipa.biolab.si/pipax/api.py"
729
730    def __init__(self, address=API_ADDRESS, buffer=None,
731                 username=None, password=None):
732        self.address = address
733        self.db = DBInterface(address)
734        self.buffer = buffer
735        self.username = username
736        self.password = password
737
738    def genomes(self, reload=False, bufver="0"):
739        """Return a list of available genomes as a list of
740        (genome_id, genome_name) tuples.
741
742        >>> pipax.genomes()
743        [('dd', 'Dictyostelium discoideum'), ('dp', 'Dictyostelium purpureum')]
744
745        """
746        data = {"action": "genomes"}
747        res, _ = self.sq("", data=data, reload=reload, bufver=bufver)
748        return [tuple(r) for r in res]
749
750    def mappings(self, reload=False, bufver="0"):
751        """Return a dictionary of {unique_id: dictionary_of_annotations}
752        where the keys for dictionary_of_annotations are
753        ["id", data_id", "data_name", "genomes_id"]
754
755        >>> mappings = pipax.mappings()
756
757        """
758        data = self.add_auth({"action": "mappings"})
759        res, legend = self.sq("", data=data, reload=reload,
760                              bufferkey=bufferkeypipax,
761                              bufver=bufver)
762
763        return dict((sa[0], dict(zip(legend[1:], sa[1:]))) for sa in res)
764
765    def result_types(self, reload=False, bufver="0"):
766        """Return a list of available result type templates.
767        """
768        data = {"action": "results_templates"}
769        res, _ = self.sq("", data=data, reload=reload, bufver=bufver)
770        return sorted(tuple(a) for a in res)
771
772    def results_list(self, rtype, reload=False, bufver="0"):
773        """Return a list of available results for a result type template
774        `rtype` (a key from the `result_types` return value).
775
776        """
777        data = {"action": "results",
778                "results_templates_id": rtype}
779        data = self.add_auth(data)
780        res, legend = self.sq("", data=data, reload=reload,
781                              bufferkey=bufferkeypipax,
782                              bufver=bufver)
783        # index by unique_id (last column)
784        res = splitTableOnColumn(res, -1)
785        return dict((id, dict(zip(legend, line[0]))) \
786                    for id, line in res.items())
787
788    def download_key_function(self):
789        data = self.add_auth({"action": "download",
790                              "ids": "$MULTI$"}
791                                )
792        keynamingfn, _ = self.downloadMulti_bufcommand_replace_multi("",
793                         data=data, chunk=100, bufferkey=bufferkeypipax,
794                         transformfn=None)
795        return keynamingfn
796
797    def get_data(self, ids=None, result_type=None,
798                 exclude_constant_labels=False, average=median,
799                 callback=None, bufver="0", transform=None,
800                 allowed_labels=None):
801        """
802        Get data in a single example table with labels of individual
803        attributes set to annotations for query and post-processing
804        instructions.
805
806        :param ids: If `result_type` is specified then this must
807            be a list of  `unique_id`s or (id, data_id) tuples as
808            returned by `mappings`. Else the `ids` must be a list
809            of `unique_id`s as returned by `results_list`.
810        :type ids: list
811
812        :param result_type: Result template type id as returned by
813             `result_types`. Not specified by default (see `ids`.)
814        :type result_type: str
815
816        :param exclude_constant_labels: If a label has the same value
817            in whole example table, remove it.
818        :type exclude_constant_labels: bool
819
820        :param average: Function used for combining multiple reading of
821            the same spot on a chip. If None, no averaging is done.
822            Function should take a list of floats and return an "averaged"
823            float (default `median`).
824        :type average: function
825
826        """
827
828        def optcb():
829            if callback:
830                callback()
831
832        def argsort(a):
833            sort = sorted([(map(int, item), i) for i, item in enumerate(a)])
834            return [i for _, i in sort]
835
836        cbc = CallBack(len(ids), optcb, callbacks=10)
837
838        if result_type is not None:
839            ids_sort = argsort(ids)
840            res_list = self.results_list(result_type, bufver=bufver)
841            # Map (data_id, mapping_id) to unique_id
842            res_types_to_unique_id = \
843                dict(((annot["data_id"], annot["mappings_id"]),
844                      annot["unique_id"]) \
845                     for annot in res_list.values())
846
847            mappings = self.mappings(bufver=bufver)
848
849            def id_map(mappings_unique_id):
850                cbc()
851                if isinstance(mappings_unique_id, tuple):
852                    data_id, mappings_id = mappings_unique_id
853                else:
854                    annot = mappings[mappings_unique_id]
855                    data_id = annot["data_id"]
856                    mappings_id = annot["id"]
857                return res_types_to_unique_id[data_id, mappings_id]
858
859            ids = map(id_map, ids)
860        else:
861            result_type_set = set(id.rsplit("_", 1)[-1] for id in ids)
862            if len(result_type_set) != 1:
863                raise ValueError("""\
864Can only retrieve a single result_template_type at a time"""
865)
866            result_type = result_type_set.pop()
867            res_list = self.results_list(result_type, bufver=bufver)
868            sort_keys = [(int(res_list[id]["data_id"]),
869                          int(res_list[id]["mappings_id"]))\
870                         for id in ids]
871            ids_sort = argsort(sort_keys)
872
873        # Sort the ids for use by pipax api
874        ids_sorted = [ids[i] for i in ids_sort]
875
876        read = {}
877        for a, b in res_list.items():
878            read[a] = b.items()
879
880        cbc.end()
881
882        #newer reload chip results
883        download_func = lambda x: self.download(x, bufver=bufver)
884
885        cbc = CallBack(len(ids_sorted) + 3, optcb,
886                       callbacks=99 - 20)
887        et = self.exampleTables(ids_sorted, spotmap={}, callback=cbc,
888                            annots=read,
889                            exclude_constant_labels=exclude_constant_labels,
890                            chipfn=download_func,
891                            allowed_labels=allowed_labels)
892        cbc.end()
893
894        # Restore input ids order.
895        domain = orange.Domain([et.domain[id] for id in ids], None)
896        domain.addmetas(et.domain.getmetas())
897        et = orange.ExampleTable(domain, et)
898
899        cbc = CallBack(2, optcb, callbacks=10)
900
901        #transformation is performed prior to averaging
902        if transform != None:
903            transformValues(et, fn=transform)  # in place transform
904            cbc()
905
906        #if average function is given, use it to join same spotids
907        if average != None:
908            et = averageAttributes(et, fn=average)
909            cbc()
910
911        cbc.end()
912
913        return et
914
915    def download(self, result_ids, reload=False, bufver="0"):
916        """result_ids must be sorted (by `(data_id, mappings_id)`)
917        """
918        data = {"action": "download", "ids": "$MULTI$"}
919        data = self.add_auth(data)
920        antss = self.downloadMulti("", result_ids, data=data, chunk=10,
921                                   bufferkey=bufferkeypipax, bufreload=reload,
922                                   bufver=bufver)
923        for a, legend in antss:
924            yield a
925
926    def downloadMulti(self, command, ids, data=None, chunk=100,
927                      transformfn=None, bufferkey=None, separatefn=None,
928                      bufreload=False, bufver="0"):
929        """
930        Downloads multiple results at once.
931        Results in the same order as in ids.
932
933        Bufferkey transforms command and data into buffer key.
934        bufver is a function returning buffer version for a given id. if
935            a string is given, use it for all ids
936        """
937
938        sids = split(ids, chunk)
939
940        bufverfn = None
941        if isinstance(bufver, basestring):
942            bufverfn = lambda x: bufver
943        else:
944            bufverfn = bufver
945
946        bufcommand, replace_multi = \
947                self.downloadMulti_bufcommand_replace_multi(command,
948                                data=data, chunk=chunk, bufferkey=bufferkey,
949                                transformfn=transformfn
950                                )
951
952        for i, sidp in enumerate(sids):
953
954            buffered = []
955            unbuffered = []
956
957            for a in sidp:
958                if self.inBuffer(bufcommand(a)) == bufverfn(a) and \
959                        bufreload == False:
960                    buffered.append(a)
961                else:
962                    unbuffered.append(a)
963
964            res = []
965            legend = []
966
967            if len(unbuffered) > 0:
968                com1, d1 = replace_multi(command, data, ",".join(unbuffered))
969                # get unbuffered part
970                res, legend = self.sq(com1, data=d1, buffer=False)
971            else:
972                # get legend from buffer also
973                legend = self.fromBuffer(bufcommand(buffered[0]))[0]
974
975            legend = ["gene_id", "value"]
976            genes = nth(res, 0)
977
978            antss = {}
979            for i, cid in enumerate(unbuffered):
980                col = i + 1
981                vals = nth(res, col)
982                antss[cid] = [[a, b] for a, b in zip(genes, vals) if b != "?"]
983
984            #here save buffer
985            for a, b in antss.items():
986                self.toBuffer(bufcommand(a), [legend] + b, bufverfn(a),
987                              autocommit=False)
988            self.bufferCommit()
989
990            #get buffered from the buffer
991            antssb = dict([(b, self.fromBuffer(bufcommand(b))[1:]) \
992                           for b in buffered])
993            antss.update(antssb)
994
995            #put results in order
996            for ci in sidp:
997                yield antss[ci], legend
998
999
1000class DictyExpress(DBCommon):
1001    """
1002    Type is object id
1003    """
1004   
1005    aoidPairs = txt2ll("""time extractions.developmental_time_point
1006sample biological_samples.sample
1007growthCond biological_samples.growth_condition
1008treatment biological_samples.treatment
1009replicate biological_sample_replicates.replicate
1010techReplicate chips.replicate
1011platform chips.chip_platform
1012isTimeSeries biological_sample_replicates.is_time_series""")
1013
1014    obidPairs = txt2ll("""norms normalizations
1015samples biological_samples
1016replicates biological_sample_replicates
1017analysis analysis
1018experiments experiments
1019extractions extractions
1020chips chips""")
1021
1022    def __init__(self, address=defaddress, buffer=None):
1023        self.address = address
1024        self.db = DBInterface(address)
1025        self.buffer = buffer
1026        self.preload()
1027
1028    def preload(self):
1029
1030        # aoids are mappings from annotation name to annotation id
1031        self.aoids = ll2dic(self.__annotationTypes(), 1, 0)
1032        self.saoids = ll2dic(self.aoidPairs, 0, 1)
1033        self.aoidsr = reverseDic(self.aoids)
1034        self.saoidsr = reverseDic(self.saoids)
1035
1036        # obids are mappings from object id to annotation id
1037        self.obids = ll2dic(self.__objects(), 1, 0)
1038        self.sobids = ll2dic(self.obidPairs, 0, 1)
1039        self.obidsr = reverseDic(self.obids)
1040        self.sobidsr = reverseDic(self.sobids)
1041
1042    def aoidt(self, s):
1043        return chainLookup(s, [self.saoids, self.aoids], force=[-1])
1044
1045    def obidt(self, s):
1046        return chainLookup(s, [self.sobids, self.obids], force=[-1])
1047 
1048    def aoidtr(self, s, **kwargs):
1049        return chainLookup(s, [self.aoidsr, self.saoidsr], **kwargs)
1050
1051    def obidtr(self, s):
1052        return chainLookup(s, [self.obidsr, self.sobidsr])
1053
1054    def pq(self, q):
1055        """
1056        Prepare query.
1057        ||| separator between conditions,
1058        *** denotes equivalence
1059        """
1060        o =  "|||".join([ self.aoidt(a) + "***" + b for a,b in q.items()])
1061        return o
1062
1063    def geneInfo(self):
1064        res,legend = self.sq("action=gene_info")
1065        return res, legend
1066
1067    def annotationOptions(self, ao=None, onlyDiff=False, **kwargs):
1068        """
1069        Returns annotation options for given query. Returns all possible
1070        annotations if the query is omitted.
1071
1072        If ao is choosen, only result
1073        """
1074        params = ""
1075        if len(kwargs) > 0: params += "&query=" + self.pq(kwargs)
1076        if ao: params += "&annotation_object_id=" +  self.aoidt(ao)
1077        res,legend = self.sq("action=get_annotation_options%s" % (params), bufadd=self.address)
1078        res = onlyColumns(res, legend, ['annotation_object_id', 'value' ])
1079
1080        #join columns with the annotation object id
1081        joined = {}
1082        for key,v in res:
1083            key = self.aoidtr(key)
1084            cur = joined.get(key, [])
1085            cur.append(v)
1086            joined[key] = cur
1087
1088        if onlyDiff:
1089            joined = dict([ (a,b) for a,b in joined.items() if len(b)>1 ])
1090
1091        return dict([ (a, sorted(b)) for a,b in joined.items() ])
1092
1093    def annotation(self, type, id):
1094        return list(self.annotations(type, [ id ]))[0]
1095
1096    def meaningfulAnnot(self, name):
1097        if name in self.saoids:
1098            return True
1099        else:
1100            return False
1101
1102    def keepOnlyMeaningful(self, annot):
1103        """
1104        Keep only meaningful annotations
1105        """
1106        if type(annot) == type({}):
1107            return dict( [ (a,b) for a,b in annot.items() \
1108                if self.meaningfulAnnot(a) ] )
1109        else:
1110            return [ [ a,b ] for a,b in annot \
1111                if self.meaningfulAnnot(a) ]
1112
1113
1114    def annotations(self, type, ids=None, all=False):
1115        """
1116        Returns a generator returning annotations for specified type and ids.
1117        If ids are left blank, all annotations are outputed. Annotations are in the same order
1118        as input ids.
1119        If all is True, all annotations are kept, else keep only "meaningful".
1120        """
1121       
1122        inputids = False
1123        if ids != None:
1124            inputids = True
1125            antss = self.downloadMulti(
1126                "action=get_annotations&ids=$MULTI$&object_id=%s" 
1127                % (self.obidt(type)), ids)
1128        else:
1129            res,legend = self.sq(
1130                "action=get_annotations&object_id=%s"
1131                % (self.obidt(type)))
1132            antss = splitTableOnColumn(res, 0)
1133            ids = nth(antss.items(),0)
1134            antss = zip(nth(antss.items(),1), [ legend ]*len(antss))
1135
1136        for ants in izip(antss,ids):
1137            (res, legend), id = ants
1138            res2 = onlyColumns(res, legend, ['name', 'value'])
1139            res2 = [ [ self.aoidtr(a),b ] for a,b in res2 ]
1140            if not all:
1141                res2 = self.keepOnlyMeaningful(res2)
1142            if inputids:
1143                yield res2
1144            else:
1145                yield (id, res2)
1146
1147    def search(self, type, **kwargs):
1148        """
1149        Break search for multiple values of one attribute to independant searches.
1150        Search is case insensitive.
1151       
1152        List of searchable annotation types: self.saoids.keys()
1153
1154        example usage:
1155        search("norms", platform='minichip', sample='abcC3-')
1156            finds all ids of normalized entries where platform is minchip and
1157            sample is abcC3-
1158        search("norms", platform='minichip', sample=[ 'abcC3-', 'abcG15-'])
1159            finds all ids of normalized entries where platform is minichip and
1160            sample is abcC3- or those where platform is minichip and sample
1161            is abcG15-
1162        """
1163       
1164        #search for all combinations of values - this is slow!
1165
1166        l = []
1167        for k,v in kwargs.items():
1168            if not issequencens(v):
1169                v = [ v ]
1170            l.append((k,v))
1171
1172        ares = []
1173
1174        for r in mxrange([ len(v) for k,v in l ]):
1175            dico = {}
1176            for i,a in enumerate(r):
1177                dico[l[i][0]] = l[i][1][a]
1178
1179            res,_ = self.sq("action=search&object_id=%s&query=%s" \
1180                % (self.obidt(type), self.pq(dico)), bufadd=self.address)
1181
1182            ares += res
1183
1184        return sorted(set(nth(ares, 0)))
1185
1186
1187    def chipN(self, id):
1188        return list(self.chipNs([id]))[0]
1189
1190    def chipR(self, id):
1191        return list(self.chipRs([id]))[0]
1192 
1193    def chipNs(self, ids, remove_bad=True):
1194        """
1195        If removebad = True removes those with weights 0.
1196        """
1197         
1198        def sel(res, legend):
1199            #Drop unwanted columns - for efficiency
1200            res = onlyColumns(res, legend, ["spot_id", 'M', 'weights'])
1201            legend = onlyColumns( [ legend ], legend, ["spot_id", 'M', 'weights'])[0]
1202
1203            def transf(a):
1204                if a[2] == 0:
1205                    return a[0], '', a[2]
1206                else:
1207                    return a[0], a[1], a[2]
1208
1209            res = [ transf(a) for a in res ]
1210            res = onlyColumns(res, legend, ["spot_id", 'M'])
1211            legend = onlyColumns( [ legend ], legend, ["spot_id", 'M'])[0]
1212            return res, legend
1213
1214        antss = self.downloadMulti("action=get_normalized_data&ids=$MULTI$", ids, chunk=2, transformfn=sel)
1215        for a,legend in antss:
1216            yield a   
1217
1218    def chipNsN(self, ids, annots):
1219        """
1220        Download chips using new shorter format.
1221        """
1222        chip_map_ids = zip(ids,[ dict(a)['chips.chip_map_id'] for a in annots ])
1223
1224        def separateByChipsMaps(l):
1225            begin = 0
1226            cm = l[0][1]
1227            cp = 0
1228            for id,m in l[1:]:
1229                cp += 1
1230                if m != cm:
1231                    yield l[begin:cp]
1232                    cm = m
1233                    begin = cp
1234            yield l[begin:cp+1]
1235       
1236        sep = list(separateByChipsMaps(chip_map_ids))
1237     
1238        def sel(res, legend):
1239            #Drop unwanted columns - for efficiency
1240            res = onlyColumns(res, legend, ["spot_id", 'M'])
1241            legend = onlyColumns( [ legend ], legend, ["spot_id", 'M'])[0]
1242            return res, legend
1243
1244        def separatefn(res):
1245            #each one is own rown
1246            #genes are in the first row
1247            genes = res[0][1:]
1248            cids = nth(res,0)[1:]
1249
1250            antss = {}
1251            for i,cid in enumerate(cids):
1252                row = i+1
1253                vals = res[row][1:]
1254                antss[cid] = [ list(a) for a in zip(genes, vals) ]
1255            return ['spot_id', 'M'], antss
1256
1257        for part in sep:
1258            pids = nth(part,0)
1259            antss = self.downloadMulti("action=get_normalized_data&mergeexperiments=1&ids=$MULTI$", pids, chunk=10, transformfn=sel, separatefn=separatefn)
1260            for a, legend in antss:
1261                yield a
1262
1263    def chipRs(self, id):
1264        antss = self.downloadMulti("action=get_raw_data&ids=$MULTI$", ids, chunk=2)
1265        for a,legend in antss:
1266            yield a
1267 
1268    def spotId(self):
1269        res,legend = self.sq("action=spot_id_mapping")
1270        res2 = onlyColumns(res, legend, ["spot_id", 'ddb_g', 'genename'])
1271        return res2
1272
1273    def annotationTypes(self):
1274        """
1275        Returns all annotation types.
1276        """
1277        return self.aoids.keys()
1278
1279    def objects(self):
1280        """
1281        Returns all objects.
1282        """
1283        return self.obids.keys()
1284
1285    def __annotationTypes(self):
1286        """
1287        Returns list of [ annotation_object_id, name ]
1288        """
1289        res, legend = self.sq("action=get_annotation_types")
1290        res2 = onlyColumns(res, legend, ["annotation_object_id", 'name'])
1291        return res2
1292
1293    def __objects(self):
1294        res, legend = self.sq("action=get_objects")
1295        res2 = onlyColumns(res, legend, ["object_id", 'object_name'])
1296        return res2
1297
1298    def spotMap(self):
1299        spotids = self.spotId()
1300        spotmap = [ (a[0],a[1]) for a in spotids ]
1301
1302        spotmapd = {}
1303
1304        for a,b in spotmap:
1305            if a in spotmapd:
1306                spotmapd[a] = spotmapd[a] + "-" + b
1307            else:
1308                spotmapd[a] = b
1309
1310        return spotmapd
1311
1312    def getData(self, *args, **kwargs):
1313        deprecatedError("Use get_single_data instead")
1314
1315    def get_data(self, type="norms", exclude_constant_labels=False, average=median, 
1316        ids=None, callback=None, format="short", transform=None, allowed_labels=None, **kwargs):
1317        """
1318        Get data in a single example table with labels of individual attributes
1319        set to annotations for query and post-processing
1320        instructions.
1321
1322        Parameters:
1323            average: function used for combining multiple reading of the same spot on
1324                a chip. If None, no averaging is done. Fuction should take a list
1325                of floats and return an "averaged" float.
1326            ids: a list of chip ids. If absent, make a search
1327            exclude_constant_labels: if a label has the same value in whole
1328                example table, remove it
1329            format: if short, use short format for chip download
1330
1331        Defaults: Median averaging.
1332        """
1333
1334        def optcb():
1335            if callback: callback()
1336
1337        cbc = CallBack(1, optcb, callbacks=10)
1338
1339        if not ids:
1340            #returns ids of elements that match the search function
1341            ids = self.search(type, **kwargs)
1342
1343        cbc.end()
1344
1345        #downloads annotations
1346        cbc = CallBack(len(ids), optcb, callbacks=10)
1347
1348        readall = self.dictionarize(ids, self.annotations, type, ids, all=True, callback=cbc)
1349
1350        read = {}
1351        for a,b in readall.items():
1352            read[a] = self.keepOnlyMeaningful(b)
1353
1354        annotsinlist = [] #annotations in the same order
1355        for id in ids:
1356            annotsinlist.append(readall[id])
1357
1358        if verbose:
1359            print zip(ids,[ dict(a)['chips.chip_map_id'] for a in annotsinlist ])
1360
1361        cbc.end()
1362
1363        #till now downloads were small
1364
1365        import time
1366        tstart = time.time()
1367
1368        #here download actually happens
1369        chipfn = None
1370
1371        if type == "norms":
1372            if format == "short":
1373                chipfn = lambda x, al=annotsinlist: self.chipNsN(x, al)
1374            else:
1375                chipfn = self.chipNs
1376        else:
1377            chipfn = self.chipRs
1378       
1379        if verbose:
1380            print "DOWNLOAD TIME", time.time() - tstart
1381
1382        cbc = CallBack(len(ids)*2+len(ids)+1, optcb, callbacks=999-30)
1383        et = self.exampleTables(ids, spotmap=self.spotMap(), callback=cbc, annots=read, exclude_constant_labels=exclude_constant_labels, chipfn=chipfn, allowed_labels=allowed_labels)
1384        cbc.end()
1385
1386        cbc = CallBack(1, optcb, callbacks=10)
1387
1388        #transformation is performed prior to averaging
1389        if transform != None:
1390            transformValues(et, fn=transform) #in place transform
1391            cbc()
1392
1393        #if average function is given, use it to join same spotids
1394        if average != None:
1395            et = averageAttributes(et, fn=average)
1396            cbc()
1397
1398        cbc.end()
1399
1400        return et
1401   
1402    def get_single_data(self, *args, **kwargs):
1403        return self.get_data(*args, **kwargs)
1404
1405class DatabaseConnection(DictyExpress):
1406    pass
1407
1408def allAnnotationVals(annots):
1409    """
1410    All annotation valuess for given annotations
1411    in a dict of { name: set of possible values } pairs.
1412    """
1413    av = defaultdict(set)
1414    for a in annots:
1415        for name,val in a:
1416            av[name].add(val)
1417    return av
1418
1419def createExampleTable(names, vals, annots, ddb, cname="DDB", \
1420        exclude_constant_labels=False, permutation=None, always_include=["id"], allowed_labels=None):
1421    """
1422    Create an ExampleTable for this group. Attributes are those in
1423    names.
1424    """
1425    attributes = [ orange.FloatVariable(n, numberOfDecimals=3) \
1426        for n in names ]
1427
1428    #exclusion of names with constant values
1429    annotsvals = allAnnotationVals(annots)
1430    oknames = set(annotsvals.keys())
1431    if exclude_constant_labels:
1432        oknames = set(nth(filter(lambda x: len(x[1]) > 1 or x[0] in always_include, 
1433            annotsvals.items()), 0))
1434
1435    if allowed_labels != None:
1436        oknames = set(filter(lambda x: x in allowed_labels, oknames))
1437
1438    #print oknames
1439
1440    for a,an in zip(attributes, annots):
1441        a.setattr("attributes", dict([(name,str(val)) for name,val in an if name in oknames]))
1442
1443    domain = orange.Domain(attributes, False)
1444    ddbv = orange.StringVariable(cname)
1445    id = orange.newmetaid()
1446    domain.addmeta(id, ddbv)
1447
1448    examples = [ None ]*len(ddb)
1449    for i,(v,d) in enumerate(izip(izip(*vals), ddb)):
1450        ex = orange.Example(domain, [ floatOrUnknown(a) for a in v ])
1451        ex[cname] = d
1452
1453        if permutation:
1454            examples[permutation[i]] = ex
1455        else:
1456            examples[i] = ex
1457
1458    return orange.ExampleTable(domain,examples)
1459
1460def transformValues(data, fn):
1461    """
1462    In place transformation.
1463    """
1464    for ex in data:
1465        for at in data.domain.attributes:
1466            if not ex[at].isSpecial():
1467                ex[at] = fn(ex[at])
1468
1469def averageAttributes(data, joinc="DDB", fn=median):
1470    """
1471    Averages attributes with the same "join" parameter using
1472    specified function. Builds a now dataset. Order of
1473    "join" parameter stays the same with regard to first
1474    appearance.
1475    """
1476
1477    if verbose:
1478        print "Averaging attributes"
1479
1480    valueso = []
1481    valuess = set(valueso)
1482
1483    attributes = [ a for a in data.domain.attributes ]
1484    domain = data.domain
1485   
1486    #accumulate values
1487
1488    for ex in data:
1489        j = str(ex[joinc])
1490
1491        if j not in valuess:
1492            valueso.append(j)
1493            valuess.add(j)
1494
1495    valuesddb = [ dict( (n,[]) for n in valueso) for at in attributes ]
1496   
1497    for ex in data:
1498        j = str(ex[joinc])
1499
1500        for a in range(len(attributes)):
1501            val = ex[a]
1502            if not val.isSpecial():
1503                valuesddb[a][j].append(val.native())
1504
1505    #create a new example table reusing the domain - apply fn
1506    examples = []
1507    for v in valueso:
1508        example = orange.Example(domain, \
1509            [ floatOrUnknown(fn(valuesddb[a][v])) for a in range(len(attributes)) ] )
1510        example[joinc] = v
1511        examples.append(example)
1512
1513    return orange.ExampleTable(domain, examples)
1514
1515def floatOrUnknown(a):
1516    """
1517    Converts an element to float if possible.
1518    If now, output "?".
1519    """
1520    try:
1521        return float(a)
1522    except:
1523        return "?"
1524
1525class CallBack():
1526    """
1527    Converts "allparts" callbacks into by "callbacks"
1528    specified number of callbacks of function fn.
1529    """
1530
1531    def __init__(self, allparts, fn, callbacks=100):
1532        self.allparts = allparts
1533        self.lastreport = 0.00001
1534        self.getparts = 0
1535        self.increase = 1.0/callbacks
1536        self.callbacks = callbacks
1537        self.fn = fn
1538        self.cbs = 0
1539
1540    def __call__(self):
1541        self.getparts += 1
1542        done = float(self.getparts)/self.allparts
1543        while done > self.lastreport + self.increase:
1544            self.lastreport += self.increase
1545            self.fn()
1546            self.cbs += 1
1547
1548    def end(self):
1549        while self.cbs < self.callbacks:
1550            self.fn()
1551            self.cbs += 1
1552
1553class BufferSQLite(object):
1554
1555    def __init__(self, filename, compress=True):
1556        self.compress = compress
1557        self.filename = filename
1558        self.conn = self.connect()
1559
1560    def clear(self):
1561        """
1562        Removes all entries in the buffer
1563        """
1564        self.conn.close()
1565        os.remove(self.filename)
1566        self.conn = self.connect()
1567
1568    def connect(self):
1569        import sqlite3
1570        conn = sqlite3.connect(self.filename)
1571        c = conn.cursor()
1572        c.execute('''create table if not exists buf
1573        (address text primary key, time text, con blob)''')
1574        c.close()
1575        conn.commit()
1576        return conn
1577
1578    def contains(self, addr):
1579        """ Returns version or False, if it does not exists """
1580        c = self.conn.cursor()
1581        c.execute('select time from buf where address=?', (addr,))
1582        lc = list(c)
1583        c.close()
1584        if len(lc) == 0:
1585            return False
1586        else:
1587            return lc[0][0]
1588
1589    def list(self):
1590        c = self.conn.cursor()
1591        c.execute('select address from buf')
1592        return nth(list(c), 0)
1593
1594    def add(self, addr, con, version="0", autocommit=True):
1595        import cPickle, zlib, sqlite3
1596        if verbose:
1597            print "Adding", addr
1598        c = self.conn.cursor()
1599        if self.compress:
1600            bin = sqlite3.Binary(zlib.compress(cPickle.dumps(con)))
1601        else:
1602            bin = sqlite3.Binary(cPickle.dumps(con))
1603        c.execute('insert or replace into buf values (?,?,?)', (addr, version, bin))
1604        c.close()
1605        if autocommit:
1606            self.commit()
1607
1608    def commit(self):
1609        self.conn.commit()
1610
1611    def get(self, addr):
1612        import cPickle, zlib
1613        if verbose:
1614            print "getting from buffer", addr
1615            t = time.time()
1616        c = self.conn.cursor()
1617        c.execute('select con from buf where address=?', (addr,))
1618        ls = list(c)
1619        first = ls[0][0]
1620        if verbose:
1621            print time.time() - t
1622        if self.compress:
1623            rc = cPickle.loads(zlib.decompress(first))
1624        else:
1625            rc = cPickle.loads(str(first))
1626        c.close()
1627
1628        if verbose:
1629            print time.time() - t
1630        return rc
1631
1632def download_url(url, repeat=2):
1633    def do():
1634        return urllib2.urlopen(url)
1635
1636    if repeat <= 0:
1637        do()
1638    else:
1639        try:
1640            return do()
1641        except:
1642            return download_url(url, repeat=repeat-1)
1643
1644def empty_none(s):
1645    if s:
1646        return s
1647    else:
1648        return None
1649
1650def join_ats(atts):
1651    """ Joins attribute attributes together. If all values are the same,
1652    set the parameter to the common value, else return a list of the
1653    values in the same order as the attributes are imputed. """
1654    keys = reduce(lambda x,y: x | y, (set(at.keys()) for at in atts))
1655    od = {}
1656
1657    def man(x):
1658        if issequencens(x):
1659            return tuple(x)
1660        else:
1661            return x
1662
1663    for k in keys:
1664        s = set(man(at[k]) for at in atts)
1665        if len(s) == 1:
1666            od[k] = list(s)[0]
1667        else:
1668            od[k] = str([ at[k] for at in atts ])
1669    return od
1670
1671def join_replicates(data, ignorenames=["replicate", "id", "name", "map_stop1"], namefn=None, avg=median):
1672    """ Join replicates by median.
1673    Default parameters work for PIPA data.
1674    Sort elements in the same order as ignorenames!
1675    """
1676
1677    d = defaultdict(list)
1678
1679    if namefn == None:
1680        namefn = lambda att: ",".join(att["id"]) if issequencens(att["id"]) else att["id"]
1681
1682    #key function
1683    def key_g(att):
1684        dk = att.copy()
1685        for iname in ignorenames:
1686            dk.pop(iname, None)
1687       
1688        def man(x):
1689            if issequencens(x):
1690                return tuple(x)
1691            else:
1692                return x
1693
1694        return tuple(nth(sorted(((a, man(b)) for a,b in dk.items())), 1))
1695
1696    #prepare groups
1697    for i,a in enumerate(data.domain.attributes):
1698        att = a.attributes
1699        k = key_g(att)
1700        d[k].append(i)
1701
1702    d = dict(d) #want errors with wrong keys
1703
1704    natts = []
1705
1706    def nativeOrNone(val):
1707        if val.isSpecial(): 
1708            return None
1709        else: 
1710            return val.native()
1711
1712    def avgnone(l):
1713        """ Removes None and run avg function"""
1714        l = filter(lambda x: x != None, l)
1715        if len(l):
1716            return avg(l)
1717        else:
1718            return None
1719
1720    def data_type(vals): #data type converter
1721        try:
1722            _ = [ int(a) for a in vals ]
1723            return int
1724        except:
1725            try:
1726                _ = [ float(a) for a in vals ]
1727                return float
1728            except:
1729                return lambda x: x
1730
1731    all_values = defaultdict(set)
1732    for a in [ at.attributes for at in data.domain.attributes ]:
1733        for k,v in a.iteritems():
1734            all_values[k].add(v)
1735
1736    types = {}
1737    for k,vals in all_values.iteritems():
1738        types[k] = data_type(vals)
1739
1740    for group, elements in d.items():
1741        a = orange.FloatVariable()
1742        #here sort elements
1743   
1744        def sk(x):
1745            return [ types[n](x[n]) for n in ignorenames if n in all_values ]
1746
1747        elements = sorted(elements, key=lambda x: sk(data.domain.attributes[x].attributes))
1748
1749        a.attributes.update(join_ats([data.domain.attributes[i].attributes for i in elements]))
1750        a.name = namefn(a.attributes)
1751
1752        def avgel(ex, el):
1753            return orange.Value(avgnone([ nativeOrNone(ex[ind]) for ind in el ]))
1754
1755        a.getValueFrom = lambda ex,rw,el=elements: avgel(ex,el)
1756        natts.append(a)
1757
1758    ndom = orange.Domain(natts, data.domain.classVar)
1759    ndom.addmetas(data.domain.getmetas())
1760    return orange.ExampleTable(ndom, data)
1761
1762
1763class DictyBase(object):
1764
1765    domain = "dictybase"
1766    filename = "information_mappings.pck"
1767    tags = [ "Dictyostelium discoideum", "gene", "essential", "dictyBase" ] 
1768 
1769    @classmethod
1770    def version(cls):
1771        orngServerFiles.localpath_download(cls.domain, cls.filename)
1772        return orngServerFiles.info(cls.domain, cls.filename)["datetime"]
1773   
1774    @classmethod
1775    def download_information(cls):
1776        """
1777        Downloads gene information and parses it.
1778        Returns a dictionary {ID: (name, synonyms, products)}
1779        """
1780        s = download_url("http://www.dictybase.org/db/cgi-bin/dictyBase/download/download.pl?area=general&ID=gene_information.txt").read()
1781        out = []
1782        for l in txt2ll(s, separ='\t', lineSepar='\n')[1:]:
1783            if len(l) == 4:
1784                id = l[0]
1785                name = l[1]
1786                synonyms = filter(None, l[2].split(", "))
1787                products = l[3]
1788                out.append((id, name, synonyms, products))
1789        return dict((a,(b,c,d)) for a,b,c,d in out)
1790
1791    @classmethod
1792    def download_mappings(cls):
1793        """
1794        Downloads DDB-GeneID-UniProt mappings and parses them.
1795        Returns a list of (ddb, ddb_g, uniprot) triplets.
1796       
1797        2009/04/07: ddb's appear unique
1798        """
1799        s = download_url("http://www.dictybase.org/db/cgi-bin/dictyBase/download/download.pl?area=general&ID=DDB-GeneID-UniProt.txt").read()
1800        out = []
1801        for l in txt2ll(s, separ='\t', lineSepar='\n')[1:]:
1802            if len(l) == 4:
1803                ddb = empty_none(l[0])
1804                ddb_g = empty_none(l[1])
1805                name = empty_none(l[2])
1806                uniprot = empty_none(l[3])
1807                out.append((ddb, ddb_g, name, uniprot))
1808        return out
1809
1810    @classmethod
1811    def pickle_data(cls):
1812        info = cls.download_information()
1813        mappings = cls.download_mappings()
1814        return pickle.dumps((info,mappings), -1)
1815
1816    def __init__(self):
1817        fn = orngServerFiles.localpath_download(self.domain, self.filename)
1818        self.info, self.mappings = pickle.load(open(fn, 'rb'))
1819
1820if __name__=="__main__":
1821    verbose = 1
1822
1823    def printet(et):
1824        et.save("ett.tab")
1825        print open("ett.tab").read()
1826
1827    """
1828    a = DictyBase()
1829    print len(a.info)
1830
1831    dbc = DictyExpress(buffer=BufferSQLite("../tmpbufnew"))
1832
1833    print dbc.annotationOptions()
1834
1835    count = 0
1836    def cb():
1837        global count
1838        count += 1
1839        #print "CBBB", count
1840
1841    et = dbc.get_single_data(sample=[ "tagA-", "pkaC-"], callback=cb, exclude_constant_labels=True, allowed_labels=["sample"])
1842    print et.domain
1843    print et.domain[0].attributes
1844    printet(et)
1845    """
1846
1847    d = PIPA(buffer=BufferSQLite("../tmpbufnewpipa"))
1848
1849    print d.gene_expression_types()
1850
1851    cov = d.coverage("777", chromosome=1, genome="dd")
1852    print cov
1853
1854    allids = d.annotations().keys()
1855    allids = allids[:1]
1856    print ("list", allids)
1857    print d.annotations().items()[0]
1858    print ("annots", d.annotations().items()[:2])
1859
1860    allids = map(str, allids)
1861
1862    import math
1863
1864    data = d.get_data(ids=allids, ctype="3")
1865
1866    #data = d.get_data(ids=allids, transform=lambda x: math.log(x+1, 2), allowed_labels=["strain"], ctype="3")
1867
1868    printet(data)
Note: See TracBrowser for help on using the repository browser.