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