Ignore:
Timestamp:
Jul 21, 2011, 4:36:15 PM (11 years ago)
Author:
robert@…
Message:

Added the possibility to export assay data for samples after searching. This also works for samples with multiple assays.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/grails-app/services/dbnp/studycapturing/AssayService.groovy

    r1937 r1969  
    3434         * module's measurements.
    3535         *
    36          * @param assay the assay for which to collect the fields
     36         * @param assay         the assay for which to collect the fields
     37         * @param samples       list of samples to retrieve the field names for. If not given, all samples from the assay are used.
    3738         * @return a map of categories as keys and field names or measurements as
    3839         *  values
    3940         */
    40         def collectAssayTemplateFields(assay) throws Exception {
     41        def collectAssayTemplateFields(assay, samples = null) throws Exception {
    4142
    4243                def getUsedTemplateFields = { templateEntities ->
     
    5051                }
    5152
    52         def moduleError = '', moduleMeasurements = []
    53 
    54         try {
    55             moduleMeasurements = requestModuleMeasurementNames(assay)
    56         } catch (e) {
    57             moduleError = e.message
    58         }
    59 
    60                 def samples = assay.samples
     53                def moduleError = '', moduleMeasurements = []
     54
     55                try {
     56                        moduleMeasurements = requestModuleMeasurementNames(assay)
     57                } catch (e) {
     58                        moduleError = e.message
     59                }
     60
     61                if( !samples )
     62                        samples = assay.samples
     63
    6164                [               'Subject Data' :            getUsedTemplateFields( samples*."parentSubject".unique() ),
    6265                                        'Sampling Event Data' :     getUsedTemplateFields( samples*."parentEvent".unique() ),
     
    6467                                        'Event Group' :             [[name: 'name', comment: 'Name of Event Group', displayName: 'name']],
    6568                                        'Module Measurement Data':  moduleMeasurements,
    66                     'ModuleError':              moduleError
    67                 ]
     69                                        'ModuleError':              moduleError
     70                                ]
    6871
    6972        }
     
    8083         * @param fieldMap                              map with categories as keys and fields as values
    8184         * @param measurementTokens     selection of measurementTokens
     85         * @param samples                               list of samples for which the data should be retrieved.
     86         *                                                              Defaults to all samples from this assay.
    8287         * @return                              The assay data structure as described above.
    8388         */
    84         def collectAssayData(assay, fieldMap, measurementTokens) throws Exception {
     89        def collectAssayData(assay, fieldMap, measurementTokens, samples = null) throws Exception {
    8590
    8691                def collectFieldValuesForTemplateEntities = { headerFields, templateEntities ->
     
    9297                                map + [(headerField.displayName): templateEntities.collect { entity ->
    9398
    94                     // default to an empty string
    95                     def val = ''
    96 
    97                     if (entity) {
    98                         def field
    99                         try {
    100 
    101                             val = entity.getFieldValue(headerField.name)
    102 
    103                             // Convert RelTime fields to human readable strings
    104                             field = entity.getField(headerField.name)
    105                             if (field.type == TemplateFieldType.RELTIME)
    106                                 val = new RelTime( val as long )
    107 
    108                         } catch (NoSuchFieldException e) { /* pass */ }
    109                     }
    110 
    111                     (val instanceof Number) ? val : val.toString()}]
     99                                                // default to an empty string
     100                                                def val = ''
     101
     102                                                if (entity) {
     103                                                        def field
     104                                                        try {
     105
     106                                                                val = entity.getFieldValue(headerField.name)
     107
     108                                                                // Convert RelTime fields to human readable strings
     109                                                                field = entity.getField(headerField.name)
     110                                                                if (field.type == TemplateFieldType.RELTIME)
     111                                                                        val = new RelTime( val as long )
     112
     113                                                        } catch (NoSuchFieldException e) { /* pass */ }
     114                                                }
     115
     116                                                (val instanceof Number) ? val : val.toString()}]
    112117                        }
    113118                }
     
    172177
    173178                // Find samples and sort by name
    174                 def samples = assay.samples.toList().sort { it.name }
     179                if( !samples )
     180                        samples = assay.samples.toList().sort { it.name }
    175181
    176182                def eventFieldMap = [:]
     
    190196                if (measurementTokens) {
    191197
    192             try {
    193                 moduleMeasurementData = requestModuleMeasurements(assay, measurementTokens, samples)
    194             } catch (e) {
    195                 moduleMeasurementData = ['error' : ['Module error, module not available or unknown assay'] * samples.size() ]
    196                 moduleError =  e.message
    197             }
    198 
    199                 }
    200                
     198                        try {
     199                                moduleMeasurementData = requestModuleMeasurements(assay, measurementTokens, samples)
     200                        } catch (e) {
     201                                moduleMeasurementData = ['error' : ['Module error, module not available or unknown assay'] * samples.size() ]
     202                                moduleError =  e.message
     203                        }
     204
     205                }
     206
    201207                [       'Subject Data' :            getFieldValues(samples, fieldMap['Subject Data'], 'parentSubject'),
    202                                 'Sampling Event Data' :     getFieldValues(samples, fieldMap['Sampling Event Data'], 'parentEvent'),
    203                 'Sample Data' :             getFieldValues(samples, fieldMap['Sample Data']),
    204                 'Event Group' :             eventFieldMap,
    205                 'Module Measurement Data' : moduleMeasurementData,
    206                 'ModuleError' :             moduleError
     208                                        'Sampling Event Data' :     getFieldValues(samples, fieldMap['Sampling Event Data'], 'parentEvent'),
     209                                        'Sample Data' :             getFieldValues(samples, fieldMap['Sample Data']),
     210                                        'Event Group' :             eventFieldMap,
     211                                        'Module Measurement Data' : moduleMeasurementData,
     212                                        'ModuleError' :             moduleError
    207213                                ]
    208214        }
     
    268274
    269275                def path = moduleUrl + "/rest/getMeasurements/query"
    270         def query = "assayToken=${assay.giveUUID()}"
    271         def jsonArray
    272 
    273         try {
    274             jsonArray = moduleCommunicationService.callModuleMethod(moduleUrl, path, query, "POST")
    275         } catch (e) {
    276             throw new Exception("An error occured while trying to get the measurement tokens from the $assay.module.name. \
     276                def query = "assayToken=${assay.giveUUID()}"
     277                def jsonArray
     278
     279                try {
     280                        jsonArray = moduleCommunicationService.callModuleMethod(moduleUrl, path, query, "POST")
     281                } catch (e) {
     282                        throw new Exception("An error occured while trying to get the measurement tokens from the $assay.module.name. \
    277283             This means the module containing the measurement data is not available right now. Please try again \
    278284             later or notify the system administrator if the problem persists. URL: $path?$query.")
    279         }
     285                }
    280286
    281287                def result = jsonArray.collect {
     
    285291                                return it.toString()
    286292                }
    287                        
     293
    288294                return result
    289295        }
     
    309315                def path = moduleUrl + "/rest/getMeasurementData/query"
    310316
    311         def query = "assayToken=$assay.assayUUID$tokenString"
     317                def query = "assayToken=$assay.assayUUID$tokenString"
    312318
    313319                def sampleTokens = [], measurementTokens = [], moduleData = []
    314320
    315         try {
    316             (sampleTokens, measurementTokens, moduleData) = moduleCommunicationService.callModuleMethod(moduleUrl, path, query, "POST")
    317         } catch (e) {
    318             throw new Exception("An error occured while trying to get the measurement data from the $assay.module.name. \
     321                try {
     322                        (sampleTokens, measurementTokens, moduleData) = moduleCommunicationService.callModuleMethod(moduleUrl, path, query, "POST")
     323                } catch (e) {
     324                        throw new Exception("An error occured while trying to get the measurement data from the $assay.module.name. \
    319325             This means the module containing the measurement data is not available right now. Please try again \
    320326             later or notify the system administrator if the problem persists. URL: $path?$query.")
    321         }
     327                }
    322328
    323329                if (!sampleTokens?.size()) return []
     
    350356                                        }  else {
    351357
    352                         def val
    353                         def measurement = moduleData[ valueIndex ]
    354 
    355                         if          (measurement == JSONObject.NULL)    val = ""
    356                         else if     (measurement instanceof Number)     val = measurement
    357                         else if     (measurement.isDouble())            val = measurement.toDouble()
    358                         else val =   measurement.toString()
     358                                                def val
     359                                                def measurement = moduleData[ valueIndex ]
     360
     361                                                if          (measurement == JSONObject.NULL)    val = ""
     362                                                else if     (measurement instanceof Number)     val = measurement
     363                                                else if     (measurement.isDouble())            val = measurement.toDouble()
     364                                                else val =   measurement.toString()
    359365                                                measurements << val
    360366                                        }
     
    463469
    464470        /**
     471         * Merges the data from multiple studies into a structure that can be exported to an excel file. The format for each assay is
     472         *
     473         *      [Category1:
     474         *      [Column1: [1,2,3], Column2: [4,5,6]],
     475         *   Category2:
     476         *      [Column3: [7,8,9], Column4: [10,11,12], Column5: [13,14,15]]]
     477         *
     478         * Where the category describes the category of data that is presented (e.g. subject, sample etc.) and the column names describe
     479         * the fields that are present. Each entry in the lists shows the value for that column for an entity. In this case, 3 entities are described.
     480         * Each field should give values for all entities, so the length of all value-lists should be the same.
     481         *
     482         * Example: If the following input is given (2 assays)
     483         *
     484         *      [
     485         *    [Category1:
     486         *      [Column1: [1,2,3], Column2: [4,5,6]],
     487         *     Category2:
     488         *      [Column3: [7,8,9], Column4: [10,11,12], Column5: [13,14,15]]],
     489         *    [Category1:
     490         *      [Column1: [16,17], Column6: [18,19]],
     491         *     Category3:
     492         *      [Column3: [20,21], Column8: [22,23]]]
     493         * ]
     494         *
     495         * the output will be (5 entries for each column, empty values for fields that don't exist in some assays)
     496         *
     497         *      [
     498         *    [Category1:
     499         *      [Column1: [1,2,3,16,17], Column2: [4,5,6,,], Column6: [,,,18,19]],
     500         *     Category2:
     501         *      [Column3: [7,8,9,,], Column4: [10,11,12,,], Column5: [13,14,15,,]],
     502         *     Category3:
     503         *      [Column3: [,,,20,21], Column8: [,,,22,23]]
     504         * ]
     505         *
     506         *
     507         * @param columnWiseAssayData   List with each entry being the column wise data of an assay. The format for each
     508         *                                                              entry is described above. The data MUST have a category named 'Sample Data' and in that map a field
     509         *                                                              named 'id'. This field is used for matching rows. However, the column is removed, unless
     510         *                                                              removeIdColumn is set to false
     511         * @param removeIdColumn                If set to true (default), the values for the sample id are removed from the output.
     512         * @return      Hashmap                         Combined assay data, in the same structure as each input entry. Empty values are given as an empty string.
     513         *                                                              So for input entries
     514         */
     515        def mergeColumnWiseDataOfMultipleStudiesForASetOfSamples(def columnWiseAssayData, boolean removeIdColumn = true ) {
     516                // Merge all assays and studies into one list
     517                def mergedData = mergeColumnWiseDataOfMultipleStudies( columnWiseAssayData )
     518
     519                // A map with keys being the sampleIds, and the values are the indices of that sample in the values list
     520                def idMap = [:]
     521               
     522                // A map with the key being an index in the value list, and the value is the index the values should be copied to
     523                def convertMap = [:]
     524
     525                for( int i = 0; i < mergedData[ "Sample Data" ][ "id" ].size(); i++ ) {
     526                        def id = mergedData[ "Sample Data" ][ "id" ][ i ];
     527
     528                        if( idMap[ id ] == null ) {
     529                                // This id occurs for the first time
     530                                idMap[ id ] = i;
     531                                convertMap[ i ] = i;
     532                        } else {
     533                                convertMap[ i ] = idMap[ id ];
     534                        }
     535                }
     536               
     537                /*
     538                 * Example output:
     539                 * idMap:      [ 12: 0, 24: 1, 26: 3 ]
     540                 * convertMap: [ 0: 0, 1: 1, 2: 0, 3: 3, 4: 3 ]
     541                 *   (meaning: rows 0, 1 and 3 should remain, row 2 should be merged with row 0 and row 4 should be merged with row 3)
     542                 *   
     543                 * The value in the convertMap is always lower than its key. So we sort the convertMap on the keys. That way, we can
     544                 * loop through the values and remove the row that has been merged.
     545                 */
     546               
     547                convertMap.sort { a, b -> b.key <=> a.key }.each {
     548                        def row = it.key;
     549                        def mergeWith = it.value;
     550                       
     551                        if( row != mergeWith ) {
     552                                // Combine the data on row [row] with the data on row [mergeWith]
     553                               
     554                                mergedData.each {
     555                                        def cat = it.key; def fields = it.value;
     556                                        fields.each { fieldData ->
     557                                                def fieldName = fieldData.key;
     558                                                def fieldValues = fieldData.value;
     559                                               
     560                                                // If one of the fields to merge is empty, use the other one
     561                                                // Otherwise the values should be the same (e.g. study, subject, sample data)
     562                                                fieldValues[ mergeWith ] = ( fieldValues[ mergeWith ] == null || fieldValues[ mergeWith ] == "" ) ? fieldValues[ row ] : fieldValues[ mergeWith ]
     563                                               
     564                                                // Remove the row from this list
     565                                                fieldValues.remove( row );
     566                                        }
     567                                }
     568                        }
     569                }
     570               
     571                // Remove sample id if required
     572                if( removeIdColumn )
     573                        mergedData[ "Sample Data" ].remove( "id" );
     574               
     575                return mergedData
     576        }
     577
     578        /**
    465579         * Converts column
    466580         * @param columnData multidimensional map containing column data.
     
    564678        /**
    565679         * Export row wise data in CSV to a stream. All values are surrounded with
    566     * double quotes (" ").
     680        * double quotes (" ").
    567681         *
    568682         * @param rowData List of lists containing for each row all cell values
     
    572686        def exportRowWiseDataToCSVFile(rowData, outputStream, outputDelimiter = '\t', locale = java.util.Locale.US) {
    573687
    574         def formatter = NumberFormat.getNumberInstance(locale)
    575         formatter.setGroupingUsed false // we don't want grouping (thousands) separators
    576 
    577         outputStream << rowData.collect { row ->
    578           row.collect{
    579 
    580               // omit quotes in case of numeric values and format using chosen locale
    581               if (it instanceof Number) return formatter.format(it)
    582 
    583               def s = it?.toString() ?: ''
    584 
    585               def addQuotes = false
    586 
    587               // escape double quotes with double quotes if they exist and
    588               // enable surround with quotes
    589               if (s.contains('"')) {
    590                   addQuotes = true
    591                   s = s.replaceAll('"','""')
    592               } else {
    593                   // enable surround with quotes in case of comma's
    594                   if (s.contains(',') || s.contains('\n')) addQuotes = true
    595               }
    596 
    597               addQuotes ? "\"$s\"" : s
    598 
    599           }.join(outputDelimiter)
    600         }.join('\n')
     688                def formatter = NumberFormat.getNumberInstance(locale)
     689                formatter.setGroupingUsed false // we don't want grouping (thousands) separators
     690
     691                outputStream << rowData.collect { row ->
     692                        row.collect{
     693
     694                                // omit quotes in case of numeric values and format using chosen locale
     695                                if (it instanceof Number) return formatter.format(it)
     696
     697                                def s = it?.toString() ?: ''
     698
     699                                def addQuotes = false
     700
     701                                // escape double quotes with double quotes if they exist and
     702                                // enable surround with quotes
     703                                if (s.contains('"')) {
     704                                        addQuotes = true
     705                                        s = s.replaceAll('"','""')
     706                                } else {
     707                                        // enable surround with quotes in case of comma's
     708                                        if (s.contains(',') || s.contains('\n')) addQuotes = true
     709                                }
     710
     711                                addQuotes ? "\"$s\"" : s
     712
     713                        }.join(outputDelimiter)
     714                }.join('\n')
    601715
    602716                outputStream.close()
Note: See TracChangeset for help on using the changeset viewer.