# baubles.py: a smarter CCP4 logfile browser # Copyright (C) STFC 2007 Peter Briggs, Kevin Cowtan # # This code is distributed under the terms and conditions of the # CCP4 licence agreement as `Part 1' (Annex 2) software. # A copy of the CCP4 licence can be obtained by writing to the # CCP4 Secretary, Daresbury Laboratory, Warrington WA4 4AD, UK. # # NB: Modified from Baubles 0.0.8 by Peter Briggs Nov 2009 # # Changes: # # 1. Introduced global JAVALOGGRAPH_CODEBASE which overrides the # automatic codebase determination in javaloggraph.__init__() # Applications can set and get JAVALOGGRAPH_CODEBASE using the # setJLoggraphCodebase() and getJLoggraphCodebase() functions. # # 2. New function "baubles()" now generates the HTML output directly # from a populated smartie.logfile object. # The "baubles_html()" function wraps "baubles()" and should # behave as before i.e. accepting the name of an input logfile # and optionally an output html file. # # 3. Added the id "warnings" to the containing HTML div for # warning messages extracted from the log file, so that they # can be linked to using e.g. ... # ######################################################################## # # baubles.py # ########################################################################## # # Backend for a smarter logfile browser # using smartie # import smartie import sys import os import re import time __cvs_id__ = "$Id: baubles.py,v 1.9 2010/01/12 15:45:25 rmk65 Exp $" __version__ = "0.0.8" __diamond_version__ = "0.0.1" ############################################################ # Module globals ############################################################ JAVALOGGRAPH_CODEBASE = None # Default for Jloggraph codebase ############################################################ # HTML and Javascript generation functions ############################################################ def writeInlineStyle(html): """Write an inline stylesheet.""" html.write(""" """) return def writeJavascriptLibrary(html): """Write a block of Javascript code.""" html.write(""" """) return def writeLoggraphFolder(html,program,n): """Write HTML and Javascript for a 'loggraph folder'.""" ntables = program.ntables() if ntables == 0: # Nothing to do return name = identifyProgram(program).upper()+"_"+str(n) # Write out the start of the containing division and table html.write("
\n\n") html.write("\n\n") # Write the list of links for each loggraph html.write("\n\n") # Write each of the jloggraph applets html.write("\n\n") # Close off the containing table and division html.write("\n
\n
\n") html.write("

The following tables were found in the logfile:

\n") fontWeight = "bold" for i in range(0,ntables): tbl = program.table(i) title = smartie.escape_xml_characters(tbl.title()) html.write("

") html.write("" + \ str(title)+"

\n") fontWeight = "normal" html.write("
\n") html.write("
\n
\n") style = "block" for i in range(0,ntables): tbl = program.table(i) title = smartie.escape_xml_characters(tbl.title()) if not tbl.parse_error(): if tbl.ngraphs() > 0: # Graphs are defined so display using some java viewer html.write("
\n") jloggraph = javaloggraph() html.write("
\n") html.write("\n") html.write("
\n") html.write("

"+str(title)+"

\n") html.write("
\n") else: # No graphs - show the tabulated data html.write("
\n") html.write("

"+str(title)+"

\n") html.write(tbl.html()) html.write("
\n") else: # There was a problem parsing this table # Write a little summary html.write("
\n") html.write("

Sorry, the graphs in this table cannot be ") html.write("displayed:

") html.write("

""+str(title)+""

") html.write("

baubles was unable to process the data in the table ") html.write("(possibly due to a formatting error in the columns)

") html.write("
\n") # Reset display style after first graph style = "none" # Close off the table element html.write("
\n") html.write("
\n") html.write("
\n") def writeFragmentFolder(html,logfile,fragment,n): """Write HTML and Javascript for an arbitrary 'fragment folder'.""" # Create the "visibility controls" at the start of the fragment # display html.write("\n\n\n") html.write("
\n") html.write(" \n") html.write("

\n") html.write(" [View fragment]\n") html.write("

\n") html.write("
\n\n") html.write("
\n") html.write(" \n") html.write("

\n") html.write(" [Hide fragment]\n") html.write("

\n") # The fragment goes in here html.write("
\n\n") print "len(fragment)="+str(len(fragment)) html.write("

Fragment with "+str(len(fragment))+" lines

\n") writeLogfileSummary(html,fragment.retrieve()) html.write("
\n") # End the open fragment section html.write("
\n\n") def writeAdvancedLogfileFolder(html,logfile,program,n): """Write HTML and Javascript for an advanced program 'logfile folder'.""" # Start up name = identifyProgram(program).upper()+"_"+str(n) # Find start and end of the program log prog_beg = program.get_startline() prog_end = program.get_endline() logfilen = logfile.filename() curr_line = -1 # Create the "visibility controls" at the start of the logfile # display html.write("\n\n\n") writeLogfileControls(html,name) # Start writing the folder html.write("\n") html.write("
\n\n") html.write("
\n\n") # Write the log file content as summaries interleaved with # non-summary blocks found_summary = False for i in range(0,logfile.nsummaries()): if found_summary: # Check whether the next summary block starts # inside the current program if logfile.summary(i).start() > prog_end: # Write the final non-summary chunk writeLogfileNonSummary( \ html,name,smartie.retrieve(\ logfilen,curr_line,prog_end)) # This is the end of the log - exit the loop break # Fetch the block before the summary writeLogfileNonSummary( \ html,name,smartie.retrieve(\ logfilen,curr_line,logfile.summary(i).start()-1)) # Fetch the summary itself writeLogfileSummary(html,logfile.summary(i).retrieve()) # Update the current line curr_line = logfile.summary(i).end()+1 # Test for stop condition in loop so we don't look # at every single summary if logfile.summary(i).start() <= prog_end and \ logfile.summary(i).end() >= prog_end: # This was the last summary - exit the loop break if not found_summary and logfile.summary(i).start() > prog_end: # We have passed the end of the program # logfile without finding any summaries # Exit the loop break if not found_summary and logfile.summary(i).end() >= prog_beg: # This is the first summary that is part of # the log file fragment of interest found_summary = True # Was there any non-summary before the summary? if prog_beg < logfile.summary(i).start(): # Write this section first writeLogfileNonSummary( \ html,name,smartie.retrieve(\ logfilen,prog_beg,logfile.summary(i).start()-1)) # Fetch the summary itself writeLogfileSummary(html,logfile.summary(i).retrieve()) # Initialise the current line curr_line = logfile.summary(i).end()+1 # Check if there is some more (non-summary) logfile to write after the # last summary in the program log if found_summary and logfile.summary(i).end() < prog_end: writeLogfileNonSummary( \ html,name,smartie.retrieve(\ logfilen,logfile.summary(i).end()+1,prog_end)) # If there were no summaries then just dump the log with no processing # Mark it as summary if not found_summary: writeLogfileSummary(html,program.retrieve()) # Close off the folder html.write("\n
\n") # Generate another set of controls html.write("\n\n\n") writeLogfileControls(html,name) # Close off completely html.write("
\n\n\n") def writeLogfileControls(html,name): """Generate a set of 'visibility controls' for a logfile folder.""" html.write("
\n") html.write(" \n") html.write("

\n") html.write(" [Show logfile summary]\n") html.write(" [Show full logfile]\n") html.write("

\n") html.write("
\n\n") html.write("
\n") html.write(" \n") html.write("
\n") html.write("

Current view: summary only\n") html.write(" [Show full logfile]\n") html.write(" [Hide logfile]\n") html.write("

\n") html.write("
\n") html.write("
\n") html.write("

Current view: full logfile\n") html.write(" [Show logfile summary]\n") html.write(" [Hide logfile]\n") html.write("

\n") html.write("
\n") html.write("
\n\n") def writeLogfileSummary(html,text): """Write a block of summary logfile text.""" html.write("
")
    html.write(smartie.strip_logfile_html(text))
    html.write("
\n\n") #html.write("
\n") #html.write(plainTextMarkup(text)) #html.write("
\n\n") def writeLogfileNonSummary(html,name,text): """Write a block of non-summary logfile text.""" html.write("
")
    html.write(smartie.strip_logfile_html(text))
    html.write("
\n") html.write(" \n") html.write("
\n") html.write(" [Show summary only]\n") html.write("
\n") html.write("
\n\n") def writeBanner(html,name,program,anchor): """Write a generic HTML banner for the program""" html.write("
\n") if anchor != "": html.write("\n") # Program name try: html.write(""+str(name)+"\n") except AttributeError: pass # Version try: html.write("Version "+str(program.version)+"\n") except AttributeError: pass # Run time/date try: html.write(""+ \ "Run at "+str(program.runtime)+" on "+str(program.rundate)+ \ "\n") except AttributeError: pass # Termination message try: html.write("Finished with: "+ \ str(program.termination_message)+ \ "\n") except AttributeError: pass if anchor != "": html.write("\n") html.write("
\n") return def writeDocumentationLink(html,program): """Write a link to the program documentation.""" if os.environ.has_key("CHTML"): name = identifyProgram(program).lower()+".html" docfile = os.path.join(os.environ["CHTML"],name) if os.path.isfile(docfile): html.write("

[Documentation]

\n") return True return False ############################################################ # Secondary processing functions and classes ############################################################ # Java applet for loggraph viewing class javaloggraph: """Encapsulate the acquisition of java loggraph.""" def __init__(self): """Determine applet parameters. On instantiation, the javaloggraph object determines which java applet is available and sets applet parameters appropriately. These parameters can then be recovered using the 'code', 'codebase' and 'archive' methods.""" self.__codebase = getJLoggraphCodebase() self.__code = "" self.__archive = "JLogGraph.jar" # Determine what archive is available if self.__codebase is None: if os.environ.has_key("CBIN"): # Local version of java self.__codebase = "file:"+os.sep*2+os.environ["CBIN"] if os.path.exists(os.path.join(os.environ["CBIN"],"JLogView.jar")): self.__archive = "JLogView.jar" elif os.path.exists(os.path.join(os.environ["CBIN"],"JLogGraph.jar")): self.__archive = "JLogGraph.jar" else: # No local version - use the web version instead self.__codebase = "http://www.ccp4.ac.uk/peter/java" # Set the 'code' parameter according to the archive name if self.__archive == "JLogGraph.jar": self.__code="JLogGraph.class" elif self.__archive == "JLogView.jar": self.__code="JLogView_.LGApplet.class" def setCodebase(self,codebase): """Explicitly set the codebase for the applet""" self.__codebase = codebase; def codebase(self): """Return the value of the codebase for the applet.""" return self.__codebase def code(self): """Return the value of the code parameter for the applet.""" return self.__code def archive(self): """Return the value of the archive parameter for the applet.""" return self.__archive # Extended pattern recognition class extendedpatternmatch(smartie.patternmatch): """Extend the patternmatch class for secondary processing. This class extends the patternmatch class from smartie and adds new patterns to match with program-specific features. The idea is that these patterns can be used in a 'secondary processing step', in which additional data can be extracted for specific programs. This class adds new patterns: issfcheckbanner: check for SFCHECK program banner matthews_table: extract tabulated data from Matthews_coef""" def __init__(self): smartie.patternmatch.__init__(self) def isrecognisedbanner(self,text): """Check text against a set of possible program banners.""" # Try SFCHECK banner result = self.issfcheckbanner(text) if result: return result # Try LIBCHECK banner result = self.islibcheckbanner(text) if result: return result return result def issfcheckbanner(self,text): """Regular expression match to SFCHECK program banner. If the match fails then return False, otherwise return a dictionary with the following keys: name: will be 'SFCHECK' version: the program version.""" # # SFCHECK banner looks like: # Sol_ # Sol_ --- SFCHECK --- /Vers 7.1.01; 30.11.2005/ # Sol_ Memory: 10 Mb N_atom_max: 40000 # Sol_ # banner = self.compile("issfcheckbanner",r" Sol_\n Sol_ --- SFCHECK --- /Vers ([0-9.]+); ([0-9.]+)/\n").search(text) result = dict() if banner: result["banner_text"] = banner.group(0) result["name"] = "SFCHECK" result["version"] = banner.group(1) return result def islibcheckbanner(self,text): """Regular expression match to LIBCHECK program banner. If the match fails then return False, otherwise return a dictionary with the following keys: name: will be 'LIBCHECK' version: the program version.""" # # LIBCHECK "banner" looks like: # --- LIBCHECK --- /Vers 4.2.3 ; 28.07.2006/ # banner = self.compile("islibcheckbanner",r" --- LIBCHECK --- /Vers ([0-9.]+); ([0-9.]+)/\n").search(text) result = dict() if banner: result["banner_text"] = banner.group(0) result["name"] = "LIBCHECK" result["version"] = banner.group(1) return result def matthews_table(self,text): """Regular expression match to Matthews_coeff data. Attempts to match the tabulated values from the output of the Matthews_coeff program. If the match fails then returns False, otherwise returns a dictionary with the following keys: mol_weight: the estimated molecular weight table_head: a list of the column titles table_data: the raw tabulated data from the body of the table""" # # One form of the Matthews table looks like: # # For estimated molecular weight 12938. # Nmol/asym Matthews Coeff %solvent P(tot) # ________________________________________________ # 1 1.28 3.75 1.00 # ________________________________________________ # 123456789012345678901234567890123456789012345678 # # The other form looks like: # # For estimated molecular weight 6600. # Nmol/asym Matthews Coeff %solvent P(4.00) P(tot) # _____________________________________________________________ # 1 2.50 50.89 1.00 1.00 # 2 1.25 1.79 0.00 0.00 # _____________________________________________________________ # 1234567890123456789012345678901234567890123456789012345678901 # table = self.compile("matthews_table",r"For estimated molecular weight *([0-9.]+)\n(Nmol/asym) *(Matthews Coeff) *(%solvent) *(P\([0-9.]*\))? *(P\(tot\))\n_{48,61}\n([ 0-9.\n]*)\n_{48,61}").search(text) result = dict() if table: result["table_text"] = table.group(0) result["mol_weight"] = table.group(1) result["table_head"] = [] for i in range(2,7): if table.group(i): result["table_head"].append(table.group(i)) result["table_data"] = table.group(7) return result def phaser_module(self,text): """Regular expression match to identify 'Phaser module' data. Attempts to identify the 'Phaser module' and the associated version number from the Phaser log file. If the match fails then returns False, otherwise returns a dictionary with the following keys: phaser_module: the name of the Phaser module phaser_module_version: the associated version number""" # # 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345 # ************************************************************************************* # *** Phaser Module: PERMUTATIONS AND SPACEGROUPS 1.3.2 *** # ************************************************************************************* module = self.compile("phaser_module",r"\*{85,85}\n\*\*\* Phaser Module: ([^\*]*) \*\*\*\n\*{85,85}").search(text) result = dict() if module: result["module_text"] = module.group(0) # Break up the text into module name and version number result["phaser_module"] = " ".join(module.group(1).split()[0:-1]) result["phaser_module_version"] = module.group(1).split()[-1] return result def processUnknownProgram(program): """Perform additonal processing on an unknown program.""" # Reacquire the text logtext = program.retrieve() # Set up objects for parsing bufsize = 10 buff = smartie.buffer(bufsize) regex = extendedpatternmatch() # Loop over lines and look for other features for line in logtext.split("\n"): buff.append(line) bufftext = buff.tail(bufsize) result = regex.isrecognisedbanner(bufftext) if result: # Recognised the program # Set the newly discovered attributes and return program.set_attributes_from_dictionary(result) buff.clear() return def processMatthews_Coef(program): """Perform additional processing on Matthews_Coef output. This extracts additional data items from the output of Matthews_coef, and sets up a table object with the tabulated data.""" # Reacquire the text logtext = program.retrieve() # Set up objects for parsing bufsize = 20 buff = smartie.buffer(bufsize) regex = extendedpatternmatch() # Loop over lines and look for specific features for line in logtext.split("\n"): buff.append(line) bufftext = buff.tail(bufsize) result = regex.matthews_table(bufftext) if result: # Collected the tabular data program.set_attributes_from_dictionary(result) # Turn this into a smartie table within the # program object table = program.addtable() table.settitle("Matthews_Coeff mol weight "+\ str(program.mol_weight)) for name in program.table_head: table.addcolumn(name) # Once the columns are defined just send # the remaining table body directly to the # table object to populate it table.setdata(program.table_data) buff.clear() return def processRefmac(program): """Perform additional processing on Refmac output.""" try: # Summary of initial and final Rfactors tbl = program.tables("Rfactor analysis, stats vs cycle")[0] program.set_attribute("Ncycles",int(tbl.col("Ncyc")[-1])) program.set_attribute("Rfact_init",tbl.col("Rfact")[0]) program.set_attribute("Rfact_final",tbl.col("Rfact")[-1]) program.set_attribute("Rfree_init",tbl.col("Rfree")[0]) program.set_attribute("Rfree_final",tbl.col("Rfree")[-1]) program.set_attribute("rmsBOND_init",tbl.col("rmsBOND")[0]) program.set_attribute("rmsBOND_final",tbl.col("rmsBOND")[-1]) program.set_attribute("rmsCHIRAL_init",tbl.col("rmsCHIRAL")[0]) program.set_attribute("rmsCHIRAL_final",tbl.col("rmsCHIRAL")[-1]) # Looks like there was a change in the spelling of ANGL(E) # between versions once try: program.set_attribute("rmsANGLE_init",tbl.col("rmsANGLE")[0]) program.set_attribute("rmsANGLE_final",tbl.col("rmsANGLE")[-1]) except LookupError: program.set_attribute("rmsANGLE_init",tbl.col("rmsANGL")[0]) program.set_attribute("rmsANGLE_final",tbl.col("rmsANGL")[-1]) program.set_attribute("Result",True) except IndexError: # Probably couldn't find the table program.set_attribute("Result",False) return def processPhaser(program): """Perform additional processing on Phaser output. This tries to acquire the 'Phaser module' name which describes the function that the run is performing e.g. 'Molecular Replacment Packing' or 'Permutations And Spacegroups'.""" # Reacquire the text logtext = program.retrieve() # Set up objects for parsing bufsize = 3 buff = smartie.buffer(bufsize) regex = extendedpatternmatch() # Loop over lines and look for specific features for line in logtext.split("\n"): buff.append(line) bufftext = buff.tail(bufsize) result = regex.phaser_module(bufftext) if result: # Identified the phaser_module program.set_attributes_from_dictionary(result) buff.clear() return def identifyProgram(program): """Return a standard (generic) program name.""" try: name = program.name except AttributeError: # No name was set for this program # Try to acquire the name from the termination # message try: name = program.termination_name except AttributeError: # No name found anywhere name = "unknown" if re.compile(r"^Refmac").match(name): name = "refmac5" if re.compile(r"^FFT(BIG)?").match(name): name = "fft" # Return name as a title return str(name).title() def collectReferences(program): """Collect program references for citation""" result = [] for i in range(0,program.nkeytexts()): keytext = program.keytext(i) if keytext.name().find("Reference") >= 0: result.append(str(keytext.message())) return result def plainTextMarkup(text): """Turn plain text into HTML markup using expert recognition This function tries to recognise 'paragraphs' in plain text (a paragraph is a set of one or more lines of text which is separated from other paragraphs by one or more lines of whitespace). Paragraphs are then rendered in one of three ways dependent on the rules encoded in the plainTextMarkup function. These are: Paragraph: the text is wrapped in

...

tags to be displayed as a HTML paragraph. List: if a group of consecutive paragraphs start with a hyphen then those paragraphs will be gathered together into a bulleted list, with each paragraph being an item in the list. Table: 'magic' tables are generated from plain text based on the following rules: 1) A data-field is a field, defined by a separator of at least two whitespaces, which contains either a numeric value, or '-' or '*'. 2) A header-field is a field, defined by a separator of at least two whitespaces, which is not a data field. 3) A data line is a line containing at least one trailing data-field. The data-count of a line is equal to the number of trailing data-fields. 4) A header line is a line consisting of at least one data or header field, where at least the last field on the line is a header field. 5) A table is a paragraph with more than one line, consisting of at most one header line followed by at least one data line, and where every data-line has the same data-count, and the total number of data fields is at least 2. 6) Multiple consecutive tables with the same data-count are merged into a single table with a thin-separator between the paragraphs. """ lines = smartie.escape_xml_characters(text).split("\n"); # split into paragraphs paras = [] para = [] for line in lines: if line.strip() != "": para.append(line) else: if len(para) > 0: paras.append(para) para = [] if len(para) > 0: paras.append(para) # identify paragraphs as tables and lists tablesep = re.compile( r'\s\s+' ) istable = [False]*len(paras) islist = [False]*len(paras) plines = [0]*len(paras) fields = [0]*len(paras) for p in range(len(paras)): lines = paras[p] plines[p] = len(lines) nnum = 0 nwrd = 0 if len(lines) > 0: # identify list elements words = lines[0].split() nwrd = len(words) if len(words) > 1: if words[0] == "-": islist[p] = True if len(lines) > 1: # identify table elements nnums = [] for line in lines: words = tablesep.split( line.strip() ) nnum = 0 for word in words: nnum += 1 # count trailing numbers or placeholders if word != "-" and word != "*": try: f = float(word) except: nnum = 0 nnums.append(nnum) if nnum > 0: nrow = 0 for n in nnums: if n == nnum: nrow += 1 if nrow == len(lines): # unheaded table istable[p] = True if nrow == len(lines)-1 and nnums[0] == 0: # headed table if nnum > 1 or nrow > 1: istable[p] = True if istable[p]: fields[p] = nnum else: fields[p] = nwrd # now print with markup result = "" intable = False inlist = False for p in range(len(paras)): # close tags if intable: if ( not istable[p] ) or ( fields[p] != fields[p-1] ): intable = False result += "\n" if inlist: if ( not islist[p] ): inlist = False result += "\n" # markup if istable[p]: if not intable: result += "\n" else: result += "\n" intable = True for line in paras[p]: words = tablesep.split( line.strip() ) heads = max( len(words)-fields[p], 0 ) row = "" for w in range(heads,len(words)): try: f = float(words[w]) row += "" except: row += "" row += "\n" result += row elif islist[p]: if not inlist: result += "
" for w in range(0,heads): row += words[w] + " " row += ""+words[w]+""+words[w]+"
\n" if inlist: result += "\n" return result ############################################################ # Summarising functions ############################################################ def summariseGeneric(html,program): """Generate a summary for a program object""" # collect warning a results messages for summary warnings = [] result = None for i in range(0,program.nkeytexts()): keytext = program.keytext(i) if keytext.name() == "Warning": warnings.append( str(keytext.message() ) ) elif keytext.name() == "Result": result = str(keytext.message()) if result != None: html.write("
\n") html.write("

Result:

\n") html.write(plainTextMarkup(result)) html.write("

\n") html.write("
\n") if len(warnings) > 0: html.write("
\n") html.write("

The following warnings were issued:

\n") html.write("") html.write("
\n") def summariseMatthews_Coef(html,program): """Generate a summary for a Matthews_Coef program object""" if program.has_attribute("mol_weight"): html.write("

Molecular weight "+\ str(program.mol_weight)+"

") def summariseRefmac(html,program): """Generate a summary for a Refmac program object""" # Write a small table with initial and final Rfactors if ( program.Result ): html.write("

Summary:

\n") html.write("

\n") html.write("") html.write("\n") html.write("") html.write("") html.write("\n") html.write("") html.write("") html.write("\n") html.write("") html.write("") html.write("\n") html.write("") html.write("") html.write("\n") html.write("") html.write("") html.write("\n") html.write("
 InitialAfter "+str(program.Ncycles)+" cycles
R factor"+str(program.Rfact_init)+""+str(program.Rfact_final)+"
Rfree"+str(program.Rfree_init)+""+str(program.Rfree_final)+"
RMSD Bond Length"+str(program.rmsBOND_init)+""+str(program.rmsBOND_final)+"
RMSD Bond Angle"+str(program.rmsANGLE_init)+""+str(program.rmsANGLE_final)+"
RMSD Chiral Centre"+str(program.rmsCHIRAL_init)+""+str(program.rmsCHIRAL_final)+"

\n") # Write a list of warnings summariseGeneric(html,program) def summarisePhaser(html,program): """Generate a summary for a Phaser program object""" # Write out the phaser module if program.has_attribute("phaser_module"): html.write("

Phaser module: "+\ str(program.phaser_module).title()+"

") # Write a list of warnings summariseGeneric(html,program) ############################################################ # Experimental conversion functions ############################################################ def polarrfn_plot(pltfile,imgbase=""): """Convert .plt output from Polarrfn to multiple gif images. Note that this function is still under development. Given a .plt output file from the Polarrfn program, this function attempts to generate a set of gif images from each frame of the plot. The operation of this function depends on the presence of the CCP4 'pltdev' program and the 'convert' program from the ImageMagick package. By default the gif images are created in the CCP4_SCR directory and have names that are derived from the plt file name with the number of the image appended and the extension '.gif' instead of '.plt'.""" # Check for the pltdev program try: pltdev = os.path.join(os.environ["CBIN"],"pltdev") except KeyError: # Unable to get CBIN from the environment print "Unable to make path name for pltdev" return # Make a temporary ps file if not os.path.exists(pltfile): print str(pltfile)+": file not found" return try: psfile = os.path.join(os.environ["CCP4_SCR"], \ os.path.splitext(os.path.basename(pltfile))[0]+".ps") os.popen("pltdev -xp 0.7 -yp 0.7 -dev ps -o "+ \ str(psfile)+" "+str(pltfile)) except: # Unable to generate the name or run the program raise if not os.path.exists(psfile): print "Failed to make postscript file" return # Count the number of pages in the file npages = 0 ps = open(psfile,"r") for line in ps: if line.count("%%Page:") > 0: npages = npages + 1 ps.close() print "Number of pages (=frames): "+str(npages) # Loop over pages and use convert to turn them # into gifs for i in range(0,npages): frame = os.path.splitext(psfile)[0]+"_"+str(i)+".gif" cmd = "convert "+str(psfile)+"'["+str(i)+"]' "+str(frame) print "Processing frame "+str(i)+"... ("+str(frame)+")" os.popen(cmd) return ############################################################ # Core baubles functions ############################################################ def baubles_html(logfile,htmlfile=None): """Create an HTML summary of the logfile. logfile specifies the log file to process; htmlfile specifies the name of the file to write the HTML output to. If htmlfile is None then the HTML is written to stdout.""" # Process the logfile log = smartie.parselog(logfile) # Generate the HTML baubles(log,htmlfile) return def baubles(log,htmlfile=None): """Given a smartie logfile object, generate HTML summary 'log' specifies a populated smartie logfile object; htmlfile specifies the name of the file to write the HTML output to. If htmlfile is None then the HTML is written to stdout.""" # Open the file if htmlfile: html = open(htmlfile,"w") else: import sys html = sys.stdout # Write a header html.write(""" \n""") jloggraph = javaloggraph() html.write("\n") writeInlineStyle(html) writeJavascriptLibrary(html) logfilename = os.path.basename(log.filename()) html.write("baubles: "+str(logfilename)+"\n") html.write(""" \n""") html.write("\n") html.write("

"+str(logfilename)+"

\n") # Write contents list, if there was more than one program if log.nprograms() > 1: html.write("\n") html.write("

The logfile is composed of output "+ "from the following programs:

\n") html.write("\n") # List of references refs = {} for i in range(0,log.nfragments()): fragment = log.fragment(i) if fragment.isprogram(): program = fragment name = identifyProgram(program) progrefs = collectReferences(program) if len(progrefs) > 0: refs[name] = progrefs if len(refs) > 0: html.write("\n") html.write("
") html.write("Please consider citing the following papers:\n\n") html.write("
\n") # Contents of the logfile if log.nfragments() > 0: for i in range(0,log.nfragments()): fragment = log.fragment(i) if fragment.isprogram(): program = fragment name = identifyProgram(program) anchor = str(name)+"_"+str(i) # Write a general banner writeBanner(html,name,program,anchor) # Output for specific programs if name == "Matthews_Coef": processMatthews_Coef(program) summariseMatthews_Coef(html,program) elif name == "Refmac5": processRefmac(program) summariseRefmac(html,program) elif name == "Phaser": processPhaser(program) summarisePhaser(html,program) else: summariseGeneric(html,program) # Write the loggraphs writeLoggraphFolder(html,program,i) # Link to program documentation writeDocumentationLink(html,program) # Generate the folder for the raw logfile writeAdvancedLogfileFolder(html,log,program,i) elif fragment.isccp4i_info(): ccp4i_info = fragment html.write("

CCP4i: "+\ str(ccp4i_info.message)+\ "

\n") elif fragment.ntables() > 0: # An arbitrary fragment with tables # Write the loggraphs writeLoggraphFolder(html,fragment,i) else: # An arbitrary fragment with no tables writeFragmentFolder(html,log,fragment,i) # Write a footer date = time.asctime() html.write("
\n") html.write("

Generated for you by baubles "+str(__version__)+\ " on "+str(date)+"

\n") html.write("\n") html.write("\n") # Close the file if html: html.close() return # Configuration def setJLoggraphCodebase(codebase): """Set the default value for the Java loggraph codebase This allows an application to override the value of the 'codebase' parameter that is written to the output HTML for the Java loggraphs. The codebase tells the browser where to find the java applet or archive containing the JLoggraph code. If this is not set to an explicit value then it is determined automatically on initialisation of a 'javaloggraph' object. Set 'codebase' to None to return to the automatic defaults.""" global JAVALOGGRAPH_CODEBASE JAVALOGGRAPH_CODEBASE = codebase return def getJLoggraphCodebase(): """Fetch the default value for the Java loggraph codebase Returns the setting of the default value of the 'codebase' parameter that is written to the output HTML for the Java loggraphs.""" global JAVALOGGRAPH_CODEBASE return JAVALOGGRAPH_CODEBASE ############################################################ # Top level (baubles program) ############################################################ if __name__ == "__main__": # Set usage string usage = "baubles [options] \n"+ \ "Options:\n"+ \ "-o : write HTML output to \n" \ "-summary: print summaries from logfile\n"+ \ "-polarrfn_plot: generate gifs from Polarrfn .plt file" # Needs at least one argument to run (i.e. name of a log file) if len(sys.argv) < 2: print "Usage: "+str(usage) sys.exit(0) # Assume that the logfile is the last argument logfile = sys.argv[-1] # Check the target file exists if not os.path.exists(logfile): print "File not found: \""+str(logfile)+"\"" sys.exit(1) # Initialise htmlfile = None # Process the command line options i = 1 nargs = len(sys.argv)-1 while i < nargs: arg = str(sys.argv[i]) if arg == "-o": # Output file name i=i+1 htmlfile = str(sys.argv[i]) print "Output file: "+str(htmlfile) elif arg == "-summary": # Write out a summary log = smartie.parselog(logfile) for i in range(0,log.nsummaries()): print str(smartie.strip_logfile_html(log.summary(i).retrieve())) sys.exit(0) elif arg == "-polarrfn_plot": # Assume that the input file is a # polarrfn plt file polarrfn_plot(logfile) sys.exit(0) else: # Unrecognised option print "Unrecognised option: "+str(arg) print "Usage: "+str(usage) sys.exit(1) # Next argument i=i+1 # Don't knowingly overwrite the input file if htmlfile: if htmlfile == logfile: print "Input and output files are the same!" sys.exit(1) # Run baubles baubles_html(logfile,htmlfile)