source: trunk/grails-app/domain/nl/tno/massSequencing/AssaySample.groovy @ 52

Last change on this file since 52 was 52, checked in by robert@…, 8 years ago
  • Updated a bug on production with org.apache.tools.zip
  • Added tooltips to all icons
File size: 8.7 KB
Line 
1package nl.tno.massSequencing
2
3/**
4 * Represents a samples that is used in an assay.
5 *
6 * @author Robert Horlings (robert@isdat.nl)
7 *
8 */
9class AssaySample {
10        // Grails datasource is used for executing custom SQL statement
11        def dataSource
12       
13        // To be computed at run time
14        private long _numSequences = -1;
15        private float _averageQuality = -1.0;
16        private long _numQualScores = -1;
17        private long _numSequenceFiles = -1;
18        private long _numQualityFiles = -1;
19       
20        Integer numUniqueSequences      // Number of unique sequences / OTUs. Is only available after preprocessing
21
22        String fwOligo
23        String fwMidName
24        String fwTotalSeq
25        String fwMidSeq
26        String fwPrimerSeq
27       
28        String revOligo
29        String revMidName
30        String revTotalSeq
31        String revMidSeq
32        String revPrimerSeq
33
34        static belongsTo  = [ assay: Assay, sample: Sample, run: Run ]
35        static hasMany    = [ sequenceData: SequenceData ]
36
37        static constraints = {
38                numUniqueSequences(nullable: true)
39                fwOligo(nullable: true)
40                fwMidName(nullable: true)
41                fwTotalSeq(nullable:true)
42                fwMidSeq(nullable:true)
43                fwPrimerSeq(nullable:true)
44                revOligo(nullable: true)
45                revMidName(nullable: true)
46                revTotalSeq(nullable:true)
47                revMidSeq(nullable:true)
48                revPrimerSeq(nullable:true)
49                run(nullable: true);
50        }
51
52        static mapping = {
53                columns {
54                        numSequences index:'numsequences_idx'
55                }
56                sequenceData cascade: "all-delete-orphan"
57                sample fetch: 'join'
58                assay fetch: 'join'
59                run fetch: 'join'
60        }
61
62        /**
63         * Returns the number of files in the system, belonging to this
64         * assay-sample combination.
65         *
66         * @return
67         */
68        public int numFiles() {
69                return numSequenceFiles() + numQualityFiles(); 
70        }
71
72        /**
73         * Returns the number of sequence files in the system, belonging to this
74         * assay-sample combination.
75         *
76         * @return
77         */
78        public int numSequenceFiles() {
79                if( _numSequenceFiles > -1 )
80                        return _numSequenceFiles;
81
82                if( !sequenceData )
83                        return 0
84
85                int numFiles = 0;
86                sequenceData.each {
87                        if( it.sequenceFile )
88                                numFiles++
89                }
90
91                return numFiles;
92        }
93       
94        /**
95         * Returns the number of quality files in the system, belonging to this
96         * assay-sample combination.
97         *
98         * @return
99         */
100        public int numQualityFiles() {
101                if( _numQualityFiles > -1 )
102                        return _numQualityFiles;
103               
104                if( !sequenceData )
105                        return 0
106
107                int numFiles = 0;
108                sequenceData.each {
109                        if( it.qualityFile )
110                                numFiles++
111                }
112
113                return numFiles;
114        }
115       
116        /**
117         * Returns the number of sequences in the files on the system, belonging to this
118         * assay-sample combination.
119         *
120         * @return
121         */
122        public long numSequences() {
123                if( _numSequences > -1 )
124                        return _numSequences;
125
126                if( !sequenceData )
127                        return 0
128
129                long numSequences = 0;
130                sequenceData.each { numSequences += it.numSequences }
131
132                // Save as cache
133                _numSequences = numSequences;
134
135                return numSequences;
136        }
137       
138        /**
139         * Returns the number of quality scores in the files on the system, belonging to this
140         * assay-sample combination.
141         *
142         * @return
143         */
144        public long numQualScores() {
145                if( _numQualScores > -1 )
146                        return _numQualScores;
147
148                if( !sequenceData )
149                        return 0
150
151                long numQualScores = 0;
152                sequenceData.each { numQualScores += it.numQualScores() }
153
154                // Save as cache
155                _numQualScores = numQualScores;
156
157                return numQualScores;
158        }
159        /**
160         * Returns the average quality of the sequences in the files on the system,
161         * belonging to this assay-sample combination.
162         *
163         * @return
164         */
165        public float averageQuality() {
166                if( _averageQuality > -1 )
167                        return _averageQuality;
168
169                if( !sequenceData )
170                        return 0.0
171
172                int numSequences = 0;
173                float averageQuality = 0.0;
174
175                sequenceData.each {
176                        numSequences += it.numSequences
177                        averageQuality = averageQuality + ( it.averageQuality - averageQuality ) / numSequences * it.numSequences;
178                }
179
180                // Save as cache
181                _averageQuality = averageQuality;
182
183                return averageQuality;
184        }
185       
186        /**
187         * Reset the statistics to their default value, in order to ensure that the values are recomputed next time.
188         */
189        public void resetStats() {
190                _numSequences = -1;
191                _numQualScores = -1;
192                _averageQuality = -1;
193               
194                _numSequenceFiles = -1;
195                _numQualityFiles = -1;
196        }
197       
198        /**
199         * Fill statistics for multiple assaySamples at once.
200         * 
201         * This method is used to improve performance. If multiple assaysamples are shown on the screen
202         * and all have to collect the statistics, it is done with n queries (where n is the number of assaysamples)
203         * This method reduces this number to 1
204         * 
205         * @param assaySamples  List of assaySamples to collect the statistics for
206         */
207        public static void initStats( ArrayList<AssaySample> assaySamples ) {
208                if( !assaySamples )
209                        return;
210               
211                // Order assaysamples by id
212                assaySamples.sort { it.id }
213               
214                // Retrieve the datasource for these assaysamples
215                groovy.sql.Sql sql = new groovy.sql.Sql(assaySamples[0].dataSource)
216
217                // Execute a custom query
218                String ids = assaySamples.id*.toString().join( ', ' );
219                String sqlStatement = """
220                        SELECT
221                                a.id,
222                                count( s.sequence_file ) AS numSequenceFiles,
223                                count( s.quality_file ) AS numQualityFiles,
224                                sum( s.num_sequences ) AS numSequences,
225                                sum( CASE WHEN s.quality_file IS NOT NULL THEN s.num_sequences ELSE 0 END ) AS numQualScores
226                        FROM assay_sample a
227                        LEFT JOIN sequence_data s ON a.id = s.sample_id
228                        WHERE a.id IN (${ids})
229                        GROUP BY a.id
230                        ORDER BY a.id
231                """
232               
233                // For each assaysample, one row is returned. In order to prevent lookups in
234                // the asasySamples list, both lists are sorted by id. For that reason, we can just
235                // walk through both lists simultaneously
236                def listIndex = 0;
237                sql.eachRow( sqlStatement ) {
238                        // Still, we perform a check to see whether the ids match. If they don't
239                        // something went wrong in the database or in grails. We note an error, and
240                        // skip the assignment. The variables will be filled later on anyhow, it
241                        // will only be a little bit slower
242                        if( it.id != assaySamples[ listIndex ]?.id ) {
243                                log.error "ID of the database row and the domain object don't match. DB: " + it.id + ", Domain object: " + assaySamples[ listIndex ]?.id
244                        } else {
245                                assaySamples[ listIndex ]._numSequences = it.numSequences ?: 0;
246                                assaySamples[ listIndex ]._numQualScores = it.numQualScores ?: 0;
247                                assaySamples[ listIndex ]._numSequenceFiles = it.numSequenceFiles ?: 0;
248                                assaySamples[ listIndex ]._numQualityFiles = it.numQualityFiles ?: 0;
249                        }
250                       
251                        listIndex++;
252                }
253        }
254       
255        /**
256         * Check whether this assay-sample combination contains information that should be saved in trash on a delete
257         * @return
258         */
259        public boolean containsData() {
260                return  fwOligo || fwMidName || fwTotalSeq || fwMidSeq || fwPrimerSeq ||
261                                revOligo || revMidName || revTotalSeq || revMidSeq || revPrimerSeq ||
262                                numFiles() > 0;
263        }
264       
265        /**
266         * Move information that should be kept on delete to another assaySample object.
267         * 
268         * N.B. The sequencedata objects are really moved, so removed from the original object!
269         * 
270         * @param otherAssaySample      Object to move
271         */
272        public void moveValuableDataTo( AssaySample otherAssaySample ) {
273                // Copy properties
274                otherAssaySample.fwOligo                = fwOligo;
275                otherAssaySample.fwMidName              = fwMidName;
276                otherAssaySample.fwTotalSeq     = fwTotalSeq;
277                otherAssaySample.fwMidSeq               = fwMidSeq;
278                otherAssaySample.fwPrimerSeq    = fwPrimerSeq;
279               
280                otherAssaySample.revOligo               = revOligo;
281                otherAssaySample.revMidName             = revMidName;
282                otherAssaySample.revTotalSeq    = revTotalSeq;
283                otherAssaySample.revMidSeq              = revMidSeq;
284                otherAssaySample.revPrimerSeq   = revPrimerSeq;
285
286                // Move attached data
287                def dataList = [] + sequenceData?.toList()
288                def otherAssay = otherAssaySample.assay;
289               
290                if( dataList && dataList.size() > 0 ) {
291                        for( def j = dataList.size() - 1; j >= 0; j-- ) {
292                                // Copy data to a new sequencedata object.
293                                // Just moving the sequencedata object to the other assay sample resulted
294                                // in a 'deleted object would be re-saved by cascade' exception
295                               
296                                if( dataList[ j ] ) {
297                                        // Clone the sequencedata object
298                                        def sd = dataList[ j ]?.clone();
299                                       
300                                        if( sd )
301                                                otherAssaySample.addToSequenceData( sd );
302                                               
303                                        // Remove the old sequencedata object
304                                        this.removeFromSequenceData( dataList[ j ] );
305                                }
306                        }
307                }
308
309                // Copy run properties
310                if( otherAssaySample.run ) {
311                        otherAssaySample.run.removeFromAssaySamples( otherAssaySample );
312                }
313               
314                // Remove this sample from the run.
315                if( run ) {
316                        def copyRun = run;
317                        copyRun.removeFromAssaySamples( this );
318                        copyRun.addToAssaySamples( otherAssaySample );
319                } else {
320                        otherAssaySample.run = null;
321                }
322        }
323       
324       
325        /**
326         * Delete all sequences from a sample
327         * @param assaySample
328         * @return
329         */
330        public int deleteSequenceData() {
331                if( !sequenceData )
332                        return 0;
333               
334                def numFiles = 0;
335                sequenceData.each { sequenceData ->
336                        numFiles += sequenceData.numFiles();
337                 
338                        removeFromSequenceData( sequenceData );
339                        sequenceData.delete(flush:true);
340                }
341
342                resetStats();
343                save();
344               
345                return numFiles;
346        }
347
348}
Note: See TracBrowser for help on using the repository browser.