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