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