diff --git a/cooltools/api/saddle.py b/cooltools/api/saddle.py index 1c9499e1..9c42fac7 100644 --- a/cooltools/api/saddle.py +++ b/cooltools/api/saddle.py @@ -182,40 +182,65 @@ def _fetch_trans_oe(reg1, reg2): raise ValueError("Unknown type of expected") -def _accumulate( - S, C, getmatrix, digitized, reg1, reg2, min_diag=3, max_diag=-1, verbose=False +def _accumulate_trans( + S, C, getmatrix, digitized, reg1, reg2, max_dist=1, verbose=False ): """ - Helper function to aggregate across region pairs. - If regions are identical, also masks returned matrices below min_diag and above max_diag. - - Used in `get_saddle()`. + Helper function to aggregate across region pairs for trans-data + S and C - are 3D """ - n_bins = S.shape[0] + n_bins = S.shape[-1] matrix = getmatrix(reg1, reg2) - if reg1 == reg2: - for d in np.arange(-min_diag + 1, min_diag): - numutils.set_diag(matrix, np.nan, d) - if max_diag >= 0: - for d in np.append( - np.arange(-matrix.shape[0], -max_diag), - np.arange(max_diag + 1, matrix.shape[0]), - ): - numutils.set_diag(matrix, np.nan, d) + if verbose: + print("regions {} vs {}".format(reg1, reg2)) + + for i in range(n_bins): + row_mask = digitized[reg1] == i + for j in range(n_bins): + col_mask = digitized[reg2] == j + data = matrix[row_mask, :][:, col_mask] + is_finite_mask = np.isfinite(data) + data = data[is_finite_mask] + S[max_dist-1, i, j] += np.sum(data) + C[max_dist-1, i, j] += float(len(data)) + + +def _accumulate_cis( + S, C, getmatrix, digitized, reg1, reg2, max_dist, verbose=False +): + """ + Helper function to aggregate across region pairs for cis-data. + Do it separately for every diagonal. + """ + + if reg1 != reg2: + raise ValueError("this is special version of accumulate for cis data only ...") + + n_bins = S.shape[-1] + matrix = getmatrix(reg1, reg2) if verbose: print("regions {} vs {}".format(reg1, reg2)) + # Toeplitz matrix with distance for every pixel ... + _dist_vec = np.arange(matrix.shape[0]) + dist_mat = np.abs(_dist_vec[None,:] - _dist_vec[:,None]) + for i in range(n_bins): row_mask = digitized[reg1] == i for j in range(n_bins): col_mask = digitized[reg2] == j data = matrix[row_mask, :][:, col_mask] - data = data[np.isfinite(data)] - S[i, j] += np.sum(data) - C[i, j] += float(len(data)) + dist = dist_mat[row_mask, :][:, col_mask] + is_finite_mask = np.isfinite(data) + data = data[is_finite_mask] + dist = dist[is_finite_mask] + # S unrolled by distances - inefficient memory access - isn't it ? + S[:, i, j] += np.bincount(dist, weights=data, minlength=max_dist) + # C unrolled by distances + C[:, i, j] += np.bincount(dist, minlength=max_dist).astype(float) def _make_binedges(track_values, n_bins, vrange=None, qrange=None): @@ -503,9 +528,12 @@ def saddle( # set "cis" or "trans" for supports (regions to iterate over) and matrix fetcher if contact_type == "cis": + # precalculate max_dist using provided expected: + max_dist = expected["dist"].max() + 1 + # redefine max_diag if it is -1: + max_diag = None if max_diag < 0 else max_diag # only symmetric intra-chromosomal regions : supports = list(zip(view_df[view_name_col], view_df[view_name_col])) - getmatrix = _make_cis_obsexp_fetcher( clr, expected, @@ -514,18 +542,41 @@ def saddle( expected_value_col=expected_value_col, clr_weight_name=clr_weight_name, ) + # n_bins here includes 2 open bins for values hi. + interaction_sum = np.zeros((max_dist, n_bins + 2, n_bins + 2)) + interaction_count = np.zeros((max_dist, n_bins + 2, n_bins + 2)) + for reg1, reg2 in supports: + _accumulate_cis( + interaction_sum, + interaction_count, + getmatrix, + digitized_tracks, + reg1, + reg2, + max_dist, + verbose=verbose + ) + # symmetrise by adding transpose "saddle" + # transpose 2nd and 3rd coords by leaving the 1st alone + interaction_sum += interaction_sum.transpose(0,2,1) + interaction_count += interaction_count.transpose(0,2,1) + if trim_outliers: + interaction_sum = interaction_sum[:, 1:-1, 1:-1] + interaction_count = interaction_count[:, 1:-1, 1:-1] + # in order to stay compatible - return aggregated sums and counts for now: + return ( + np.nansum(interaction_sum[min_diag:max_diag], axis=0), + np.nansum(interaction_count[min_diag:max_diag], axis=0) + ) elif contact_type == "trans": + # precalculate max_dist using provided expected: + max_dist = 1 # asymmetric inter-chromosomal regions : - supports = list(combinations(view_df[view_name_col], 2)) - supports = [ - i - for i in supports - if ( - view_df["chrom"].loc[view_df[view_name_col] == i[0]].values - != view_df["chrom"].loc[view_df[view_name_col] == i[1]].values - ) - ] - + view_df_index = view_df.set_index(view_name_col) + supports = [] + for i1, i2 in combinations(view_df[view_name_col], 2): + if view_df_index.at[i1, "chrom"] != view_df_index.at[i2, "chrom"]: + supports.append((i1, i2)) getmatrix = _make_trans_obsexp_fetcher( clr, expected, @@ -534,235 +585,262 @@ def saddle( expected_value_col=expected_value_col, clr_weight_name=clr_weight_name, ) + # n_bins here includes 2 open bins for values hi. + interaction_sum = np.zeros((max_dist, n_bins + 2, n_bins + 2)) + interaction_count = np.zeros((max_dist, n_bins + 2, n_bins + 2)) + for reg1, reg2 in supports: + _accumulate_trans( + interaction_sum, + interaction_count, + getmatrix, + digitized_tracks, + reg1, + reg2, + max_dist, + verbose=verbose, + ) + # symmetrise by adding transpose "saddle" + # transpose 2nd and 3rd coords by leaving the 1st alone + interaction_sum += interaction_sum.transpose(0,2,1) + interaction_count += interaction_count.transpose(0,2,1) + if trim_outliers: + interaction_sum = interaction_sum[:, 1:-1, 1:-1] + interaction_count = interaction_count[:, 1:-1, 1:-1] + # in order to stay compatible - return aggregated sums and counts for now: + return ( + np.nansum(interaction_sum, axis=0), + np.nansum(interaction_count, axis=0) + ) else: raise ValueError("Allowed values for contact_type are 'cis' or 'trans'.") - # n_bins here includes 2 open bins for values hi. - interaction_sum = np.zeros((n_bins + 2, n_bins + 2)) - interaction_count = np.zeros((n_bins + 2, n_bins + 2)) - - for reg1, reg2 in supports: - _accumulate( - interaction_sum, - interaction_count, - getmatrix, - digitized_tracks, - reg1, - reg2, - min_diag=min_diag, - max_diag=max_diag, - verbose=verbose, - ) - - interaction_sum += interaction_sum.T - interaction_count += interaction_count.T - - if trim_outliers: - interaction_sum = interaction_sum[1:-1, 1:-1] - interaction_count = interaction_count[1:-1, 1:-1] - - return interaction_sum, interaction_count - -def saddleplot( +def saddle_stack( + clr, + expected, track, - saddledata, + contact_type, n_bins, vrange=None, - qrange=(0.0, 1.0), - cmap="coolwarm", - scale="log", - vmin=0.5, - vmax=2, - color=None, - title=None, - xlabel=None, - ylabel=None, - clabel=None, - fig=None, - fig_kws=None, - heatmap_kws=None, - margin_kws=None, - cbar_kws=None, - subplot_spec=None, + qrange=None, + view_df=None, + clr_weight_name="weight", + expected_value_col="balanced.avg", + view_name_col="name", + trim_outliers=False, + verbose=False, + drop_track_na=False, ): """ - Generate a saddle plot. + Get a matrix of average interactions between genomic bin + pairs as a function of a specified genomic track. + + The provided genomic track is either: + (a) digitized inside this function by passing 'n_bins', and one of 'v_range' or 'q_range' + (b) passed as a pre-digitized track with a categorical value column as generated by `get_digitized()`. Parameters ---------- - track : pd.DataFrame - See get_digitized() for details. - saddledata : 2D array-like - Saddle matrix produced by `make_saddle`. It will include 2 flanking - rows/columns for outlier signal values, thus the shape should be - `(n+2, n+2)`. - cmap : str or matplotlib colormap - Colormap to use for plotting the saddle heatmap - scale : str - Color scaling to use for plotting the saddle heatmap: log or linear - vmin, vmax : float - Value limits for coloring the saddle heatmap - color : matplotlib color value - Face color for margin bar plots - fig : matplotlib Figure, optional - Specified figure to plot on. A new figure is created if none is - provided. - fig_kws : dict, optional - Passed on to `plt.Figure()` - heatmap_kws : dict, optional - Passed on to `ax.imshow()` - margin_kws : dict, optional - Passed on to `ax.bar()` and `ax.barh()` - cbar_kws : dict, optional - Passed on to `plt.colorbar()` - subplot_spec : GridSpec object - Specify a subregion of a figure to using a GridSpec. - + clr : cooler.Cooler + Observed matrix. + expected : DataFrame in expected format + Diagonal summary statistics for each chromosome, and name of the column + with the values of expected to use. + contact_type : str + If 'cis' then only cis interactions are used to build the matrix. + If 'trans', only trans interactions are used. + track : DataFrame + A track, i.e. BedGraph-like dataframe, which is digitized with + the options n_bins, vrange and qrange. Can optionally be passed + as a pre-digitized dataFrame with a categorical value column, + as generated by get_digitzied(), also passing n_bins as None. + n_bins : int or None + number of bins for signal quantization. If None, then track must + be passed as a pre-digitized track. + vrange : tuple + Low and high values used for binning track values. + See get_digitized(). + qrange : tuple + Low and high values for quantile binning track values. + Low must be 0.0 or more, high must be 1.0 or less. + Only one of vrange or qrange can be passed. See get_digitzed(). + view_df: viewframe + Viewframe with genomic regions. If none, generate from track chromosomes. + clr_weight_name : str + Name of the column in the clr.bins to use as balancing weights. + Using raw unbalanced data is not supported for saddles. + expected_value_col : str + Name of the column in expected used for normalizing. + view_name_col : str + Name of column in view_df with region names. + trim_outliers : bool, optional + Remove first and last row and column from the output matrix. + verbose : bool, optional + If True then reports progress. + drop_track_na : bool, optional + If True then drops NaNs in input track (as if they were missing), + If False then counts NaNs as present in dataframe. + In general, this only adds check form chromosomes that have all missing values, but does not affect the results. Returns ------- - Dictionary of axes objects. - + interaction_sum : 3D array + The matrix of summed interaction probability between two genomic bins + given their values of the provided genomic track. The first dimension + corresponds to the index of the diagonal where the data was collected from. + interaction_count : 3D array + The matrix of the number of genomic bin pairs that contributed to the + corresponding pixel of ``interaction_sum``. The first dimension + corresponds to the index of the diagonal where the data was collected from. """ - warnings.warn( - "Generating a saddleplot will be deprecated in future versions, " - + "please see https://github.com/open2c_examples for examples on how to plot saddles.", - DeprecationWarning, - ) - - from matplotlib.gridspec import GridSpec, GridSpecFromSubplotSpec - from matplotlib.colors import Normalize, LogNorm - from matplotlib import ticker - import matplotlib.pyplot as plt - - class MinOneMaxFormatter(ticker.LogFormatter): - def set_locs(self, locs=None): - self._sublabels = set([vmin % 10 * 10, vmax % 10, 1]) + if type(n_bins) is int: + # perform digitization + track = align_track_with_cooler( + track, + clr, + view_df=view_df, + clr_weight_name=clr_weight_name, + mask_clr_bad_bins=True, + drop_track_na=drop_track_na, # this adds check for chromosomes that have all missing values + ) + digitized_track, binedges = digitize( + track.iloc[:, :4], + n_bins, + vrange=vrange, + qrange=qrange, + digitized_suffix=".d", + ) + digitized_col = digitized_track.columns[3] - def __call__(self, x, pos=None): - if x not in [vmin, 1, vmax]: - return "" - else: - return "{x:g}".format(x=x) + elif n_bins is None: + # assume and test if track is pre-digitized + digitized_track = track + digitized_col = digitized_track.columns[3] + is_track(track.astype({digitized_col: "float"}), raise_errors=True) + if ( + type(digitized_track.dtypes[3]) + is not pd.core.dtypes.dtypes.CategoricalDtype + ): + raise ValueError( + """when n_bins=None, saddle assumes the track has been + pre-digitized and the value column is a + pandas categorical. See get_digitized().""" + ) + cats = digitized_track[digitized_col].dtype.categories.values + # cats has two additional categories, 0 and n_bins+1, for values + # falling outside range, as well as -1 for NAs. + n_bins = len(cats[cats > -1]) - 2 + else: + raise ValueError("n_bins must be provided as int or None") - track_value_col = track.columns[3] - track_values = track[track_value_col].values + if view_df is None: + view_df = view_from_track(digitized_track) + else: + # Make sure view_df is a proper viewframe + try: + _ = is_compatible_viewframe( + view_df, + clr, + check_sorting=True, # just in case + raise_errors=True, + ) + except Exception as e: + raise ValueError("view_df is not a valid viewframe or incompatible") from e - # Digitize the track and calculate histogram - digitized_track, binedges = digitize(track, n_bins, vrange=vrange, qrange=qrange) - x = digitized_track[digitized_track.columns[3]].values.astype(int).copy() - x = x[(x > -1) & (x < len(binedges) + 1)] - hist = np.bincount(x, minlength=len(binedges) + 1) + # make sure provided expected is compatible + try: + _ = is_valid_expected( + expected, + contact_type, + view_df, + verify_cooler=clr, + expected_value_cols=[ + expected_value_col, + ], + raise_errors=True, + ) + except Exception as e: + raise ValueError("provided expected is not compatible") from e - if vrange is not None: - lo, hi = vrange - if qrange is not None: - lo, hi = qrange - # Reset the binedges for plotting - binedges = np.linspace(lo, hi, n_bins + 1) + # check if cooler is balanced + if clr_weight_name: + try: + _ = is_cooler_balanced(clr, clr_weight_name, raise_errors=True) + except Exception as e: + raise ValueError( + f"provided cooler is not balanced or {clr_weight_name} is missing" + ) from e - # Histogram and saddledata are flanked by outlier bins - n = saddledata.shape[0] - X, Y = np.meshgrid(binedges, binedges) - C = saddledata - if (n - n_bins) == 2: - C = C[1:-1, 1:-1] - hist = hist[1:-1] - - # Layout - if subplot_spec is not None: - GridSpec = partial(GridSpecFromSubplotSpec, subplot_spec=subplot_spec) - grid = {} - gs = GridSpec( - nrows=3, - ncols=3, - width_ratios=[0.2, 1, 0.1], - height_ratios=[0.2, 1, 0.1], - wspace=0.05, - hspace=0.05, - ) + digitized_tracks = {} + for num, reg in view_df.iterrows(): + digitized_reg = bioframe.select(digitized_track, reg) + digitized_tracks[reg[view_name_col]] = digitized_reg[digitized_col] - # Figure - if fig is None: - fig_kws_default = dict(figsize=(5, 5)) - fig_kws = _merge_dict(fig_kws_default, fig_kws if fig_kws is not None else {}) - fig = plt.figure(**fig_kws) - - # Heatmap - if scale == "log": - norm = LogNorm(vmin=vmin, vmax=vmax) - elif scale == "linear": - norm = Normalize(vmin=vmin, vmax=vmax) + # set "cis" or "trans" for supports (regions to iterate over) and matrix fetcher + if contact_type == "cis": + # precalculate max_dist using provided expected: + max_dist = expected["dist"].max() + 1 + # only symmetric intra-chromosomal regions : + supports = list(zip(view_df[view_name_col], view_df[view_name_col])) + getmatrix = _make_cis_obsexp_fetcher( + clr, + expected, + view_df, + view_name_col=view_name_col, + expected_value_col=expected_value_col, + clr_weight_name=clr_weight_name, + ) + _acc = partial(_accumulate_cis, + getmatrix=getmatrix, + digitized=digitized_tracks, + max_dist=max_dist, + verbose=verbose, + ) + elif contact_type == "trans": + # fake max_dist for trans - 1: + max_dist = 1 + # asymmetric inter-chromosomal regions : + supports = list(combinations(view_df[view_name_col], 2)) + supports = [ + i + for i in supports + if ( + view_df["chrom"].loc[view_df[view_name_col] == i[0]].values + != view_df["chrom"].loc[view_df[view_name_col] == i[1]].values + ) + ] + getmatrix = _make_trans_obsexp_fetcher( + clr, + expected, + view_df, + view_name_col=view_name_col, + expected_value_col=expected_value_col, + clr_weight_name=clr_weight_name, + ) + _acc = partial(_accumulate_trans, + getmatrix=getmatrix, + digitized=digitized_tracks, + max_dist=max_dist, + verbose=verbose, + ) else: - raise ValueError("Only linear and log color scaling is supported") + raise ValueError("Allowed values for contact_type are 'cis' or 'trans'.") - grid["ax_heatmap"] = ax = plt.subplot(gs[4]) - heatmap_kws_default = dict(cmap=cmap, rasterized=True) - heatmap_kws = _merge_dict( - heatmap_kws_default, heatmap_kws if heatmap_kws is not None else {} - ) - img = ax.pcolormesh(X, Y, C, norm=norm, **heatmap_kws) - plt.gca().yaxis.set_visible(False) + # n_bins here includes 2 open bins for values hi. + _sum = np.zeros((max_dist, n_bins + 2, n_bins + 2)) + _count = np.zeros((max_dist, n_bins + 2, n_bins + 2)) - # Margins - margin_kws_default = dict(edgecolor="k", facecolor=color, linewidth=1) - margin_kws = _merge_dict( - margin_kws_default, margin_kws if margin_kws is not None else {} - ) - # left margin hist - grid["ax_margin_y"] = plt.subplot(gs[3], sharey=grid["ax_heatmap"]) - plt.barh( - binedges[:-1], height=np.diff(binedges), width=hist, align="edge", **margin_kws - ) - plt.xlim(plt.xlim()[1], plt.xlim()[0]) # fliplr - plt.ylim(hi, lo) - plt.gca().spines["top"].set_visible(False) - plt.gca().spines["bottom"].set_visible(False) - plt.gca().spines["left"].set_visible(False) - plt.gca().xaxis.set_visible(False) - # top margin hist - grid["ax_margin_x"] = plt.subplot(gs[1], sharex=grid["ax_heatmap"]) - plt.bar( - binedges[:-1], width=np.diff(binedges), height=hist, align="edge", **margin_kws - ) - plt.xlim(lo, hi) - # plt.ylim(plt.ylim()) # correct - plt.gca().spines["top"].set_visible(False) - plt.gca().spines["right"].set_visible(False) - plt.gca().spines["left"].set_visible(False) - plt.gca().xaxis.set_visible(False) - plt.gca().yaxis.set_visible(False) - - # Colorbar - grid["ax_cbar"] = plt.subplot(gs[5]) - cbar_kws_default = dict(fraction=0.8, label=clabel or "") - cbar_kws = _merge_dict(cbar_kws_default, cbar_kws if cbar_kws is not None else {}) - if scale == "linear" and vmin is not None and vmax is not None: - grid["cbar"] = cb = plt.colorbar(img, **cbar_kws) - # cb.set_ticks(np.arange(vmin, vmax + 0.001, 0.5)) - # # do linspace between vmin and vmax of 5 segments and trunc to 1 decimal: - decimal = 10 - nsegments = 5 - cd_ticks = np.trunc(np.linspace(vmin, vmax, nsegments) * decimal) / decimal - cb.set_ticks(cd_ticks) - else: - grid["cbar"] = cb = plt.colorbar(img, format=MinOneMaxFormatter(), **cbar_kws) - cb.ax.yaxis.set_minor_formatter(MinOneMaxFormatter()) - - # extra settings - grid["ax_heatmap"].set_xlim(lo, hi) - grid["ax_heatmap"].set_ylim(hi, lo) - plt.grid(False) - plt.axis("off") - if title is not None: - grid["ax_margin_x"].set_title(title) - if xlabel is not None: - grid["ax_heatmap"].set_xlabel(xlabel) - if ylabel is not None: - grid["ax_margin_y"].set_ylabel(ylabel) - - return grid + for reg1, reg2 in supports: + _acc( _sum, _count, reg1=reg1, reg2=reg2 ) + + # symmetrise by adding transpose "saddle" + # transpose 2nd and 3rd coords by leaving the 1st alone + _sum += _sum.transpose(0,2,1) + _count += _count.transpose(0,2,1) + if trim_outliers: + _sum = _sum[:, 1:-1, 1:-1] + _count = _count[:, 1:-1, 1:-1] + return _sum, _count def saddle_strength(S, C): diff --git a/cooltools/cli/saddle.py b/cooltools/cli/saddle.py index 64eca7f9..0fd520c2 100644 --- a/cooltools/cli/saddle.py +++ b/cooltools/cli/saddle.py @@ -1,6 +1,5 @@ # inspired by: # saddles.py by @nvictus -# https://github.com/nandankita/labUtilityTools from functools import partial import os.path as op import sys @@ -125,36 +124,6 @@ "bedGraph-style TSV.", required=True, ) -@click.option( - "--fig", - type=click.Choice(["png", "jpg", "svg", "pdf", "ps", "eps"]), - multiple=True, - help="Generate a figure and save to a file of the specified format. " - "If not specified - no image is generated. Repeat for multiple " - "output formats.", -) -@click.option( - "--scale", - help="Value scale for the heatmap", - type=click.Choice(["linear", "log"]), - default="log", - show_default=True, -) -@click.option( - "--cmap", help="Name of matplotlib colormap", default="coolwarm", show_default=True -) -@click.option( - "--vmin", - help="Low value of the saddleplot colorbar. " - "Note: value in original units irrespective of used scale, " - "and therefore should be positive for both vmin and vmax.", - type=float, - default=0.5, -) -@click.option( - "--vmax", help="High value of the saddleplot colorbar", type=float, default=2 -) -@click.option("--hist-color", help="Face color of histogram bar chart") @click.option( "-v", "--verbose", help="Enable verbose output", is_flag=True, default=False ) @@ -172,12 +141,6 @@ def saddle( strength, view, out_prefix, - fig, - scale, - cmap, - vmin, - vmax, - hist_color, verbose, ): """ @@ -336,51 +299,3 @@ def saddle( # Save data np.savez(out_prefix + ".saddledump", **to_save) # .npz auto-added digitized_track.to_csv(out_prefix + ".digitized.tsv", sep="\t", index=False) - - # Generate figure - if len(fig): - try: - import matplotlib as mpl - - mpl.use("Agg") # savefig only for now: - import matplotlib.pyplot as plt - except ImportError: - print("Install matplotlib to use ", file=sys.stderr) - sys.exit(1) - - if hist_color is None: - color = ( - 0.41568627450980394, - 0.8, - 0.39215686274509803, - ) # sns.color_palette('muted')[2] - else: - color = mpl.colors.colorConverter.to_rgb(hist_color) - title = op.basename(cool_path) + " ({})".format(contact_type) - - if qrange is not None: - track_label = track_name + " quantiles" - else: - track_label = track_name - - clabel = "(contact frequency / expected)" - - api.saddle.saddleplot( - track, - saddledata, - n_bins, - vrange=vrange, - qrange=qrange, - scale=scale, - vmin=vmin, - vmax=vmax, - color=color, - title=title, - xlabel=track_label, - ylabel=track_label, - clabel=clabel, - cmap=cmap, - ) - - for ext in fig: - plt.savefig(out_prefix + "." + ext, bbox_inches="tight") diff --git a/tests/test_compartments_saddle.py b/tests/test_compartments_saddle.py index d125aa62..ce7abe7e 100644 --- a/tests/test_compartments_saddle.py +++ b/tests/test_compartments_saddle.py @@ -156,10 +156,6 @@ def test_saddle_cli(request, tmpdir): "0.5", "--n-bins", "30", - "--scale", - "log", - "--fig", - "png", in_cool, f"{out_eig_prefix}.cis.vecs.tsv", out_expected, @@ -215,8 +211,6 @@ def test_trans_saddle_cli(request, tmpdir): "0.5", "--n-bins", "30", - "--scale", - "log", in_cool, f"{out_eig_prefix}.trans.vecs.tsv", out_expected, @@ -275,8 +269,6 @@ def test_trans_saddle_cli_viewframe(request, tmpdir): "0.5", "--n-bins", "30", - "--scale", - "log", "--view", in_regions, in_cool,