def parse_reports(self):
    """ Find Picard WgsMetrics reports and parse their data """

    # Set up vars
    self.picard_wgsmetrics_data = dict()
    self.picard_wgsmetrics_histogram = dict()
    self.picard_wgsmetrics_samplestats = dict()

    # Go through logs and find Metrics
    for f in self.find_log_files('picard/wgs_metrics', filehandles=True):
        s_name = None
        in_hist = False
        for l in f['f']:

            # Catch the histogram values
            if s_name is not None and in_hist is True:
                try:
                    sections = l.split("\t")
                    cov = int(sections[0])
                    count = int(sections[1])
                    self.picard_wgsmetrics_histogram[s_name][cov] = count
                except ValueError:
                    # Reset in case we have more in this log file
                    s_name = None
                    in_hist = False

            # New log starting
            if 'WgsMetrics' in l and 'INPUT' in l:
                s_name = None
                # Pull sample name from input
                fn_search = re.search(r"INPUT(?:=|\s+)(\[?[^\s]+\]?)", l, flags=re.IGNORECASE)
                if fn_search:
                    s_name = os.path.basename(fn_search.group(1).strip('[]'))
                    s_name = self.clean_s_name(s_name, f['root'])

            if s_name is not None:
                if 'CollectWgsMetrics$WgsMetrics' in l and '## METRICS CLASS' in l:
                    if s_name in self.picard_wgsmetrics_data:
                        log.debug("Duplicate sample name found in {}! Overwriting: {}".format(f['fn'], s_name))
                    self.add_data_source(f, s_name, section='WgsMetrics')
                    self.picard_wgsmetrics_data[s_name] = dict()
                    keys = f['f'].readline().strip("\n").split("\t")
                    vals = f['f'].readline().strip("\n").split("\t")
                    if len(vals) == len(keys):
                        for i, k in enumerate(keys):
                            try:
                                self.picard_wgsmetrics_data[s_name][k] = float(vals[i])
                            except ValueError:
                                self.picard_wgsmetrics_data[s_name][k] = vals[i]

                    # Skip lines on to histogram
                    next(f['f'])
                    next(f['f'])
                    next(f['f'])

                    self.picard_wgsmetrics_histogram[s_name] = OrderedDict()
                    in_hist = True

        for key in list(self.picard_wgsmetrics_data.keys()):
            if len(self.picard_wgsmetrics_data[key]) == 0:
                self.picard_wgsmetrics_data.pop(key, None)
        for s_name in list(self.picard_wgsmetrics_histogram.keys()):
            if len(self.picard_wgsmetrics_histogram[s_name]) == 0:
                self.picard_wgsmetrics_histogram.pop(s_name, None)
                log.debug("Ignoring '{}' histogram as no data parsed".format(s_name))

    # Filter to strip out ignored sample names
    self.picard_wgsmetrics_data = self.ignore_samples(self.picard_wgsmetrics_data)

    if len(self.picard_wgsmetrics_data) > 0:

        # Write parsed data to a file
        self.write_data_file(self.picard_wgsmetrics_data, 'multiqc_picard_wgsmetrics')

        # Add to general stats table
        self.general_stats_headers['MEDIAN_COVERAGE'] = {
            'title': 'Median Coverage',
            'description': 'The median coverage in bases of the genome territory, after all filters are applied.',
            'min': 0,
            'suffix': 'X',
            'scale': 'GnBu',
        }
        self.general_stats_headers['MEAN_COVERAGE'] = {
            'title': 'Mean Coverage',
            'description': 'The mean coverage in bases of the genome territory, after all filters are applied.',
            'min': 0,
            'suffix': 'X',
            'scale': 'GnBu',
            'hidden': True,
        }
        self.general_stats_headers['SD_COVERAGE'] = {
            'title': 'Median Coverage',
            'description': 'The standard deviation coverage in bases of the genome territory, after all filters are applied.',
            'min': 0,
            'suffix': 'X',
            'scale': 'GnBu',
            'hidden': True,
        }
        # user configurable coverage level
        try:
            covs = config.picard_config['general_stats_target_coverage']
            assert type(covs) == list
            assert len(covs) > 0
            covs = [str(i) for i in covs]
            log.debug("Custom Picard coverage thresholds: {}".format(", ".join([i for i in covs])))
        except (AttributeError, TypeError, AssertionError):
            covs = ['30']
        for c in covs:
            self.general_stats_headers['PCT_{}X'.format(c)] = {
                'id': 'picard_target_bases_{}X'.format(c),
                'title': 'Bases &ge; {}X'.format(c),
                'description': 'Percent of target bases with coverage &ge; {}X'.format(c),
                'max': 100,
                'min': 0,
                'suffix': '%',
                'format': '{:,.0f}',
                'scale': 'RdYlGn',
                'modify': lambda x: self.multiply_hundred(x)
            }

        for s_name in self.picard_wgsmetrics_data:
            if s_name not in self.general_stats_data:
                self.general_stats_data[s_name] = dict()
            self.general_stats_data[s_name].update( self.picard_wgsmetrics_data[s_name] )

        # Section with histogram plot
        if len(self.picard_wgsmetrics_histogram) > 0:

            # Figure out where to cut histogram tail
            max_cov = 10
            for s_name, samp in self.picard_wgsmetrics_histogram.items():
                total = float( sum( samp.values() ) )
                running_total = 0
                for k, v in samp.items():
                    running_total += v
                    if running_total > total * 0.99:
                        max_cov = max(k, max_cov)
                        break

            # Cut histogram tail and make a normalised percentage version of the data plus dropoff
            data = {}
            data_percent = {}
            maxval = 0
            for s_name, samp in self.picard_wgsmetrics_histogram.items():
                data[s_name] = OrderedDict()
                data_percent[s_name] = OrderedDict()
                total = float( sum( samp.values() ) )
                cumulative = 0
                for k, v in samp.items():
                    if k <= max_cov:
                        cumulative += v
                        data[s_name][k] = v
                        maxval = max(maxval, v)
                        data_percent[s_name][k] = 100 - (cumulative/total)*100
                    else:
                        break

            # Plot the data and add section
            pconfig = {
                'id': 'picard_wgs_metrics_histogram',
                'title': 'Picard: WGS Coverage',
                'ylab': 'Percentage of Bases',
                'xlab': 'Fold Coverage',
                'xDecimals': False,
                'tt_label': '<b>{point.x} X</b>: {point.y:.1f}',
                'ymin': 0,
                'ymax': 100,
                'data_labels': [
                    {'name': 'Percentage Drop-Off', 'ylab': 'Percentage of Bases', 'ymax': 100},
                    {'name': 'Counts Histogram', 'ylab': 'Coverage', 'ymax': maxval}
                ]
            }
            self.add_section (
                name = 'WGS Coverage',
                anchor = 'picard-wgsmetrics-cov',
                description = 'The number of bases in the genome territory for each fold coverage. ' +
                            'Note that final 1% of data is hidden to prevent very long tails.',
                plot = linegraph.plot([data_percent, data], pconfig)
            )

            # Bar plot of ignored bases
            pdata = dict()
            for s_name, data in self.picard_wgsmetrics_data.items():
                pdata[s_name] = dict()
                pdata[s_name]['PCT_EXC_MAPQ'] = data['PCT_EXC_MAPQ'] * 100.0
                pdata[s_name]['PCT_EXC_DUPE'] = data['PCT_EXC_DUPE'] * 100.0
                pdata[s_name]['PCT_EXC_UNPAIRED'] = data['PCT_EXC_UNPAIRED'] * 100.0
                pdata[s_name]['PCT_EXC_BASEQ'] = data['PCT_EXC_BASEQ'] * 100.0
                pdata[s_name]['PCT_EXC_OVERLAP'] = data['PCT_EXC_OVERLAP'] * 100.0
                pdata[s_name]['PCT_EXC_CAPPED'] = data['PCT_EXC_CAPPED'] * 100.0

            keys = OrderedDict()
            keys['PCT_EXC_MAPQ'] = {'name': 'Low mapping quality'}
            keys['PCT_EXC_DUPE'] = {'name': 'Duplicates reads'}
            keys['PCT_EXC_UNPAIRED'] = {'name': 'No mapped mate pair'}
            keys['PCT_EXC_BASEQ'] = {'name': 'Low base quality'}
            keys['PCT_EXC_OVERLAP'] = {'name': 'Overlapping insert'}
            keys['PCT_EXC_CAPPED'] = {'name': 'Over capped coverage'}

            # Config for the plot
            pconfig = {
                'id': 'picard_wgs_metrics_bases',
                'title': 'Picard: WGS Filtered Bases',
                'cpswitch': False,
                'ylab': '% Bases',
                'ymax': 100
            }

            self.add_section (
                name = 'WGS Filtered Bases',
                anchor = 'picard-wgsmetrics-bases',
                description = 'For more information about the filtered categories, see the '+
                           '<a href="http://broadinstitute.github.io/picard/picard-metric-definitions.html#CollectWgsMetrics.WgsMetrics" target="_blank">Picard documentation</a>.',
                plot = bargraph.plot(pdata, keys, pconfig)
            )


    # Return the number of detected samples to the parent module
    return len(self.picard_wgsmetrics_data)
Find Picard WgsMetrics reports and parse their data