diff --git a/angdist.py b/angdist.py new file mode 100644 index 0000000..e1e0863 --- /dev/null +++ b/angdist.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import starfile as star +import matplotlib.pyplot as plt +from mpl_toolkits.axes_grid1 import make_axes_locatable +import click + +# Building blocks + +def load_angles(starfile): + """Load rlnAngleRot and rlnAngleTilt from a run_data.star file.""" + star_data = star.open(starfile) + angles = star_data[1][['rlnAngleRot', 'rlnAngleTilt']] + return angles + +def build_histogram(angles, title, colormap, gridsize): + """Builds a 2D histogram of number of particles per Euler angle pair.""" + fig, ax = plt.subplots() + hb = ax.hexbin(angles.rlnAngleRot, angles.rlnAngleTilt, bins='log', cmap=colormap, gridsize=gridsize) + ax.set(xlim=(-180, 180), ylim=(0, 180)) + ax.set_xlabel('$\phi$ (rlnAngleRot, deg)') + ax.set_xticks(range(-180, 181, 45)) + ax.set_ylabel('$\\theta$ (rlnAngleTilt, deg)') + ax.set_yticks(range(0, 181, 45)) + ax.set_title(title) + fig.gca().set_aspect('equal', adjustable='box') + divider = make_axes_locatable(ax) + cax = divider.append_axes('right', size='5%', pad=0.1) + cb = fig.colorbar(hb, ax=ax, cax=cax) + cb.set_label('Number of particles') + fig.tight_layout() + return fig + +# Command-line tool made from the buidling blocks + +@click.command(context_settings = dict(help_option_names = ['-h', '--help'])) +@click.argument('starfile', metavar = '') +@click.option('-t', '--title', 'title', default = '', type = str, help = 'Title of the histogram (default: no title).') +@click.option('-c', '--colormap', 'colormap', default = 'viridis', type = str, help = 'A color map supported by matplotlib (default: "viridis").') +@click.option('-g', '--gridsize', 'gridsize', default = 50, type = int, help = 'Number of hexagonal bins along the x axis (default: 50).') +@click.option('-o', '--output', 'output_file', default = '', type = str, help = 'File name to save the histogram (optional: with no file name, simply display the histogram on screen without saving it; recommended file formats: .png, .pdf, .svg or any format supported by matplotlib).') +def cli(starfile, title, colormap, gridsize, output_file): + """Plots a 2D histogram of Euler angles distribution from a run_data.star file produced by RELION.""" + angles = load_angles(starfile) + histogram = build_histogram(angles, title, colormap, gridsize) + if output_file: + histogram.figsize = (11.80, 8.85) + histogram.dpi = 300 + plt.savefig(output_file) + else: + plt.show() + +if __name__ == '__main__': + cli() diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..a3b7681 --- /dev/null +++ b/setup.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from setuptools import setup +from os import path + +# Get the long description from the README file +here = path.abspath(path.dirname(__file__)) +with open(path.join(here, 'README.md'), encoding = 'utf-8') as f: + readme = f.read() + +setup( + name = 'angdist', + version = '1.0', + + description = 'Plot the 2D histogram of Euler angles covered by a set of cryo-EM particles.', + long_description = readme, + long_description_content_type = 'text/markdown', + url = 'https://github.com/Guillawme/angdist', + + author = 'Guillaume Gaullier', + author_email = 'contact@gaullier.org', + + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Science/Research', + 'Topic :: Scientific/Engineering', + 'Topic :: Scientific/Engineering :: Bio-Informatics', + 'Topic :: Scientific/Engineering :: Visualization', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 3', + 'Operating System :: OS Independent', + 'Environment :: Console', + 'Natural Language :: English' + ], + keywords = 'cryo-EM Euler angle histogram visualization', + + py_modules = ["angdist"], + + python_requires = '>=3.8.5', + install_requires = [ + 'click>=7.1.2', + 'matplotlib>=3.3.1', + 'starfile>=0.3.1' + ], + + entry_points = { + 'console_scripts': [ + 'angdist=angdist:cli' + ] + }, + + project_urls = { + 'Bug Reports': 'https://github.com/Guillawme/angdist/issues', + 'Source': 'https://github.com/Guillawme/angdist' + } +)