def library(page=1):
    per_page = 100
    sort = request.args.get("sort")
    ontogenic_age = request.args.getlist("ontogenic_age")
    geologic_age = request.args.getlist("geologic_age")
    elements = request.args.getlist("elements")
    taxonomy = request.args.getlist("taxonomy")
    mine = 'mine' in request.args.keys()
    search = request.args.get('q')

    scanConditions = [Scan.published]

    if search:
        searchQuery = '%{0}%'.format(search)

        textSearch = db.or_(Scan.scientific_name.ilike(searchQuery),
                            Scan.alt_name.ilike(searchQuery),
                            Scan.specimen_id.ilike(searchQuery),
                            Scan.specimen_location.ilike(searchQuery),
                            Scan.description.ilike(searchQuery))

        scanConditions.append(textSearch)

    for searchTags in [ontogenic_age, geologic_age, elements]:
        if len(searchTags) > 0:
            scanConditions.append(
                Scan.tags.any(
                    Tag.taxonomy.startswith(searchTags[0])
                    if len(searchTags) == 1 else db.or_(
                        *
                        [Tag.taxonomy.startswith(term)
                         for term in searchTags])))

    if len(taxonomy) > 0:
        scanConditions.append(Scan.taxonomy.any(Taxonomy.id.in_(taxonomy)))

    if mine and current_user.is_authenticated:
        scanConditions.append(Scan.author_id == current_user.id)

    scanConditions = db.and_(*scanConditions)

    # This is annoying... if we're sorting by name it's just sort,
    # but if we're sorting by tag we need to group it all.
    # Put it under the `groups` key so the view knows it needs to render differently
    if (sort in ('geologic_age', 'ontogenic_age')):
        results = [(tag, tag.scans.filter(scanConditions).all())
                   for tag in Tag.query.filter_by(category=sort).all()]

        data = {
            'groups': [{
                'group': tag.name,
                'items': [s.serialize(full=False) for s in scans]
            } for (tag, scans) in results if len(scans) > 0]
        }
    else:
        query = Scan.scientific_name

        results = Scan.query.filter(scanConditions).order_by(query).paginate(
            page, per_page)

        data = {
            'scans': [s.serialize(full=False) for s in results.items],
            'page': page,
            'total_pages': math.ceil(results.total / per_page)
        }

    data['tags'] = Tag.tree()
    data['tags']['taxonomy'] = Taxonomy.tree()
    data['q'] = search
    data[
        'showMine'] = current_user.is_authenticated and current_user.isContributor(
        )

    out = render_vue(data, title="Scans", menu='library')

    return out