Don't crash if there are no lane result summary entries when rendering
[htsworkflow.git] / htsworkflow / frontend / samples / views.py
1 # Create your views here.
2 from htsworkflow.frontend.experiments.models import FlowCell
3 from htsworkflow.frontend.samples.changelist import ChangeList
4 from htsworkflow.frontend.samples.models import Library
5 from htsworkflow.frontend.samples.results import get_flowcell_result_dict, parse_flowcell_id
6 from htsworkflow.frontend.bcmagic.forms import BarcodeMagicForm
7 from htsworkflow.pipelines.runfolder import load_pipeline_run_xml
8 from htsworkflow.pipelines import runfolder
9 from htsworkflow.pipelines.eland import ResultLane
10 from htsworkflow.frontend import settings
11 from htsworkflow.util import makebed
12 from htsworkflow.util import opener
13
14 from django.core.exceptions import ObjectDoesNotExist
15 from django.http import HttpResponse, HttpResponseRedirect
16 from django.shortcuts import render_to_response
17 from django.template import RequestContext
18 from django.template.loader import get_template
19 from django.contrib.auth.decorators import login_required
20
21 import StringIO
22 import logging
23 import os
24
25 LANE_LIST = [1,2,3,4,5,6,7,8]
26 SAMPLES_CONTEXT_DEFAULTS = {
27     'app_name': 'Flowcell/Library Tracker',
28     'bcmagic': BarcodeMagicForm()
29 }
30
31 def create_library_context(cl):
32     """
33     Create a list of libraries that includes how many lanes were run
34     """
35     records = []
36     #for lib in library_items.object_list:
37     for lib in cl.result_list:
38        summary = {}
39        summary['library_id'] = lib.library_id
40        summary['library_name'] = lib.library_name
41        summary['species_name' ] = lib.library_species.scientific_name
42        if lib.amplified_from_sample is not None:
43            summary['amplified_from'] = lib.amplified_from_sample.library_id
44        else:
45            summary['amplified_from'] = ''
46        lanes_run = 0
47        #for lane_id in LANE_LIST:
48        #    lane = getattr(lib, 'lane_%d_library' % (lane_id,))
49        #    lanes_run += len( lane.all() )
50        lanes_run = lib.lane_set.count()
51        summary['lanes_run'] = lanes_run
52        summary['is_archived'] = lib.is_archived()
53        records.append(summary)
54     cl.result_count = unicode(cl.paginator._count) + u" libraries"
55     return {'library_list': records }
56
57 def library(request):
58    # build changelist
59     fcl = ChangeList(request, Library,
60         list_filter=['affiliations', 'library_species'],
61         search_fields=['library_id', 'library_name', 'amplified_from_sample__library_id'],
62         list_per_page=200,
63         queryset=Library.objects.filter(hidden__exact=0)
64     )
65
66     context = { 'cl': fcl, 'title': 'Library Index'}
67     context.update(create_library_context(fcl))
68     t = get_template('samples/library_index.html')
69     c = RequestContext(request, context)
70     
71     app_context = {
72         'page_name': 'Library Index',
73         'east_region_config_div': 'changelist-filter',
74         'body': t.render(c)
75     }
76     app_context.update(SAMPLES_CONTEXT_DEFAULTS)
77     
78     app_t = get_template('flowcell_libraries_app.html')
79     app_c = RequestContext(request, app_context)
80     return HttpResponse( app_t.render(app_c) )
81
82 def library_to_flowcells(request, lib_id):
83     """
84     Display information about all the flowcells a library has been run on.
85     """
86     
87     try:
88       lib = Library.objects.get(library_id=lib_id)
89     except:
90       return HttpResponse("Library %s does not exist" % (lib_id))
91    
92     flowcell_list = []
93     interesting_flowcells = {} # aka flowcells we're looking at
94     #for lane in LANE_LIST:
95     for lane in lib.lane_set.all():
96         #lane_library = getattr(lib, 'lane_%d_library' % (lane,))
97         #for fc in lane_library.all():
98         fc = lane.flowcell
99         flowcell_id, id = parse_flowcell_id(fc.flowcell_id)
100         if flowcell_id not in interesting_flowcells:
101             interesting_flowcells[flowcell_id] = get_flowcell_result_dict(flowcell_id)
102         flowcell_list.append((fc.flowcell_id, lane.lane_number))
103
104     flowcell_list.sort()
105     
106     lane_summary_list = []
107     eland_results = []
108     for fc, lane_number in flowcell_list:
109         lane_summary, err_list = _summary_stats(fc, lane_number)
110
111         eland_results.extend(_make_eland_results(fc, lane_number, interesting_flowcells))
112         lane_summary_list.extend(lane_summary)
113
114     context = {
115         'page_name': 'Library Details',
116         'lib': lib,
117         'eland_results': eland_results,
118         'lane_summary_list': lane_summary_list,
119     }
120     context.update(SAMPLES_CONTEXT_DEFAULTS)
121
122     return render_to_response(
123         'samples/library_detail.html',
124         context,
125         context_instance = RequestContext(request))
126
127 def summaryhtm_fc_cnm(request, flowcell_id, cnm):
128     """
129     returns a Summary.htm file if it exists.
130     """
131     fc_id, status = parse_flowcell_id(flowcell_id)
132     d = get_flowcell_result_dict(fc_id)
133     
134     if d is None:
135         return HttpResponse('<b>Results for Flowcell %s not found.</b>' % (fc_id))
136     
137     if cnm not in d:
138         return HttpResponse('<b>Results for Flowcell %s; %s not found.</b>' % (fc_id, cnm))
139     
140     summary_filepath = d[cnm]['summary']
141     
142     if summary_filepath is None:
143         return HttpResponse('<b>Summary.htm for Flowcell %s; %s not found.</b>' % (fc_id, cnm))
144     
145     f = open(summary_filepath, 'r')
146     
147     return HttpResponse(f)
148
149
150 def result_fc_cnm_eland_lane(request, flowcell_id, cnm, lane):
151     """
152     returns an eland_file upon calling.
153     """
154     fc_id, status = parse_flowcell_id(flowcell_id)
155     d = get_flowcell_result_dict(fc_id)
156     
157     if d is None:
158         return HttpResponse('<b>Results for Flowcell %s not found.</b>' % (fc_id))
159     
160     if cnm not in d:
161         return HttpResponse('<b>Results for Flowcell %s; %s not found.</b>' % (fc_id, cnm))
162     
163     erd = d[cnm]['eland_results']
164     lane = int(lane)
165     
166     if lane not in erd:
167         return HttpResponse('<b>Results for Flowcell %s; %s; lane %s not found.</b>' % (fc_id, cnm, lane))
168     
169     filepath = erd[lane]
170     
171     #f = opener.autoopen(filepath, 'r')
172     # return HttpResponse(f, mimetype="application/x-elandresult")
173
174     f = open(filepath, 'r')
175     return HttpResponse(f, mimetype='application/x-bzip2')
176     
177
178
179 def bedfile_fc_cnm_eland_lane_ucsc(request, fc_id, cnm, lane):
180     """
181     returns a bed file for a given flowcell, CN-M (i.e. C1-33), and lane (ucsc compatible)
182     """
183     return bedfile_fc_cnm_eland_lane(request, fc_id, cnm, lane, ucsc_compatible=True)
184
185
186 def bedfile_fc_cnm_eland_lane(request, flowcell_id, cnm, lane, ucsc_compatible=False):
187     """
188     returns a bed file for a given flowcell, CN-M (i.e. C1-33), and lane
189     """
190     fc_id, status = parse_flowcell_id(flowcell_id)
191     d = get_flowcell_result_dict(fc_id)
192     
193     if d is None:
194         return HttpResponse('<b>Results for Flowcell %s not found.</b>' % (fc_id))
195     
196     if cnm not in d:
197         return HttpResponse('<b>Results for Flowcell %s; %s not found.</b>' % (fc_id, cnm))
198     
199     erd = d[cnm]['eland_results']
200     lane = int(lane)
201     
202     if lane not in erd:
203         return HttpResponse('<b>Results for Flowcell %s; %s; lane %s not found.</b>' % (fc_id, cnm, lane))
204     
205     filepath = erd[lane]
206     
207     # Eland result file
208     fi = opener.autoopen(filepath, 'r')
209     # output memory file
210     
211     name, description = makebed.make_description( fc_id, lane )
212     
213     bedgen = makebed.make_bed_from_eland_generator(fi, name, description)
214     
215     if ucsc_compatible:
216         return HttpResponse(bedgen)
217     else:
218         return HttpResponse(bedgen, mimetype="application/x-bedfile")
219
220
221 def _summary_stats(flowcell_id, lane_id):
222     """
223     Return the summary statistics for a given flowcell, lane, and end.
224     """
225     fc_id, status = parse_flowcell_id(flowcell_id)
226     fc_result_dict = get_flowcell_result_dict(fc_id)
227
228     summary_list = []
229     err_list = []
230     
231     if fc_result_dict is None:
232         err_list.append('Results for Flowcell %s not found.' % (fc_id))
233         return (summary_list, err_list)
234
235     for cycle_width in fc_result_dict:
236         xmlpath = fc_result_dict[cycle_width]['run_xml']
237         
238         if xmlpath is None:
239             err_list.append('Run xml for Flowcell %s(%s) not found.' % (fc_id, cycle_width))
240             continue
241         
242         run = load_pipeline_run_xml(xmlpath)
243         gerald_summary = run.gerald.summary.lane_results
244         for end in range(len(gerald_summary)):
245             end_summary = run.gerald.eland_results.results[end]
246             if end_summary.has_key(lane_id):
247                 eland_summary = run.gerald.eland_results.results[end][lane_id]
248             else:
249                 eland_summary = ResultLane(lane_id=lane_id, end=end)
250             # add information to lane_summary
251             eland_summary.flowcell_id = flowcell_id
252             if len(gerald_summary) > end and gerald_summary[end].has_key(lane_id):
253                 eland_summary.clusters = gerald_summary[end][lane_id].cluster
254             else:
255                 eland_summary.clusters = 'n/a'
256             eland_summary.cycle_width = cycle_width
257             if hasattr(eland_summary, 'genome_map'):
258                 eland_summary.summarized_reads = runfolder.summarize_mapped_reads( 
259                                                    eland_summary.genome_map, 
260                                                    eland_summary.mapped_reads)
261
262             # grab some more information out of the flowcell db
263             flowcell = FlowCell.objects.get(flowcell_id=flowcell_id)
264             #pm_field = 'lane_%d_pM' % (lane_id)
265             lane_obj = flowcell.lane_set.get(lane_number=lane_id)
266             eland_summary.successful_pm = lane_obj.pM
267
268             summary_list.append(eland_summary)
269
270         #except Exception, e:
271         #    summary_list.append("Summary report needs to be updated.")
272         #    logging.error("Exception: " + str(e))
273     
274     return (summary_list, err_list)
275
276 def _summary_stats_old(flowcell_id, lane):
277     """
278     return a dictionary of summary stats for a given flowcell_id & lane.
279     """
280     fc_id, status = parse_flowcell_id(flowcell_id)
281     fc_result_dict = get_flowcell_result_dict(fc_id)
282     
283     dict_list = []
284     err_list = []
285     summary_list = []
286     
287     if fc_result_dict is None:
288         err_list.append('Results for Flowcell %s not found.' % (fc_id))
289         return (dict_list, err_list, summary_list)
290     
291     for cnm in fc_result_dict:
292     
293         xmlpath = fc_result_dict[cnm]['run_xml']
294         
295         if xmlpath is None:
296             err_list.append('Run xml for Flowcell %s(%s) not found.' % (fc_id, cnm))
297             continue
298         
299         tree = ElementTree.parse(xmlpath).getroot()
300         results = runfolder.PipelineRun(pathname='', xml=tree)
301         try:
302             lane_report = runfolder.summarize_lane(results.gerald, lane)
303             summary_list.append(os.linesep.join(lane_report))
304         except Exception, e:
305             summary_list.append("Summary report needs to be updated.")
306             logging.error("Exception: " + str(e))
307        
308         print "----------------------------------"
309         print "-- DOES NOT SUPPORT PAIRED END ---"
310         print "----------------------------------"
311         lane_results = results.gerald.summary[0][lane]
312         lrs = lane_results
313         
314         d = {}
315         
316         d['average_alignment_score'] = lrs.average_alignment_score
317         d['average_first_cycle_intensity'] = lrs.average_first_cycle_intensity
318         d['cluster'] = lrs.cluster
319         d['lane'] = lrs.lane
320         d['flowcell'] = flowcell_id
321         d['cnm'] = cnm
322         d['percent_error_rate'] = lrs.percent_error_rate
323         d['percent_intensity_after_20_cycles'] = lrs.percent_intensity_after_20_cycles
324         d['percent_pass_filter_align'] = lrs.percent_pass_filter_align
325         d['percent_pass_filter_clusters'] = lrs.percent_pass_filter_clusters
326         
327         #FIXME: function finished, but need to take advantage of
328         #   may need to take in a list of lanes so we only have to
329         #   load the xml file once per flowcell rather than once
330         #   per lane.
331         dict_list.append(d)
332     
333     return (dict_list, err_list, summary_list)
334     
335     
336 def get_eland_result_type(pathname):
337     """
338     Guess the eland result file type from the filename
339     """
340     path, filename = os.path.split(pathname)
341     if 'extended' in filename:
342         return 'extended'
343     elif 'multi' in filename:
344         return 'multi'
345     elif 'result' in filename:
346         return 'result'
347     else:
348         return 'unknown'
349
350 def _make_eland_results(flowcell_id, lane, interesting_flowcells):
351     fc_id, status = parse_flowcell_id(flowcell_id)
352     cur_fc = interesting_flowcells.get(fc_id, None)
353     if cur_fc is None:
354       return []
355
356     flowcell = FlowCell.objects.get(flowcell_id=flowcell_id)
357     # Loop throw storage devices if a result has been archived
358     storage_id_list = []
359     if cur_fc is not None:
360         for lts in flowcell.longtermstorage_set.all():
361             for sd in lts.storage_devices.all():
362                 # Use barcode_id if it exists
363                 if sd.barcode_id is not None and sd.barcode_id != '':
364                     storage_id_list.append(sd.barcode_id)
365                 # Otherwise use UUID
366                 else:
367                     storage_id_list.append(sd.uuid)
368         
369     # Formatting for template use
370     if len(storage_id_list) == 0:
371         storage_ids = None
372     else:
373         storage_ids = ', '.join([ '<a href="/inventory/%s/">%s</a>' % (s,s) for s in storage_id_list ])
374
375     results = []
376     for cycle in cur_fc.keys():
377         result_path = cur_fc[cycle]['eland_results'].get(lane, None)
378         result_link = make_result_link(fc_id, cycle, lane, result_path)
379         results.append({'flowcell_id': fc_id,
380                         'cycle': cycle, 
381                         'lane': lane, 
382                         'summary_url': make_summary_url(flowcell_id, cycle),
383                         'result_url': result_link[0],
384                         'result_label': result_link[1],
385                         'bed_url': result_link[2],
386                         'storage_ids': storage_ids
387         })
388     return results
389
390 def make_summary_url(flowcell_id, cycle_name):
391     url = '/results/%s/%s/summary/' % (flowcell_id, cycle_name)
392     return url
393
394 def make_result_link(flowcell_id, cycle_name, lane, eland_result_path):
395     if eland_result_path is None:
396         return ("", "", "")
397
398     result_type = get_eland_result_type(eland_result_path)
399     result_url = '/results/%s/%s/eland_result/%s' % (flowcell_id, cycle_name, lane)
400     result_label = 'eland %s' % (result_type,)
401     bed_url = None
402     if result_type == 'result':
403        bed_url_pattern = '/results/%s/%s/bedfile/%s'
404        bed_url = bed_url_pattern % (flowcell_id, cycle_name, lane)
405     
406     return (result_url, result_label, bed_url)
407
408 def _files(flowcell_id, lane):
409     """
410     Sets up available files for download
411     """
412     lane = int(lane)
413
414     flowcell_id, id = parse_flowcell_id(flowcell_id)
415     d = get_flowcell_result_dict(flowcell_id)
416     
417     if d is None:
418         return ''
419     
420     output = []
421     
422     # c_name == 'CN-M' (i.e. C1-33)
423     for c_name in d:
424         
425         if d[c_name]['summary'] is not None:
426             output.append('<a href="/results/%s/%s/summary/">summary(%s)</a>' \
427                           % (flowcell_id, c_name, c_name))
428         
429         erd = d[c_name]['eland_results']
430         if lane in erd:
431             result_type = get_eland_result_type(erd[lane])
432             result_url_pattern = '<a href="/results/%s/%s/eland_result/%s">eland %s(%s)</a>'
433             output.append(result_url_pattern % (flowcell_id, c_name, lane, result_type, c_name))
434             if result_type == 'result':
435                 bed_url_pattern = '<a href="/results/%s/%s/bedfile/%s">bedfile(%s)</a>'
436                 output.append(bed_url_pattern % (flowcell_id, c_name, lane, c_name))
437     
438     if len(output) == 0:
439         return ''
440     
441     return '(' + '|'.join(output) + ')'
442
443 def library_id_to_admin_url(request, lib_id):
444     lib = Library.objects.get(library_id=lib_id)
445     return HttpResponseRedirect('/admin/samples/library/%s' % (lib.id,))
446
447 @login_required
448 def user_profile(request):
449     """
450     Information about the user
451     """
452     context = {
453                 'page_name': 'User Profile',
454                 'media': '',
455                 #'bcmagic': BarcodeMagicForm(),
456                 #'select': 'settings',
457             }
458     context.update(SAMPLES_CONTEXT_DEFAULTS)
459     return render_to_response('registration/profile.html', context,
460                               context_instance=RequestContext(request))