1
2 '''
3 B{Console User Interface Views}
4 all views on a SBML file relevant for semantiSBML
5 the views also contain the connection to the cotrollers that modifiy a SBML file
6 '''
7 import sys, os, re, copy
8 from textwrap import fill
9 if not hasattr(__builtins__, 'set'):
10 from sets import Set as set
11 from PyQt4 import QtCore,QtGui
12
13 FALSE=0
14 TRUE=1
15 from libSBAnnotation.config import *
16 from semanticSBML.kegg2sbml import kegg2sbml,KEGG2sbmlError
17 import libsbml
18 from semanticSBML.merge import Merger
19 from CustomInteractiveConsole import CustomConsole
20
21 from semanticSBML.annotate import StAnError
22
24 '''
25 merge 2 or more sbml models
26 in 2 steps:
27 - resolve conflict
28 - resolve circular rule definitions
29 '''
31 '''
32 get instances of: the database, the merge class
33 create tab and initialize the inherited class
34 '''
35 self._parent=parent
36
37
38
39
40
41
42
43
44 self.desc2choice={}
45 self.collision=None
46
47 QtCore.QObject.__init__(self)
48 Base_Merge_view.__init__(self,documents)
49 self.no_more_collisions=False
50 self.no_more_circles=False
51
53 '''
54 start the commandloop
55
56 this means call L{makeCollisionMenu} if a flag is set continue on for the
57 '''
58 self.makeCollisionMenu()
59 if self.no_more_collisions:
60 print 'no conflicts move on :)'
61 self.circleMenu(self._merger.find_circle(self.dotfilename))
62 if self.no_more_circles:
63 newmodel=self.finish()
64
65
66
67 self.emit(QtCore.SIGNAL("sigDocumentCreated"),newmodel)
68
70 '''
71 build the collision menu for two colliding sbml elemntens, display compare and choose new values
72 table 4*n (n=number of element values)
73 - first row: title of columns
74 - first column:description of values
75 - second column: name or id
76 '''
77 self.collision=self._merger.find_collision()
78 if not self.collision:
79 self.no_more_collisions=True
80
81 return True
82 (elmA,elmB,original_contentlist)=self.collision
83 contentlist=copy.copy(original_contentlist)
84
85
86 contentlist.pop('MetaId')
87 try:
88 contentlist.pop('ListOfProducts')
89 contentlist.pop('ListOfReactants')
90 except:
91 pass
92
93 print '%-22s%-30s%-16s%-30s'%('Description','Value A','Conflict','Value B')
94
95 for tkey in ['Name','Id']:
96 if contentlist.has_key(tkey):
97 (a,b,tf)=contentlist.pop(tkey)
98 def sh(instr,maxlen):
99 if len(instr)>maxlen: return instr[0:maxlen-4]+'...'
100 else: return instr
101 print '%-22s%-30s%-16s%-30s'%(tkey,sh(a,30),'',sh(b,30))
102 break
103
104 self.desc2combo=[]
105 self.combovalue={}
106 self.combo2value={}
107
108 sortedContentList=[]
109 for n in ['Id','Annotation','Compartment','InitialAmount','InitialConcentration','BoundaryCondition','Constant']:
110 if contentlist.has_key(n):
111 sortedContentList.append((n,contentlist.pop(n)))
112 sortedContentList +=sorted(contentlist.items())
113 ccount=0
114 for i,(desc,(valA,valB,conflicting)) in enumerate(sortedContentList):
115 if (valA or valB) and not (desc.find('Num') == 0 or desc=='Formula'):
116 strA=self.elemval2str(elmA,desc,valA)
117 strB=self.elemval2str(elmB,desc,valB)
118 if desc == 'Annotation':
119 conflicting=0
120 def an2str(an):
121 noa=len(an.split('\n'))-1
122 an=an.split('\n')[0]
123 if noa:
124 an+= '('+str(noa)+' further annotations)'
125 return an
126 strA=an2str(strA)
127 strB=an2str(strB)
128 conflictstr=''
129 if conflicting:
130 if desc == 'KineticLaw':
131 choose='choose'
132 if self.desc2choice.has_key(desc):
133 choose=self.desc2choice[desc]
134 self.desc2combo.append((desc,self.desc2choice[desc]))
135 else:
136 self.desc2combo.append((desc,self.elemval2str(elmA,desc,valA)))
137 conflictstr=str(ccount)+' ('+choose+')'
138 self.combovalue[ccount]=(desc,0,[self.elemval2str(elmA,desc,valA), self.elemval2str(elmB,desc,valB)])
139 self.combo2value[self.elemval2str(elmA,desc,valA)]=valA
140 self.combo2value[self.elemval2str(elmB,desc,valB)]=valB
141 else:
142 choose='choose/edit'
143 if self.desc2choice.has_key(desc):
144 choose=self.desc2choice[desc]
145 print choose
146 self.desc2combo.append((desc,self.desc2choice[desc]))
147 else:
148 self.desc2combo.append((desc,self.elemval2str(elmA,desc,valA)))
149 conflictstr=str(ccount)+' ('+choose+')'
150 self.combovalue[ccount]=(desc,1,[self.elemval2str(elmA,desc,valA), self.elemval2str(elmB,desc,valB)])
151 ccount+=1
152 elif desc == 'Annotation' or desc.find('ListOf') != -1 :
153 conflictstr='?'
154 else:
155 conflictstr='='
156 def sh(instr,maxlen):
157 if len(instr)>maxlen: return instr[0:maxlen-4]+'...'
158 else: return instr
159 print '%-22s%-30s%-16s%-30s'%(desc,sh(strA,30),sh(conflictstr,16),sh(strB,30))
160
161 help = '''
162 <<Merge: Resolve Conflict>>
163 l use A (left element)
164 r use B (right element)
165 m <CONFLICT_NR> resolve conflict manually
166 c use manually edited values
167 k keep both elements (this will result in an errorous SBML file)
168 '''
169 locals={
170 'l':(self.slotResolve_left,'use A'),
171 'r':(self.slotResolve_right,'use B'),
172 'm':(self.manualResolve,'resolve manually'),
173 'c':(self.slotResolve_merge,'use manual values'),
174 'k':(self.slotResolve_keep,'keep both')
175 }
176 CustomConsole(locals,help).run('...',1)
177
179 if desc:
180 self.desc2choice[desc]=CustomeConsole().raw_input('new value: ')
181 else:
182 num=int(num)
183 if self.combovalue.has_key(num):
184 (desc,editable,choicelist)=self.combovalue[num]
185 i=0
186 num2c={}
187 for c in choicelist:
188 print i,' use: '+c
189 num2c[i]=c
190 i+=1
191 if editable:
192 print i,'enter a new value'
193 rin=CustomConsole().raw_input('??: ')
194 rin=int(rin)
195 if rin==0:
196 self.desc2choice[desc]=num2c[rin]
197 elif rin==1:
198 self.desc2choice[desc]=num2c[rin]
199 elif rin==2:
200 self.manualResolve(-1,desc)
201 return self.makeCollisionMenu()
202
204 '''
205 call L{resolve_collision} for the merged values
206 '''
207 (elmA,elmB,contentlist)=self.collision
208 desc2type={}
209 for desc,(valA,valB,tf) in contentlist.iteritems():
210 desc2type[desc]=valA
211
212 resDict={}
213 for (desc,combo) in self.desc2combo:
214 if desc.find('ListOf') != -1 or desc == 'KineticLaw':
215 resDict[desc]=self.combo2value[combo]
216
217
218 elif desc == 'Math':
219 resDict[desc]=libsbml.parseFormula(combo)
220 elif isinstance(desc2type[desc], bool):
221 resDict[desc]=bool(combo)
222 elif isinstance(desc2type[desc], int):
223 resDict[desc]=int(combo)
224 elif isinstance(desc2type[desc], float):
225 resDict[desc]=float(combo)
226 else:
227 print 'FIXME NOW new type in backconversion of combobox values: ',desc2type[desc],desc2type[desc].__class__
228
229
230 self._merger.resolve_collision(elmA,elmB,resDict)
231 return self.makeCollisionMenu()
232
234 '''
235 show circular rule definitions
236 if no circle were found finish the merging process and return true
237 @param circle: output from find_circle
238 @type circle: {str rulename:alternative circle size}
239 @return: are there no more circles
240 @rtype : bool
241 '''
242 self.circle=circle
243 if self.circle:
244
245 (rules,picData)=self.circle
246 print 'Choose one of the following Rules to remove a circular Rule definition'
247 print '%-20s%-40s%s' % ('Rule','Size of Circle for Alternative Rule','Alternative')
248 i=0
249 for k,v in rules.iteritems():
250 self.uin_id2rule[i]=(k,0)
251 ktmp='['+str(i)+'] '+k
252 i+=1
253 alt='-'
254 if v:
255 alt='['+str(i)+']'
256 self.uin_id2rule[i]=(k,1)
257 i+=1
258 print '%-20s%-40s%s' % (ktmp,v or '-',alt)
259 CustomConsole({'r':(self.slot_remove_circle,'remove circle')},'r <CHOICE_NR> resolve').run('...',1)
260 else:
261 self.no_more_circles=True
262 return 1
263
264 - def close(self, destroy=0):
266
268 """create a tab that is split into a treeview and an annotation view, when elemtns in the treeveiw items are clicked the annotationview changes, the annotationview uses the sematicSBML.annotate and semanticSBML.annotationparser api, as well as SemanticSbmlGui_doc functions"""
269
271 '''get the Annotator, Callback and the database
272 print the treeview of annotations
273 start the interactive console
274 @param document: document class instance of the document that should be annotated
275 '''
276 self._document=document
277 self.cb=StAn_Callback()
278 self._document.set_callback(self.cb)
279
280
281 self.makeTreeView(0)
282
283
284
285 help='''
286 <<<Annotation Menu>>>
287 l list elements (without annotations)
288 la list elements and their annotations
289 d <ELEMENT_NUM> delete annoation
290 a <ELEMENT_NUM> add suggested annotaion/ automatically annotate "List of .." or whole Model
291 s <ELEMENT_NUM> <QUERY> search and add identifier
292 f <ELEMENT_NUM> <DB> <ID> add an identfier directly (DB: KEGG, GO ...)
293 q back'''
294 cmddir={
295 'l':(self.list,'list elemts'),
296 'la':(self.listall,'list elemts'),
297 'd':(self.delete,'delete annotation'),
298 'a':(self.add_suggestion,'add suggestion/automatic'),
299 's':(self.add_search,'add from search'),
300 'f':(self.add,'add id direcly')
301 }
302 CustomConsole(cmddir,help).run('...')
304 '''
305 wrap L{makeTreeView}
306 do not show annotations
307 '''
308 self.makeTreeView(0)
310 '''
311 wrap L{makeTreeView}
312 show annotations
313 '''
314 self.makeTreeView(1)
315
317 '''create a treeview of the sbml model by using the elementns from the Annotator
318 element nodes annotation status is marked
319 @param show_annotations: show the list incuding the annoattions
320 @type show_annotations: bool
321 '''
322
323
324
325 self.treenode2element={}
326 self.treenode2typecode={}
327 annotation_status=['!!! (annoation missing)','(annotation not supported)','','(bad annotation)']
328 num=0
329
330
331 print num,' Model '+self._document.fileName()
332 self.treenode2typecode[num]=0
333 num += 1
334
335
336 etypecode = ''
337 list_annotated=1
338 for (elmt,anoh) in self._document.getElementAnnotations():
339
340 if etypecode != elmt.getTypeCode():
341 etypecode = elmt.getTypeCode()
342 list_annotated=1
343 self.treenode2typecode[num]=etypecode
344 if etypecode == libsbml.SBML_REACTION:
345 print num," List of Reactions"
346 elif etypecode == libsbml.SBML_COMPARTMENT:
347 print num," List of Compartments"
348 elif etypecode == libsbml.SBML_SPECIES:
349 print num," List of Species"
350 num += 1
351
352 n=elmt.getName()
353 if not n:
354 if anoh:
355 (k,v)=anoh.items()[0]
356 nm=self._document.id2str(k,v) or 'id not found'
357 n='['+nm+']'
358 else:
359 n='[Unnamed Element]'
360 i=elmt.getId()
361 if i:
362 n+=' [ID:'+i+']'
363 anots=' '
364 asnum=self._document.getAnnotationStatus(elmt)
365 if not asnum:
366 anots = '*'
367 elif asnum==1:
368 anots = '.'
369 print num,'\t'+anots+n+annotation_status[asnum]
370 self.treenode2element[num]=elmt
371
372 if show_annotations:
373 if self._document.issetAnnotation(elmt):
374 for key,val in anoh.iteritems():
375
376 print '\t\t'+key+': '+val+' ',self._document.id2str(key,val) or 'id not found'
377 else:
378 if elmt.getAnnotation():
379 print '\t\tannotation not recognized'
380 else:
381 print '\t\tannotation empty'
382 num += 1
383
384
386 '''
387 invoke the stupid annotator for a list of elements
388 @param num: element number that will be mapped to the according libsbml element
389 @type num: int
390 '''
391 print '\t<Automatic annotation>\nAnnotate all elements with the first search hit found'
392 if CustomConsole().raw_input('Annotate Now? (y/n):').lower().strip()[0] == 'y':
393 for (elmt,anoh) in self._document.getElementAnnotations():
394 if self.treenode2typecode[num] == elmt.getTypeCode() or not self.treenode2typecode[num]:
395 try:
396 self._document.addStAnAnnotationLink(elmt,self.cb)
397 print self.cb.flush_buffer()
398 except StAnError, e:
399 print e
400
402 '''
403 make annotation menu with sarch results
404 @param elemnum: element number that will be mapped to the according libsbml element
405 @type elemnum: int
406 @param query: searchsting
407 @type query: string
408 '''
409 if self.treenode2element.has_key(int(elemnum)):
410 num2anot={}
411 for i,(val,(db,id)) in enumerate(self._document.getAnnotationSuggestions(self.treenode2element[int(elemnum)],query)):
412 i=str(i)
413 print i,' add'+' '+db+' '+id+' : '+' '+val
414 num2anot[i]=(self.treenode2element[int(elemnum)],db,id)
415 n=CustomConsole().raw_input('Add Annotaion Number (leave blank to cancel):')
416 if n:
417 if num2anot.has_key(n):
418 (e,d,i)=num2anot[n]
419 self.add(e,d,i)
420 else:
421 print 'no such element'
422
424 '''
425 part of the annotation menu for single elements
426 @param elemnum: element number that will be mapped to the according libsbml element
427 @type elemnum: int
428 '''
429 elemnum=int(elemnum)
430 if self.treenode2element.has_key(elemnum):
431 num2anot={}
432 for i,(val,(db,id)) in enumerate(self._document.getAnnotationSuggestions(self.treenode2element[elemnum])):
433 i=str(i)
434 print i+' add'+' '+db+' '+id+' : '+' '+val
435 num2anot[i]=(self.treenode2element[int(elemnum)],db,id)
436 n=CustomConsole().raw_input('Add Annotaion Number (leave blank to cancel):')
437 if n:
438 if num2anot.has_key(n):
439 (e,d,i)=num2anot[n]
440 self.add(e,d,i)
441 elif self.treenode2typecode.has_key(elemnum):
442 self.makeStAnMenu(elemnum)
443 else:
444 print 'no such element'
445
446 - def add(self,elm,db,id):
447 '''
448 add an annotation link or
449 print error message if annotation is invalid
450 '''
451 if isinstance(elm, str):
452 elm=self.treenode2element[int(elm)]
453 try:
454 self._document.addAnnotationLink(elm,db,id)
455 except InvalidAnnotationError,e:
456 print "Error: ",str(e)
457
459 '''list annotation (if available) delete annotation from users choice'''
460 ap = annotationparser(self.treenode2element[int(elmnum)])
461 num2anot={}
462 if ap.containsLinks():
463 for i,(db,id) in enumerate(ap.getLinks().iteritems()):
464
465 print str(i)+' delete '+db+': '+id+' ',self._document.id2str(db,id) or 'id not found'
466 num2anot[str(i)]=(self.treenode2element[int(elmnum)],db,id)
467 n=CustomConsole().raw_input('Delete Annotaion Number (leave blank to cancel):')
468
469
470 if num2anot.has_key(n):
471 (e,d,i)=num2anot[n]
472 self._document.delAnnotationLink(e,d,i)
473 else:
474 if elmt.getAnnotation():
475 print 'annotation not recognized'
476 else:
477 print 'annotation empty'
478
479
481 '''callback for the annotator'''
483 '''initialize the buffer'''
484 self.buffer=''
485 pass
486
488 '''write input to a buffer'''
489 self.buffer+=str
490 print str
491
493 '''emty the buffer and return its content'''
494 tmp=self.buffer
495 self.buffer=''
496 return tmp
497
498
500 """ Display results of the sbmlcheck function in a QListView with icons for differnet messages.
501 Results will appear in a new tab of the mainTabWidget (parent) """
502
504 '''
505 display the checkresults in a treelike list
506 - type of check
507 - information, warning, error
508 '''
509 self._document=document
510
511
512
513 for cr in self._document.check():
514 print '->'+cr.name+" ("+str(cr.errors)+"/"+str(cr.warnings)+"/"+str(cr.infos)+")"
515 for msg in cr.error_messages:
516 print '\terror: '+msg
517 for msg in cr.warning_messages:
518 print '\twarning: '+msg
519 for msg in cr.info_messages:
520 print '\tinformation:'+msg
521
522
523
525 '''contoll and view for the creation of SBML files from a list of KEGG reaction identifiers'''
527 '''
528 initialize the database
529 '''
530 QtCore.QObject.__init__(self)
531
532 self._cfg = Config()
533 self.k2sDir=self._cfg.getpath('gui','k2sDirOpenPath')
534 self.rawidlist=''
535 self.react2comp={}
536
537
541 '''
542 create menu with
543 - input box:
544 - file input
545 - text box manual input
546 - output box:
547 - choose folder button
548 - filename text field
549 - next button (slotNext())
550 '''
551
552
553 help='''
554 <<< ID -> SBML >>>
555 o <FILENAME> Open a File Containing KEGG Reaction Identifiers
556 e <ID1 ID2> Enter a List of KEGG Reaction Identifiers
557 q exit this menu'''
558
559 cc = CustomConsole({'o':(self.slotNext_f,'open file'),'e':(self.slotNext_l,'insert list'),'q':(self.exit,'exit')},help).run('...')
561 '''do nothing'''
562 pass
564 '''join args (idlist) and filter all KEGG rection IDs
565 make mext menu slotNext()'''
566 self.rawidlist='\n'.join(set(re.findall('(R\d{5})',' '.join(idlist))))
567 self.slotNext()
569 '''traverse list of fileNames and check if files are existing
570 if the exist parse all KEGG reaction IDs
571 make next menu slotNext()'''
572 self.rawidlist=''
573 self._cfg.set('gui','I2SinfileOpenPath',os.path.dirname(str(fileNames[0])))
574 self._cfg.write()
575 print 'fn..',fileNames
576 for fn in filter(os.path.exists, fileNames):
577 print 'o..',fn
578 f=open(str(fn), 'r').read()
579 f = '\n'.join(set(re.findall('(R\d{5})',f)))
580 self.rawidlist+=f
581 self.outfilename=CustomConsole().raw_input('new filename')
582 self.slotNext()
583
585 '''
586 make a list of all reaction (2 rows per reaction )
587 - first row: reaction ids and reaction in word:s
588 - second row: selected compartment for rection
589 '''
590
591 if not self.rawidlist:
592 self.makeInitMenu()
593 else:
594
595 idlist=self.rawidlist.split()
596 self.rId2cIdcomboBox={}
597 print '\nyou have inserted the following IDs'
598 print '%s\n%s\n%s'%('Reaction Number','ID and Formula','Compartment')
599 for id in idlist:
600 self.react2comp[id]='GO:0005623'
601 f=self._document.id2str_reaction(id) or 'id not found'
602 print '-----\n%s\n%s'%(id+': '+f,'GO:0005623')
603
604 help='''
605 s show current reaction list
606 p <REACT_ID> <GO_ID> choose a different compartment
607 l list compartments in local database
608 c create the SBML file
609 q go back into main ID -> SBML menu
610 '''
611 cc = CustomConsole({'q':(self.makeInitMenu,'Back'),'c':(self.slotKegg2Sbml,'Create'),'l':(self.listCompartments,'List Compatments'),'p':(self.chooseCompartment,'choose compartment'),'s':(self.chooseCompartment,'reaction list')},help).run('...',1)
612
614 '''list all avialable compartments'''
615 compartmentList=['GO:0005623 (cell)']
616 for i,id in enumerate(self._document.compartmentids):
617 print id+' ('+self._document.id2str_compartment(id)+')'
618 if i%30==0:
619 if CustomConsole().raw_input('---more--- (y/n):').lower().strip()[0]=='n':
620 break
621
623 '''choose a new compartemt for a reaction id and redisplay reactions'''
624 if self.react2comp.has_key(rid):
625 self.react2comp[rid]=cid.strip().split()[0]
626 for rid,cid in self.react2comp.iteritems():
627 f=self._document.id2str_reaction(id) or 'id not found'
628 print '-----\n%s\n%s'%(rid+': '+f,cid+' ('+self._ldb.id2str_compartment(cid)+')')
629
631 '''
632 try to create an sbml file
633 catch errors: not internet connection, output file has no xml extension for output file, wrong input, output file already exists
634 '''
635 if self.rawidlist:
636 try:
637 k2s=kegg2sbml(str(self.rawidlist).split(),self.react2comp)
638 except KEGG2sbmlError, e:
639 print 'KEGG to SBML Error',e
640 return
641 create=True
642 outfile = CustomConsole().raw_input('output file:')
643 (x,extension)=os.path.splitext(outfile)
644 if extension.lower()!='.xml':
645 outfile += '.xml'
646 if os.path.exists(outfile):
647 create=CustomConsole().raw_input('The file already exists! Overwrite? (y/n): ').lower().strip()[0]=='y'
648 if k2s.getErrors() and create:
649 create=CustomConsole().raw_input('KEGG to SBML Errors:\n'+k2s.getErrors()+'\nCreate Anyway? (y/n): ').lower().strip()[0]=='y'
650 if create:
651
652 self.emit(QtCore.SIGNAL("sigDocumentCreated"),k2s.getModel(),outfile)
653 else:
654 return
655
656
657
659 """ SBML-file
660 this is the main view on the file"""
661
663 '''
664 initialize database
665 check document
666 connect signals I{sigDocChecked} I{sigDocModified} I{sigDocAnnotate} I{sigDocActive}
667 '''
668 QtCore.QObject.__init__(self)
669 self._parent =parent
670 self._document=document
671 self._document.check()
672 self.states = ['','-','-','-']
673 self.refresh()
674 self.connect(self._document, QtCore.SIGNAL("sigDocChecked"), self.refresh)
675 self.connect(self._document, QtCore.SIGNAL("sigDocModified"), self.refresh)
676 self.connect(self._document, QtCore.SIGNAL("sigDocAnnotate"), self.refresh)
677 self.connect(self._document, QtCore.SIGNAL("sigDocAcitve"), self.refresh)
678
690
692 '''return information as string'''
693 return '%-30s%-15s%-22s%s\n' % (self._document.fileName(),self.states[1],self.states[2],self.states[3])
694
696 '''return the header line describing the columns'''
697 return '%3s %-30s%-15s%-22s%s\n' % ('!','filename','check errors','missing annotations','modified')
698
699 - def close(self, destroy=0):
700 '''do nothing'''
701 pass
702