From 38ec6f0333a74575c729ae2bbf4a758be05a294c Mon Sep 17 00:00:00 2001 From: Jeremy Muhlich Date: Thu, 24 Aug 2023 17:17:50 -0400 Subject: [PATCH] WIP Add layer map to report --- ashlar/report.py | 113 ++++++++++++++++++++++++--------------- ashlar/scripts/ashlar.py | 16 +++--- 2 files changed, 78 insertions(+), 51 deletions(-) diff --git a/ashlar/report.py b/ashlar/report.py index c484fd30..d809766e 100644 --- a/ashlar/report.py +++ b/ashlar/report.py @@ -43,6 +43,13 @@ def generate_report(path, aligners): pdf.add_figure(fig, "Cycle 1: Tile pair alignment quality") plt.close(fig) + for i, aligner in enumerate(aligners, 2): + pdf.add_page() + fig = plot_layer_map(aligner, aligner.reader.thumbnail) + fig.set_size_inches(7.5, 9) + pdf.add_figure(fig, f"Cycle {i}: Cycle alignment map") + plt.close(fig) + pdf.output(path) @@ -133,14 +140,7 @@ def plot_edge_map( **nx_kwargs, ) nx.draw_networkx_labels(g, pos, ax=ax, font_size=font_size, **nx_kwargs) - rh, rw = aligner.metadata.size - for x, y in pos: - x -= rw / 2 - y -= rh / 2 - rect = mpatches.Rectangle( - (x, y), rw, rh, color='silver', alpha=0.25, fill=False, lw=0.25, zorder=0.5 - ) - ax.add_patch(rect) + draw_borders(ax, aligner, pos) cbar = fig.colorbar( mcm.ScalarMappable(mcolors.Normalize(emin, emax), edge_cmap), extend="max", @@ -247,7 +247,7 @@ def plot_layer_map( im_kwargs=None, nx_kwargs=None, ): - """Plot tile centers colored by shift distance""" + """Plot tile shift distance and direction""" if pos == "metadata": centers = aligner.metadata.centers - aligner.metadata.origin @@ -261,23 +261,61 @@ def plot_layer_map( fig, ax = plt.subplots() draw_mosaic_image(ax, aligner, img, cmap=cmap, **im_kwargs) - # FIXME colors are wrong -- how do lists passed to draw_networkx_nodes need - # to be organized/sorted? shifts = np.linalg.norm(aligner.shifts, axis=1) skeep = shifts[~aligner.discard] smin, smax = (skeep.min(), skeep.max()) if len(skeep) > 0 else (0, 0) # Map discards to the "over" color in the cmap. shifts[aligner.discard] = smax + 1 + # Reorder to match the graph's internal node ordering. + node_values = shifts[np.array(aligner.neighbors_graph)] diameter = nx.diameter(aligner.neighbors_graph) drange = [10, 60] interp = functools.partial(np.interp, diameter, drange) node_size = node_size or interp([100, 8]) font_size = font_size or interp([6, 2]) - font_color = np.where(aligner.discard, "w", "k") - node_cmap = mcm.Greens.with_extremes(over=(0.15, 0.15, 0.15)) + node_cmap = mcm.Greens.with_extremes(over="#252525") g = aligner.neighbors_graph pos = np.fliplr(centers) + qlen = np.min(aligner.metadata.size) * 0.45 + q_angles = np.rad2deg(np.arctan2(*aligner.shifts.T)) # FIXME: invert Y? + reference_offset = ( + aligner.cycle_offset + + aligner.metadata.origin + - aligner.reference_aligner.metadata.origin + ) + reference_corners = ( + aligner.reference_aligner.metadata.centers + - aligner.reference_aligner.metadata.origin + + aligner.reference_aligner.metadata.size / 2 + ) + reference_size = np.max(reference_corners, axis=0) + + ax.add_patch( + mpatches.Rectangle( + -reference_offset[::-1], + *np.max(centers + aligner.metadata.size / 2, axis=0)[::-1], + color=mcm.Blues(0.75), + lw=2, + linestyle="--", + fill=False, + ) + ) + ax.quiver( + pos[:, 0], + pos[:, 1], + [qlen] * len(pos), + [0] * len(pos), + shifts, + cmap=node_cmap, + clim=(smin, smax), + angles=q_angles, + scale_units="x", + scale=1, + headwidth=1, + headlength=1, + headaxislength=1, + ) nx.draw_networkx_nodes( g, pos, @@ -285,7 +323,7 @@ def plot_layer_map( cmap=node_cmap, vmin=smin, vmax=smax, - node_color=shifts, + node_color=node_values, node_size=node_size, edgecolors=None, **nx_kwargs, @@ -297,17 +335,9 @@ def plot_layer_map( font_size=font_size, **nx_kwargs, ) - nodes = np.arange(len(g)) - draw_labels(g.subgraph(nodes[aligner.discard]), font_color="gray") - draw_labels(g.subgraph(nodes[~aligner.discard]), font_color="k") - rh, rw = aligner.metadata.size - for x, y in pos: - x -= rw / 2 - y -= rh / 2 - rect = mpatches.Rectangle( - (x, y), rw, rh, color='silver', alpha=0.25, fill=False, lw=0.25, zorder=0.5 - ) - ax.add_patch(rect) + draw_labels(g.subgraph(np.nonzero(aligner.discard)[0]), font_color="gray") + draw_labels(g.subgraph(np.nonzero(~aligner.discard)[0]), font_color="k") + draw_borders(ax, aligner, pos) cbar = fig.colorbar( mcm.ScalarMappable(mcolors.Normalize(smin, smax), node_cmap), extend="max", @@ -319,26 +349,8 @@ def plot_layer_map( ax.set_frame_on(False) ax.margins(0) fig.tight_layout() - return fig - if artist == 'quiver': - ax.quiver( - *centers.T[::-1], *shifts.T[::-1], aligner.discard, - units='dots', width=2, scale=1, scale_units='xy', angles='xy', - cmap='Greys' - ) - if artist == 'patches': - for xy, dxy, is_discarded in zip( - np.fliplr(centers), np.fliplr(shifts), aligner.discard - ): - arrow = mpatches.FancyArrowPatch( - xy, np.array(xy) + np.array(dxy), - arrowstyle='->', color='0' if is_discarded else '1', - mutation_scale=8, - ) - ax.add_patch(arrow) - def draw_mosaic_image(ax, aligner, img, **kwargs): if img is None: @@ -350,3 +362,18 @@ def draw_mosaic_image(ax, aligner, img, **kwargs): if "vmax" not in kwargs: kwargs["vmax"] = np.percentile(img, 99) ax.imshow(img, extent=(-0.5, w-0.5, h-0.5, -0.5), **kwargs) + + +def draw_borders(ax, aligner, pos): + rh, rw = aligner.metadata.size + for x, y in pos - (rw / 2, rh / 2): + rect = mpatches.Rectangle( + (x, y), + rw, + rh, + color=(0.2, 0.2, 0.2), + fill=False, + lw=0.25, + zorder=0.5, + ) + ax.add_patch(rect) diff --git a/ashlar/scripts/ashlar.py b/ashlar/scripts/ashlar.py index dde14981..fd753ff2 100644 --- a/ashlar/scripts/ashlar.py +++ b/ashlar/scripts/ashlar.py @@ -296,14 +296,14 @@ def process_single( edge_aligner.reader.reader.thumbnail = edge_aligner.reader.thumbnail edge_aligner.reader = edge_aligner.reader.reader - if not quiet: - print() - print(f"Merging tiles and writing to {output_path_format}") - writer_class = reg.PyramidWriter if pyramid else reg.TiffListWriter - writer = writer_class( - mosaics, output_path_format, verbose=not quiet, **writer_args - ) - writer.run() + # if not quiet: + # print() + # print(f"Merging tiles and writing to {output_path_format}") + # writer_class = reg.PyramidWriter if pyramid else reg.TiffListWriter + # writer = writer_class( + # mosaics, output_path_format, verbose=not quiet, **writer_args + # ) + # writer.run() if report_path: generate_report(report_path, [m.aligner for m in mosaics])