Ignore:
Timestamp:
Jan 12, 2011, 9:45:08 PM (9 years ago)
Author:
robert@…
Message:

Externalized configuration; improved assay view (detail views of runs and samples); implemented uploading and parsing of FASTA and QUAL files

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk

    • Property svn:ignore
      •  

        old new  
        44.classpath
        55.project
         6fileuploads
  • trunk/grails-app/services/nl/tno/metagenomics/FastaService.groovy

    r2 r3  
    11package nl.tno.metagenomics
    2 
    32
    43import java.io.File;
     
    98        def fileService
    109        def fuzzySearchService
    11        
    12     static transactional = true
     10
     11        static transactional = true
    1312
    1413        /**
    1514         * Parses uploaded files and checks them for FASTA and QUAL files
    1615         * @param filenames             List of filenames currently existing in the upload directory
     16         * @param onProgress    Closure to execute when progress indicators should be updated.
     17         *                                              Has 2 parameters: numFilesProcessed and numBytesProcessed that indicate the number
     18         *                                              of files and bytes that have been processed in total
    1719         * @param directory             Directory to move the files to
    1820         * @return                              Structure with information about the parsed files. The 'success' files are
     
    3234         *
    3335         */
    34         def parseFiles( ArrayList filenames, File directory = null ) {
     36        def parseFiles( ArrayList filenames, Closure onProgress, File directory = null ) {
    3537                if( filenames.size() == 0 ) {
    3638                        return [ success: [], failure: [] ];
     
    4345                def success = [];
    4446                def failure = [];
     47
     48                long filesProcessed = 0;
     49                long bytesProcessed = 0;
    4550
    4651                // Loop through all filenames
     
    5560                        } else {
    5661                                try {
    57                                         def contents = parseFile( file, filetype );
    58                                        
     62                                        def contents = parseFile( file, filetype, { files, bytes ->
     63                                                filesProcessed += files;
     64                                                bytesProcessed += bytes;
     65
     66                                                onProgress( filesProcessed, bytesProcessed );
     67                                        } );
     68
    5969                                        contents.filename = file.getName();
    60                                         contents.originalfilename = fileService.originalFilename( contents.filename ) 
    61                                        
     70                                        contents.originalfilename = fileService.originalFilename( contents.filename )
     71
    6272                                        if( contents.success ) {
    6373                                                success << contents;
     
    7181                                        failure << [ filename: filename, originalfilename: fileService.originalFilename( filename ), type: filetype, message: e.getMessage() ];
    7282                                }
    73        
     83
    7484                        }
    7585                }
     
    103113                def quals = parsedFiles.findAll { it.type == "qual" }
    104114                samples = samples.toList()
    105                
     115
    106116                def files = [];
    107                
     117
    108118                fastas.each { fastaFile ->
    109119                        // Remove extension
    110                         def matchWith = fastaFile.originalfilename.substring( 0, fastaFile.filename.lastIndexOf( '.' ) )
    111                        
     120                        def matchWith = fastaFile.originalfilename.substring( 0, fastaFile.originalfilename.lastIndexOf( '.' ) )
     121
    112122                        // Determine feasible quals (based on number of sequences )
    113123                        def feasibleQuals = quals.findAll { it.numSequences == fastaFile.numSequences }
    114                        
     124
    115125                        // Best matching qual file
    116126                        def qualIdx = fuzzySearchService.mostSimilarWithIndex( matchWith + '.qual', feasibleQuals.originalfilename );
     
    119129                        if( qualIdx != null )
    120130                                qual = feasibleQuals[ qualIdx ];
    121                        
     131
    122132                        // Best matching sample
    123133                        def sampleIdx = fuzzySearchService.mostSimilarWithIndex( matchWith, samples.sample.name );
    124134                        def assaySample = null
    125                         if( sampleIdx != null ) { 
     135                        if( sampleIdx != null ) {
    126136                                assaySample = samples[ sampleIdx ];
    127137                        }
    128138
    129139                        files << [
    130                                 fasta: fastaFile,
    131                                 feasibleQuals: feasibleQuals,
    132                                 qual: qual,
    133                                 assaySample: assaySample
    134                         ]
    135                 }
    136                
     140                                                fasta: fastaFile,
     141                                                feasibleQuals: feasibleQuals,
     142                                                qual: qual,
     143                                                assaySample: assaySample
     144                                        ]
     145                }
     146
    137147                return files;
    138                
    139                
    140         }
    141        
    142        
     148
     149
     150        }
     151
     152
    143153        /**
    144154         * Determines the file type of a given file, based on the extension.
     
    195205        /**
    196206         * Parses the given file
    197          * @param file          File to parse
    198          * @param filetype      Type of the given file
    199          * @return                      List structure. Examples:
     207         * @param file                  File to parse
     208         * @param filetype              Type of the given file
     209         * @param onProgress    Closure to execute when progress indicators should be updated.
     210         *                                              Has 2 parameters: numFilesProcessed and numBytesProcessed that indicate the number
     211         *                                              of files and bytes that have been processed in this file (so the first parameter should
     212         *                                              only be 1 when the file is finished)
     213         *
     214         * @return                              List structure. Examples:
    200215         *
    201216         *   [ success: true, filename: 'abc.fasta', type: 'fasta', numSequences: 200 ]
     
    203218         *   [ success: false, filename: 'abc.txt', type: 'txt', message: 'Filetype could not be parsed.' ]
    204219         */
    205         protected def parseFile( File file, String filetype ) {
     220        protected def parseFile( File file, String filetype, Closure onProgress ) {
    206221                switch( filetype ) {
    207222                        case "fasta":
    208                                 return parseFasta( file );
     223                                return parseFasta( file, onProgress );
    209224                        case "qual":
    210                                 return parseQual( file );
     225                                return parseQual( file, onProgress );
    211226                        default:
     227                                onProgress( 1, file.length() );
    212228                                return [ success: false, type: filetype, message: 'Filetype could not be parsed.' ]
    213229                }
     
    216232        /**
    217233         * Parses the given FASTA file
    218          * @param file          File to parse
    219          * @return                      List structure. Examples:
     234         * @param file                  File to parse
     235         * @param onProgress    Closure to execute when progress indicators should be updated.
     236         *                                              Has 2 parameters: numFilesProcessed and numBytesProcessed that indicate the number
     237         *                                              of files and bytes that have been processed in this file (so the first parameter should
     238         *                                              only be 1 when the file is finished)
     239         * @return                              List structure. Examples:
    220240         *
    221241         *   [ success: true, filename: 'abc.fasta', type: 'fasta', numSequences: 200 ]
    222242         *   [ success: false, filename: 'def.fasta', type: 'fasta', message: 'File is not a valid FASTA file' ]
    223243         */
    224         protected def parseFasta( File file ) {
    225                
     244        protected def parseFasta( File file, Closure onProgress ) {
     245
    226246                long startTime = System.nanoTime();
    227                 log.trace "Start parsing FASTA " + file.getName() 
    228                
     247                log.trace "Start parsing FASTA " + file.getName()
     248
    229249                // Count the number of lines, starting with '>' (and where the following line contains a character other than '>')
    230250                long numSequences = 0;
     251                long bytesProcessed = 0;
    231252                boolean lookingForSequence = false;
    232253
     
    241262                                        }
    242263                                }
    243                         }
    244                 }
     264
     265
     266                                // Update progress every time 1MB is processed
     267                                bytesProcessed += line.size();
     268                                if( bytesProcessed > 1000000 ) {
     269                                        onProgress( 0, bytesProcessed );
     270                                        bytesProcessed = 0;
     271                                }
     272                        }
     273
     274
     275
     276                }
     277
     278                // Update progress and say we're finished
     279                onProgress( 1, bytesProcessed );
    245280
    246281                log.trace "Finished parsing FASTA " + file.getName() + ": " + ( System.nanoTime() - startTime ) / 1000000L
     
    251286        /**
    252287         * Parses the given QUAL file
    253          * @param file          File to parse
    254          * @return                      List structure. Examples:
     288         * @param file                  File to parse
     289         * @param onProgress    Closure to execute when progress indicators should be updated.
     290         *                                              Has 2 parameters: numFilesProcessed and numBytesProcessed that indicate the number
     291         *                                              of files and bytes that have been processed in this file (so the first parameter should
     292         *                                              only be 1 when the file is finished)
     293         * @return                              List structure. Examples:
    255294         *
    256295         *   [ success: true, filename: 'abc.qual', type: 'qual', numSequences: 200, avgQuality: 31 ]
    257296         *   [ success: false, filename: 'def.qual', type: 'qual', message: 'File is not a valid QUAL file' ]
    258297         */
    259         protected def parseQual( File file ) {
     298        protected def parseQual( File file, Closure onProgress ) {
    260299                long startTime = System.nanoTime();
    261300                log.trace "Start parsing QUAL " + file.getName()
    262301
    263                 // Count the number of lines, starting with '>'. After we've found such a character, we continue looking for 
     302                // Count the number of lines, starting with '>'. After we've found such a character, we continue looking for
    264303                // quality scores
    265304                long numSequences = 0;
     305                long bytesProcessed = 0;
    266306                def quality = [ quality: 0.0, number: 0L ]
    267                
     307
    268308                boolean lookingForFirstQualityScores = false;
    269309                file.eachLine { line ->
     
    275315                                                numSequences++;
    276316                                                lookingForFirstQualityScores = false;
    277                                                
     317
    278318                                                quality = updateQuality( quality, line );
    279319                                        }
     
    281321                                        quality = updateQuality( quality, line );
    282322                                }
    283                         }
    284                 }
     323                               
     324                                // Update progress every time 1MB is processed
     325                                bytesProcessed += line.size();
     326                                if( bytesProcessed > 1000000 ) {
     327                                        onProgress( 0, bytesProcessed );
     328                                        bytesProcessed = 0;
     329                                }
     330                        }
     331
     332                }
     333
     334                // Update progress and say we're finished
     335                onProgress( 1, bytesProcessed );
    285336
    286337                log.trace "Finished parsing QUAL " + file.getName() + ": " + ( System.nanoTime() - startTime ) / 1000000L
     
    288339                return [ success: true, type: "qual", numSequences: numSequences, avgQuality: quality.quality ];
    289340        }
    290        
     341
    291342        /**
    292343         * Parses the given line and updates the average quality based on the scores
     
    298349                // Determine current average
    299350                List tokens = line.tokenize();
    300                 Long total = 0; 
    301                
     351                Long total = 0;
     352
    302353                tokens.each {
    303354                        total += Integer.parseInt( it );
    304355                }
    305                
     356
    306357                int numTokens = tokens.size();
    307                
     358
    308359                // Update the given average
    309360                if( numTokens > 0 ) {
     
    311362                        quality.quality = quality.quality + ( ( total / numTokens as double ) - quality.quality ) / quality.number * numTokens;
    312363                }
    313                
     364
    314365                return quality
    315366        }
    316        
     367
    317368        /**
    318369         * Moves a fasta and qual file to their permanent location, and returns information about these files
     
    326377                File permanentDirectory = fileService.absolutePath( ConfigurationHolder.config.metagenomics.fileDir );
    327378                def returnStructure = [:];
    328                
     379
    329380                if( fileService.fileExists( fastaFile ) ) {
    330381                        // Lookup the original filename
     
    340391                        throw new Exception( "Fasta file to save doesn't exist on disk" );
    341392                }
    342                
     393
    343394                if( qualFile && fileService.fileExists( qualFile ) ) {
    344395                        // Lookup the original filename
    345396                        def qualData = processedFiles.parsed.success.find { it.filename == qualFile };
    346                        
     397
    347398                        if( qualData ) {
    348399                                returnStructure.qual = fileService.moveFileToUploadDir( fileService.get(qualFile ), qualData.originalfilename, permanentDirectory );
     
    358409                        returnStructure.avgQuality = 0;
    359410                }
    360                
     411
    361412                return returnStructure;
    362413        }
Note: See TracChangeset for help on using the changeset viewer.