diff --git a/README.md b/README.md index f8657898..e4bd6d3d 100644 --- a/README.md +++ b/README.md @@ -7,29 +7,33 @@
- - - + + + - - - + + + - - - + + +
+ + + + - - - +
- - - +
+ + + +
@@ -69,6 +73,18 @@ isns.imgplot(data, dx=0.01, units="um", cbar_label="Height (nm)") +### Plot image with a histogram + +```python +import seaborn_image as isns + +isns.imghist(data, dx=150, units="nm", cbar_label="Height (nm)", cmap="ice") +``` + + + + + ### Set context like seaborn ```python @@ -92,4 +108,5 @@ isns.filterplot(data, filter="gaussian", sigma=5, cbar_label="Height (nm)") ## Documentation + Check out the docs [here](https://seaborn-image.readthedocs.io/) diff --git a/examples/image_5.png b/examples/image_5.png new file mode 100644 index 00000000..a7c6b1a9 Binary files /dev/null and b/examples/image_5.png differ diff --git a/pyproject.toml b/pyproject.toml index aed29acf..74abf6cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "seaborn-image" -version = "0.2.0" +version = "0.3.0" description = "seaborn-image: image data visualization and processing like seaborrn using matplotlib, scipy and scikit-image" authors = ["Sarthak Jariwala "] license = "MIT" diff --git a/src/seaborn_image/_context.py b/src/seaborn_image/_context.py index fb129054..0d535742 100644 --- a/src/seaborn_image/_context.py +++ b/src/seaborn_image/_context.py @@ -31,18 +31,21 @@ def set_context(mode="talk", fontfamily="arial", fontweight="bold", rc=None): plt.rc("axes", linewidth=1.5) plt.rc("axes", titlesize=15, titleweight=fontweight) plt.rc("axes", labelsize=15, labelweight=fontweight) + mpl.rcParams.update({"figure.constrained_layout.wspace": 0.2}) font = {"family": fontfamily, "weight": fontweight, "size": 10} if mode == "notebook" or mode == "presentation" or mode == "talk": plt.rc("axes", linewidth=2.5) plt.rc("axes", titlesize=20, titleweight=fontweight) plt.rc("axes", labelsize=20, labelweight=fontweight) + mpl.rcParams.update({"figure.constrained_layout.wspace": 0.3}) font = {"family": fontfamily, "weight": fontweight, "size": 15} if mode == "poster": plt.rc("axes", linewidth=3.5) plt.rc("axes", titlesize=25, titleweight=fontweight) plt.rc("axes", labelsize=25, labelweight=fontweight) + mpl.rcParams.update({"figure.constrained_layout.wspace": 0.4}) font = {"family": fontfamily, "weight": fontweight, "size": 20} plt.rc("font", **font) diff --git a/src/seaborn_image/_core.py b/src/seaborn_image/_core.py index 0b6dfb74..621895a5 100644 --- a/src/seaborn_image/_core.py +++ b/src/seaborn_image/_core.py @@ -121,10 +121,13 @@ def plot(self): if self.cbar_label is not None: cb.set_label(self.cbar_label, fontdict=self.cbar_fontdict) + else: + cax = None + if not self.showticks: ax.get_yaxis().set_visible(False) ax.get_xaxis().set_visible(False) f.tight_layout() - return f, ax + return f, ax, cax diff --git a/src/seaborn_image/_general.py b/src/seaborn_image/_general.py index 188a358a..ba3bfd27 100644 --- a/src/seaborn_image/_general.py +++ b/src/seaborn_image/_general.py @@ -1,6 +1,10 @@ +import matplotlib.pyplot as plt +import numpy as np +from matplotlib import gridspec from matplotlib.axes import Axes from matplotlib.colors import Colormap +from ._colormap import _CMAP_QUAL from ._core import _SetupImage @@ -63,6 +67,7 @@ def imgplot( (tuple): tuple containing: (`matplotlib.figure.Figure`): Matplotlib figure. (`matplotlib.axes.Axes`): Matplotlib axes where the image is drawn. + (`matplotlib.axes.Axes`): Colorbar axes Example: >>> import seaborn_image as isns @@ -114,6 +119,125 @@ def imgplot( fontdict=title_fontdict, ) - f, ax = img_plotter.plot() + f, ax, cax = img_plotter.plot() - return f, ax, data + return f, ax, cax + + +def imghist( + data, + cmap=None, + bins=500, + vmin=None, + vmax=None, + dx=None, + units=None, + cbar=True, + cbar_label=None, + cbar_fontdict=None, + cbar_ticks=None, + showticks=False, + title=None, + title_fontdict=None, +): + """Plot data as a 2-D image with histogram showing the distribution of + the data. Options to add scalebar, colorbar, title. + + Args: + data: Image data (array-like). Supported array shapes are all + `matplotlib.pyplot.imshow` array shapes + cmap (str or `matplotlib.colors.Colormap`, optional): Colormap for image. + Can be a seaborn-image colormap or default matplotlib colormaps or + any other colormap converted to a matplotlib colormap. Defaults to None. + bins (int, optional): Number of histogram bins. Defaults to 500. + vmin (float, optional): Minimum data value that colormap covers. Defaults to None. + vmax (float, optional): Maximum data value that colormap covers. Defaults to None. + dx (float, optional): Size per pixel of the image data. If scalebar + is required, `dx` and `units` must be sepcified. Defaults to None. + units (str, optional): Units of `dx`. Defaults to None. + cbar (bool, optional): Specify if a colorbar is required or not. + Defaults to True. + cbar_label (str, optional): Colorbar label. Defaults to None. + cbar_fontdict (dict, optional): Font specifications for colorbar label - `cbar_label`. + Defaults to None. + cbar_ticks (list, optional): List of colorbar ticks. If None, min and max of + the data are used. If `vmin` and `vmax` are specified, `vmin` and `vmax` values + are used for colorbar ticks. Defaults to None. + showticks (bool, optional): Show image x-y axis ticks. Defaults to False. + title (str, optional): Image title. Defaults to None. + title_fontdict (dict, optional): [Font specifications for `title`. Defaults to None. + + Raises: + TypeError: if `bins` is not int + + Returns: + (tuple): tuple containing: + (`matplotlib.figure.Figure`): Matplotlib figure. + (tuple): tuple containing: + (`matplotlib.axes.Axes`): Matplotlib axes where the image is drawn. + (`matplotlib.axes.Axes`): Matplotlib axes where the histogram is drawn. + (`matplotlib.axes.Axes`): Colorbar axes + + Example: + >>> import seaborn_image as isns + >>> isns.imghist(data) + >>> isns.imghist(data, bins=300) # specify the number of histogram bins + >>> isns.imghist(data, dx=2, units="nm") # add a scalebar + >>> isns.imghist(data, cmap="deep") # specify a colormap + """ + + if not isinstance(bins, int): + raise TypeError("'bins' must be a positive integer") + if not bins > 0: + raise ValueError("'bins' must be a positive integer") + + f = plt.figure(figsize=(10, 6)) # TODO make figsize user defined + gs = gridspec.GridSpec(1, 2, width_ratios=[5, 1], figure=f) + + ax1 = f.add_subplot(gs[0]) + + f, ax1, cax = imgplot( + data, + ax=ax1, + cmap=cmap, + vmin=vmin, + vmax=vmax, + dx=dx, + units=units, + cbar=cbar, + cbar_label=cbar_label, + cbar_fontdict=cbar_fontdict, + cbar_ticks=cbar_ticks, + showticks=showticks, + title=title, + title_fontdict=title_fontdict, + ) + + ax2 = f.add_subplot(gs[1], sharey=cax) + + n, bins, patches = ax2.hist( + data.ravel(), bins=bins, density=True, orientation="horizontal" + ) + + ax2.get_xaxis().set_visible(False) + ax2.get_yaxis().set_visible(False) + ax2.set_frame_on(False) + + if cmap is None: + cm = _CMAP_QUAL.get("deep").mpl_colormap + else: + if cmap in _CMAP_QUAL.keys(): + cm = _CMAP_QUAL.get(cmap).mpl_colormap + else: + cm = plt.cm.get_cmap(cmap) + + bin_centers = bins[:-1] + bins[1:] + + # scale values to interval [0,1] + col = bin_centers - np.min(bin_centers) + col /= np.max(col) + + for c, p in zip(col, patches): + plt.setp(p, "facecolor", cm(c)) + + return f, (ax1, ax2), cax diff --git a/tests/test_core.py b/tests/test_core.py index e4f8d1cb..e81c94d8 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -36,7 +36,7 @@ def test_plot_check_cbar_dict(): img_setup = isns._core._SetupImage( data, cbar=True, cbar_fontdict=[{"fontsize": 20}] ) - f, ax = img_setup.plot() + f, ax, cax = img_setup.plot() @pytest.mark.parametrize("cmap", [None, "acton"]) @@ -82,7 +82,11 @@ def test_plot_w_all_inputs( cbar_ticks=cbar_ticks, showticks=showticks, ) - f, ax = img_setup.plot() + f, ax, cax = img_setup.plot() assert isinstance(f, Figure) assert isinstance(ax, Axes) + if cbar is True: + assert isinstance(cax, Axes) + else: + assert cax is None diff --git a/tests/test_general.py b/tests/test_general.py index fe94022f..4de63726 100644 --- a/tests/test_general.py +++ b/tests/test_general.py @@ -51,21 +51,21 @@ def test_title_fontdict_type(): def test_imgplot_return(): - f, ax, d = isns.imgplot(data) + f, ax, cax = isns.imgplot(data) assert isinstance(f, Figure) assert isinstance(ax, Axes) - assert d.all() == data.all() + assert isinstance(cax, Axes) -@pytest.mark.parametrize("cmap", ["acton"]) +@pytest.mark.parametrize("cmap", [None, "acton", "inferno"]) @pytest.mark.parametrize("cbar", [True, False]) @pytest.mark.parametrize("cbar_label", ["My title", None]) -@pytest.mark.parametrize("cbar_fontdict", [{"fontsize": 20}]) +@pytest.mark.parametrize("cbar_fontdict", [{"fontsize": 20}, None]) @pytest.mark.parametrize("showticks", [True, False]) @pytest.mark.parametrize("title", ["My title", None]) -@pytest.mark.parametrize("title_fontdict", [{"fontsize": 20}]) -def test_all_valid_inputs( +@pytest.mark.parametrize("title_fontdict", [{"fontsize": 20}, None]) +def test_imgplot_w_all_valid_inputs( cmap, cbar, cbar_label, cbar_fontdict, showticks, title, title_fontdict ): isns.imgplot( @@ -79,3 +79,48 @@ def test_all_valid_inputs( title=title, title_fontdict=title_fontdict, ) + + +@pytest.mark.parametrize("bins", [None, 200.0, -400.13]) +def test_imghist_bins_type(bins): + with pytest.raises(TypeError): + isns.imghist(data, bins=bins) + + +@pytest.mark.parametrize("bins", [-100, 0]) +def test_imghist_bins_value(bins): + with pytest.raises(ValueError): + isns.imghist(data, bins=bins) + + +def test_imghist_return(): + f, axes, cax = isns.imghist(data) + + assert isinstance(f, Figure) + assert isinstance(axes[0], Axes) + assert isinstance(axes[1], Axes) + assert isinstance(cax, Axes) + + +@pytest.mark.parametrize("cmap", [None, "acton", "inferno"]) +@pytest.mark.parametrize("bins", [500, 10]) +@pytest.mark.parametrize("cbar", [True, False]) +@pytest.mark.parametrize("cbar_label", ["My title", None]) +@pytest.mark.parametrize("cbar_fontdict", [{"fontsize": 20}, None]) +@pytest.mark.parametrize("showticks", [True, False]) +@pytest.mark.parametrize("title", ["My title", None]) +@pytest.mark.parametrize("title_fontdict", [{"fontsize": 20}, None]) +def test_imghist_w_all_valid_inputs( + cmap, bins, cbar, cbar_label, cbar_fontdict, showticks, title, title_fontdict +): + isns.imghist( + data, + cmap=cmap, + bins=bins, + cbar=cbar, + cbar_label=cbar_label, + cbar_fontdict=cbar_fontdict, + showticks=showticks, + title=title, + title_fontdict=title_fontdict, + )