From d785bbf363f5af5531bc5a553edba3b33eb4deab Mon Sep 17 00:00:00 2001 From: AlexanderButyaev Date: Mon, 22 Sep 2025 14:24:31 -0400 Subject: [PATCH 1/2] add thread LOCK for mpl --- DashML/Basecall/Basecall_Bias.py | 35 +++-- DashML/Basecall/Basecall_Plot.py | 146 +++++++++--------- .../Cluster/Centroid_ConservedRegions.py | 48 +++--- DashML/Landscape/Cluster/Centroid_Putative.py | 36 +++-- DashML/Landscape/Cluster/Cluster_Num.py | 98 ++++++------ DashML/Landscape/Cluster/Cluster_means.py | 88 ++++++----- DashML/Landscape/Cluster/Cluster_mode.py | 91 ++++++----- DashML/Landscape/Cluster/Cluster_native.py | 77 +++++---- DashML/Predict/run_predict.py | 96 ++++++------ DashML/__init__.py | 5 +- DashML/locks.py | 3 + 11 files changed, 385 insertions(+), 338 deletions(-) create mode 100644 DashML/locks.py diff --git a/DashML/Basecall/Basecall_Bias.py b/DashML/Basecall/Basecall_Bias.py index 9f50d6845..7a21c862d 100644 --- a/DashML/Basecall/Basecall_Bias.py +++ b/DashML/Basecall/Basecall_Bias.py @@ -5,6 +5,7 @@ import matplotlib.pyplot as plt import numpy as np import pandas as pd +from DashML.locks import PLOT_LOCK class Modification_Bias: @@ -199,21 +200,21 @@ def plot_df(self, df, curr_seq): df = df.loc[df['Number of Mismatches'] > df['Number of Mismatches'].mean()] df['Mismatches'] = df['Mismatches'] + df['Unmodified Structure'] #print(df) - - fig = df.plot(x='Position', y = 'Number of Mismatches', kind="bar", rot=45, title= curr_seq + " Mismatches by Structure", figsize=(12,8)) - # fig = df.plot(kind="bar", rot=90, title="Modification Rates by Position", figsize=(12,8), stacked=True) - #### save plot ##### - i = 0 - score = df['Mismatches'].to_numpy() - for p in fig.patches: - fig.annotate(score[i], xy=(p.get_x(), p.get_height())) - i += 1 - - fig = plt.gcf() - - if not os.path.exists(self.save_path): - os.makedirs(self.save_path) - - figname = os.path.join(self.save_path + '/'+ curr_seq + '_' + 'Position_Structure_Modification_Rate' + '.png') - fig.savefig(figname) + with PLOT_LOCK: + fig = df.plot(x='Position', y = 'Number of Mismatches', kind="bar", rot=45, title= curr_seq + " Mismatches by Structure", figsize=(12,8)) + # fig = df.plot(kind="bar", rot=90, title="Modification Rates by Position", figsize=(12,8), stacked=True) + #### save plot ##### + i = 0 + score = df['Mismatches'].to_numpy() + for p in fig.patches: + fig.annotate(score[i], xy=(p.get_x(), p.get_height())) + i += 1 + + fig = plt.gcf() + + if not os.path.exists(self.save_path): + os.makedirs(self.save_path) + + figname = os.path.join(self.save_path + '/'+ curr_seq + '_' + 'Position_Structure_Modification_Rate' + '.png') + fig.savefig(figname) #plt.show) diff --git a/DashML/Basecall/Basecall_Plot.py b/DashML/Basecall/Basecall_Plot.py index 09793cec6..36cd86a74 100644 --- a/DashML/Basecall/Basecall_Plot.py +++ b/DashML/Basecall/Basecall_Plot.py @@ -5,6 +5,7 @@ import matplotlib.pyplot as plt import numpy as np import pandas as pd +from DashML.locks import PLOT_LOCK def plot_modification(mods, dir_name="Default", save_path="./", mod_type="Modifications", seq_name="Sequence"): ### deprecated #### @@ -48,23 +49,24 @@ def plot_modification(mods, dir_name="Default", save_path="./", mod_type="Modifi if not os.path.exists(dir_name): os.makedirs(dir_name) - curr_pos = 0 - for i in range(num_plots): - mod_pos = np.array(positions[curr_pos:curr_pos+positions_size]) - mod_num = np.array(mods[curr_pos:curr_pos+positions_size]) - df = pd.DataFrame({'Reference Nucleotide Position': mod_pos, - 'Number of Modifications': mod_num}) - ax = df.plot.bar(x='Reference Nucleotide Position', y='Number of Modifications', rot=90, figsize=(figlen, 10), - color=bar_color, xlabel='Reference Nucleotide Position', ylabel='Number of ' + mod_type, - title=seq_name +' ' + mod_type, legend=False) - # pps = ax.bar(X, np.array(mods[curr_pos:curr_pos+positions_size]), - # color=bar_color, align="center", edgecolor="black", width=.4) - #ax.bar_label(ax, label_type='edge') - curr_pos = curr_pos + positions_size - fig = plt.gcf() - #plt.showblock=False) - figname = os.path.join(dir_name + '/' + seq_name + '_' + mod_type + '_' + str(i) + '.png') - fig.savefig(figname) + with PLOT_LOCK: + curr_pos = 0 + for i in range(num_plots): + mod_pos = np.array(positions[curr_pos:curr_pos+positions_size]) + mod_num = np.array(mods[curr_pos:curr_pos+positions_size]) + df = pd.DataFrame({'Reference Nucleotide Position': mod_pos, + 'Number of Modifications': mod_num}) + ax = df.plot.bar(x='Reference Nucleotide Position', y='Number of Modifications', rot=90, figsize=(figlen, 10), + color=bar_color, xlabel='Reference Nucleotide Position', ylabel='Number of ' + mod_type, + title=seq_name +' ' + mod_type, legend=False) + # pps = ax.bar(X, np.array(mods[curr_pos:curr_pos+positions_size]), + # color=bar_color, align="center", edgecolor="black", width=.4) + #ax.bar_label(ax, label_type='edge') + curr_pos = curr_pos + positions_size + fig = plt.gcf() + #plt.showblock=False) + figname = os.path.join(dir_name + '/' + seq_name + '_' + mod_type + '_' + str(i) + '.png') + fig.savefig(figname) def plot_modification_summary(mods, dir_name="Default", save_path="./", \ @@ -90,28 +92,28 @@ def plot_modification_summary(mods, dir_name="Default", save_path="./", \ dir_name = save_path + dir_name + '_Modification_Plots' if not os.path.exists(dir_name): os.makedirs(dir_name) - curr_pos = 0 - for i in range(num_plots): - mod_pos = np.array(positions[curr_pos:curr_pos + positions_size]) - mod_del = np.array(dels[curr_pos:curr_pos + positions_size]) - mod_ins = np.array(ins[curr_pos:curr_pos + positions_size]) - mod_mis = np.array(mismatch[curr_pos:curr_pos + positions_size]) - - df = pd.DataFrame({'Reference Nucleotide Position': mod_pos, - 'Deletions': mod_del, 'Insertions': mod_ins, - 'Mismatches': mod_mis}) - ax = df.plot.bar(x='Reference Nucleotide Position', rot=90, figsize=(figlen, 10), - xlabel='Reference Nucleotide Position', ylabel='Number of ' + mod_type, - title=seq_name + ' ' + mod_type, legend=True, ylim=(0,.01)) - # pps = ax.bar(X, np.array(mods[curr_pos:curr_pos+positions_size]), - # color=bar_color, align="center", edgecolor="black", width=.4) - # ax.bar_label(ax, label_type='edge') - curr_pos = curr_pos + positions_size - fig = plt.gcf() - #plt.showblock=False) - figname = os.path.join(dir_name + '/' + seq_name + '_' + mod_type + '_' + str(i) + '.png') - fig.savefig(figname) + with PLOT_LOCK: + for i in range(num_plots): + mod_pos = np.array(positions[curr_pos:curr_pos + positions_size]) + mod_del = np.array(dels[curr_pos:curr_pos + positions_size]) + mod_ins = np.array(ins[curr_pos:curr_pos + positions_size]) + mod_mis = np.array(mismatch[curr_pos:curr_pos + positions_size]) + + df = pd.DataFrame({'Reference Nucleotide Position': mod_pos, + 'Deletions': mod_del, 'Insertions': mod_ins, + 'Mismatches': mod_mis}) + ax = df.plot.bar(x='Reference Nucleotide Position', rot=90, figsize=(figlen, 10), + xlabel='Reference Nucleotide Position', ylabel='Number of ' + mod_type, + title=seq_name + ' ' + mod_type, legend=True, ylim=(0,.01)) + # pps = ax.bar(X, np.array(mods[curr_pos:curr_pos+positions_size]), + # color=bar_color, align="center", edgecolor="black", width=.4) + # ax.bar_label(ax, label_type='edge') + curr_pos = curr_pos + positions_size + fig = plt.gcf() + #plt.showblock=False) + figname = os.path.join(dir_name + '/' + seq_name + '_' + mod_type + '_' + str(i) + '.png') + fig.savefig(figname) # #### plot modifications ##### @@ -189,38 +191,40 @@ def plot_mismatch(mismatches, seq_name="Sequence", dir_name="Default", save_path #plt.showblock=False) def plot_average_mod_rate(df, dir_name="Default", save_path="./"): - plt.rcParams.update({'font.size': 20}) - df = df.drop("Condition", axis=1) - #x = df.values # returns a numpy array - #min_max_scaler = preprocessing.MinMaxScaler() - #x_scaled = min_max_scaler.fit_transform(x) - #df = pd.DataFrame(x_scaled) - df = df.iloc[:,:] * 100 - fig = df.plot(kind="bar", rot=30, title="Overall Modification Rates", figsize=(12, 12)) - #### save plot ##### - fig = plt.gcf() - dir_name = save_path + dir_name + '_Modification_Plots' - if not os.path.exists(dir_name): - os.makedirs(dir_name) - figname = os.path.join(dir_name + '/Average_Modification_Rate' + '.png') - fig.savefig(figname) - #plt.showblock=False) + with PLOT_LOCK: + plt.rcParams.update({'font.size': 20}) + df = df.drop("Condition", axis=1) + #x = df.values # returns a numpy array + #min_max_scaler = preprocessing.MinMaxScaler() + #x_scaled = min_max_scaler.fit_transform(x) + #df = pd.DataFrame(x_scaled) + df = df.iloc[:,:] * 100 + fig = df.plot(kind="bar", rot=30, title="Overall Modification Rates", figsize=(12, 12)) + #### save plot ##### + fig = plt.gcf() + dir_name = save_path + dir_name + '_Modification_Plots' + if not os.path.exists(dir_name): + os.makedirs(dir_name) + figname = os.path.join(dir_name + '/Average_Modification_Rate' + '.png') + fig.savefig(figname) + #plt.showblock=False) def plot_average_mod_by_pos_rate(df, dir_name="Default", save_path="./"): - plt.rcParams.update({'font.size': 20}) - #x = df.values # returns a numpy array - #min_max_scaler = preprocessing.MinMaxScaler() - #x_scaled = min_max_scaler.fit_transform(x) - #df = pd.DataFrame(x_scaled) - df = df.iloc[:, :] * 100 - fig = df.plot(kind="line", rot=45, title="Modification Rates by Position", subplots=True, figsize=(12,8), - ylim=(0,100), color='purple') - #fig = df.plot(kind="bar", rot=90, title="Modification Rates by Position", figsize=(12,8), stacked=True) - #### save plot ##### - fig = plt.gcf() - dir_name = save_path + dir_name + '_Modification_Plots' - if not os.path.exists(dir_name): - os.makedirs(dir_name) - figname = os.path.join(dir_name + '/Position_Modification_Rate' + '.png') - fig.savefig(figname) - #plt.showblock=False) + with PLOT_LOCK: + plt.rcParams.update({'font.size': 20}) + #x = df.values # returns a numpy array + #min_max_scaler = preprocessing.MinMaxScaler() + #x_scaled = min_max_scaler.fit_transform(x) + #df = pd.DataFrame(x_scaled) + df = df.iloc[:, :] * 100 + fig = df.plot(kind="line", rot=45, title="Modification Rates by Position", subplots=True, figsize=(12,8), + ylim=(0,100), color='purple') + #fig = df.plot(kind="bar", rot=90, title="Modification Rates by Position", figsize=(12,8), stacked=True) + #### save plot ##### + fig = plt.gcf() + dir_name = save_path + dir_name + '_Modification_Plots' + if not os.path.exists(dir_name): + os.makedirs(dir_name) + figname = os.path.join(dir_name + '/Position_Modification_Rate' + '.png') + fig.savefig(figname) + #plt.showblock=False) diff --git a/DashML/Landscape/Cluster/Centroid_ConservedRegions.py b/DashML/Landscape/Cluster/Centroid_ConservedRegions.py index eb56c287d..49b52f781 100644 --- a/DashML/Landscape/Cluster/Centroid_ConservedRegions.py +++ b/DashML/Landscape/Cluster/Centroid_ConservedRegions.py @@ -14,6 +14,7 @@ import varnaapi import DashML.Database_fx.Insert_DB as dbins import DashML.Database_fx.Select_DB as dbsel +from DashML.locks import PLOT_LOCK #### Must Load Varna Jar locally ##### varna_path = files("DashML.Varna") / "VARNAv3-93.jar" @@ -143,29 +144,30 @@ def save_bpseq(lid, seq, seqlen, structure, metric): return def get_vplot(lid, seq, seq_len, sequence, ss, cons_ss, cons_bp, metric): - v = varnaapi.Structure(structure=ss, sequence=sequence) - v.update(resolution=10, zoom=1, algorithm='radiate', flat=True) - save_bpseq(lid, seq, seqlen=seq_len, structure=v, metric=metric) - out_fig = save_path + str(lid) + "_" + seq + "_" + metric + "_conserved.png" - v.dump_param(save_path + str(lid) + "_" + seq + "_" + metric + "_conserved.yml") - #annotating high reactivity regions. - for r in cons_ss: - if r < seq_len: - r = r + 1 - v.add_highlight_region(r, r, fill='#f16849', outline='#f16849') - #conserved inaccessible regions - for r in cons_bp: - if r < seq_len: - r = r + 1 - v.add_highlight_region(r, r, fill='#c5def2', outline='#c5def2') - #v.add_colormap(values=np.arange(1, 10), vmin=30, vmax=40, style='bw') - #values is an array where each position indicates color 0-n - #overall style is applied - # annotating interactions - cmap = np.ones(seq_len) - v.add_colormap(values=[3], style='energy') - #v.add_aux_BP(1, 10, color='red') - v.savefig(out_fig) + with PLOT_LOCK: + v = varnaapi.Structure(structure=ss, sequence=sequence) + v.update(resolution=10, zoom=1, algorithm='radiate', flat=True) + save_bpseq(lid, seq, seqlen=seq_len, structure=v, metric=metric) + out_fig = save_path + str(lid) + "_" + seq + "_" + metric + "_conserved.png" + v.dump_param(save_path + str(lid) + "_" + seq + "_" + metric + "_conserved.yml") + #annotating high reactivity regions. + for r in cons_ss: + if r < seq_len: + r = r + 1 + v.add_highlight_region(r, r, fill='#f16849', outline='#f16849') + #conserved inaccessible regions + for r in cons_bp: + if r < seq_len: + r = r + 1 + v.add_highlight_region(r, r, fill='#c5def2', outline='#c5def2') + #v.add_colormap(values=np.arange(1, 10), vmin=30, vmax=40, style='bw') + #values is an array where each position indicates color 0-n + #overall style is applied + # annotating interactions + cmap = np.ones(seq_len) + v.add_colormap(values=[3], style='energy') + #v.add_aux_BP(1, 10, color='red') + v.savefig(out_fig) #v.show() diff --git a/DashML/Landscape/Cluster/Centroid_Putative.py b/DashML/Landscape/Cluster/Centroid_Putative.py index edf5b5592..d3029a90e 100644 --- a/DashML/Landscape/Cluster/Centroid_Putative.py +++ b/DashML/Landscape/Cluster/Centroid_Putative.py @@ -8,6 +8,7 @@ from importlib.resources import files import DashML.Database_fx.Insert_DB as dbins import DashML.Database_fx.Select_DB as dbsel +from DashML.locks import PLOT_LOCK ######### Draws Varna Images of Predicted Secondary Structures ##### # TODO: Different from Native Images @@ -74,23 +75,24 @@ def draw_structure(df, method='hamming'): print(out) clusters = df['cluster'].unique() for cluster in clusters: - print(cluster) - ss = str(df.loc[df['cluster'] == cluster, 'secondary'].unique()[0]) - v = varnaapi.Structure(structure=ss, sequence=sequence) - v.update(resolution=10, zoom=1) - out_fig = out + seq + "_" + str(cluster) + ".png" - save_bpseq(seq, seqlen, cluster, v, out) - - # annotating high reactivity regions. - # v.add_highlight_region(11, 21) - # v.add_colormap(values=np.arange(1, 10), vmin=30, vmax=40, style='bw') - # values is an array where each position indicates color 0-n - # overall style is applied - # annotating interactions - # v.add_colormap(values=[2,5,5,5,5, 0, 0, 0, 0, 3,3 ,3],style='energy') - # v.add_aux_BP(1, 10, color='red') - v.savefig(out_fig) - v.show() + with PLOT_LOCK: + print(cluster) + ss = str(df.loc[df['cluster'] == cluster, 'secondary'].unique()[0]) + v = varnaapi.Structure(structure=ss, sequence=sequence) + v.update(resolution=10, zoom=1) + out_fig = out + seq + "_" + str(cluster) + ".png" + save_bpseq(seq, seqlen, cluster, v, out) + + # annotating high reactivity regions. + # v.add_highlight_region(11, 21) + # v.add_colormap(values=np.arange(1, 10), vmin=30, vmax=40, style='bw') + # values is an array where each position indicates color 0-n + # overall style is applied + # annotating interactions + # v.add_colormap(values=[2,5,5,5,5, 0, 0, 0, 0, 3,3 ,3],style='energy') + # v.add_aux_BP(1, 10, color='red') + v.savefig(out_fig) + v.show() # sys.exit(0) # dataframes of centroids diff --git a/DashML/Landscape/Cluster/Cluster_Num.py b/DashML/Landscape/Cluster/Cluster_Num.py index 9b4a73cf9..77323253c 100644 --- a/DashML/Landscape/Cluster/Cluster_Num.py +++ b/DashML/Landscape/Cluster/Cluster_Num.py @@ -5,6 +5,8 @@ import numpy as np import pandas as pd import matplotlib + +from DashML.locks import PLOT_LOCK matplotlib.use("Agg") import matplotlib.pyplot as plt import seaborn as sns @@ -77,33 +79,35 @@ def distancematrix_means(df, seq='hcv'): def optimal_dendrogram(X, seq, tit, linkage_method='complete', threshold='auto', percent=.2): distance_matrix = pdist(X) Z = linkage(distance_matrix, method=linkage_method) - - plt.figure(figsize=(8, 5)) - dendrogram(Z) - - if 'kmeans' in tit: - percent = .45 - - if threshold == 'auto': - max_height = np.max(Z[:, 2]) - threshold_val = percent * max_height - num_clusters = np.sum(Z[:, 2] > threshold_val) + 1 - plt.axhline(y=threshold_val, color='r', linestyle='--', - label=f'{int(percent * 100)}% Height = {threshold_val:.2f}') - plt.legend() - print(f"Estimated number of clusters at {percent * 100}% height threshold: {num_clusters}") - - elif isinstance(threshold, (int, float)): - num_clusters = np.sum(Z[:, 2] > threshold) + 1 - plt.axhline(y=threshold, color='r', linestyle='--', label=f'Threshold = {threshold}') - plt.legend() - print(f"Estimated number of clusters at threshold {threshold}: {num_clusters}") - - plt.title('Dendrogram') - plt.xlabel('Read Index') - plt.ylabel('Distance') - plt.savefig(save_path + seq + tit + '_dendrogram.png', dpi=300) - #plt.showblock=False) + img_path = save_path + seq + tit + '_dendrogram.png' + with PLOT_LOCK: + plt.figure(figsize=(8, 5)) + dendrogram(Z) + + if 'kmeans' in tit: + percent = .45 + + if threshold == 'auto': + max_height = np.max(Z[:, 2]) + threshold_val = percent * max_height + num_clusters = np.sum(Z[:, 2] > threshold_val) + 1 + plt.axhline(y=threshold_val, color='r', linestyle='--', + label=f'{int(percent * 100)}% Height = {threshold_val:.2f}') + plt.legend() + print(f"Estimated number of clusters at {percent * 100}% height threshold: {num_clusters}") + + elif isinstance(threshold, (int, float)): + num_clusters = np.sum(Z[:, 2] > threshold) + 1 + plt.axhline(y=threshold, color='r', linestyle='--', label=f'Threshold = {threshold}') + plt.legend() + print(f"Estimated number of clusters at threshold {threshold}: {num_clusters}") + + plt.title('Dendrogram') + plt.xlabel('Read Index') + plt.ylabel('Distance') + plt.savefig(img_path, dpi=300) + #plt.showblock=False) + return img_path def get_optimal_clusters(X, seq, read_list, tit, plot=False): @@ -137,26 +141,28 @@ def get_optimal_clusters(X, seq, read_list, tit, plot=False): #print('Silhouette Score:', silhouette, "WSS:", wss) print('Silhouette Score:', len(s_scores), "WSS:", len(wss_values)) + img_sil = save_path + seq + tit + '_Silhouette.png' + img_wss = save_path + seq + tit + '_WSS.png' if plot: - # Plot Elbow Curve - plt.figure(figsize=(6, 4)) - plt.plot( c_scores, s_scores, marker='o', label='Silhouette Score') - plt.title('Elbow Method using Silhouette Score') - plt.xlabel('Number of Clusters') - plt.ylabel('Score') - img_sil = save_path + seq + tit + '_Silhouette.png' - plt.savefig(img_sil, dpi=300) - #plt.showblock=False) - - # Plot Elbow Curve - plt.figure(figsize=(6, 4)) - plt.plot( c_scores, wss_values, marker='*', label='WSS') - plt.title('Elbow Method using WSS') - plt.xlabel('Number of Clusters') - plt.ylabel('Score') - img_wss = save_path + seq + tit + '_WSS.png' - plt.savefig(img_wss, dpi=300) - #plt.showblock=False) + with PLOT_LOCK: + # Plot Elbow Curve + plt.figure(figsize=(6, 4)) + plt.plot( c_scores, s_scores, marker='o', label='Silhouette Score') + plt.title('Elbow Method using Silhouette Score') + plt.xlabel('Number of Clusters') + plt.ylabel('Score') + plt.savefig(img_sil, dpi=300) + #plt.showblock=False) + + with PLOT_LOCK: + # Plot Elbow Curve + plt.figure(figsize=(6, 4)) + plt.plot( c_scores, wss_values, marker='*', label='WSS') + plt.title('Elbow Method using WSS') + plt.xlabel('Number of Clusters') + plt.ylabel('Score') + plt.savefig(img_wss, dpi=300) + #plt.showblock=False) return img_sil, img_wss def den_array(df, plot=True): diff --git a/DashML/Landscape/Cluster/Cluster_means.py b/DashML/Landscape/Cluster/Cluster_means.py index 62b9618dd..7f662d216 100644 --- a/DashML/Landscape/Cluster/Cluster_means.py +++ b/DashML/Landscape/Cluster/Cluster_means.py @@ -5,6 +5,8 @@ import numpy as np import pandas as pd import matplotlib + +from DashML.locks import PLOT_LOCK matplotlib.use("Agg") import matplotlib.pyplot as plt import seaborn as sns @@ -86,15 +88,16 @@ def get_clusters(X, seq, read_list, tit, clust_num): df = pd.DataFrame(columns=['read_id', 'cluster']) df['read_id'] = read_list df['cluster'] = model.labels_ - # plot the top three levels of the dendrogram - plt.figure(figsize=(10, 7)) - plt.title("KMeans Hierarchical Clustering Dendrogram") - plt.xlabel("Number of points in node (or index of point).") - ax = plt.gca() - plot_dendrogram(model, truncate_mode="level", p=5, ax=ax) - img_path = save_path + seq + tit + "_dendrogram.png" - plt.savefig(img_path, dpi=600) - #plt.showblock=False) + with PLOT_LOCK: + # plot the top three levels of the dendrogram + plt.figure(figsize=(10, 7)) + plt.title("KMeans Hierarchical Clustering Dendrogram") + plt.xlabel("Number of points in node (or index of point).") + ax = plt.gca() + plot_dendrogram(model, truncate_mode="level", p=5, ax=ax) + img_path = save_path + seq + tit + "_dendrogram.png" + plt.savefig(img_path, dpi=600) + #plt.showblock=False) return df, img_path @@ -106,27 +109,29 @@ def clustermap(X, seq='HCV', tit='Kmeans'): print(z_col.shape) z_row = linkage(squareform(X), method='complete') print(z_row.shape) - g = sns.clustermap(data=X, row_linkage=z_row, col_linkage=z_col, figsize = (8,8)) - #g.ax_heatmap.set_xlabel("Cluster Labels", labelpad=1, fontweight='extra bold') - #g.ax_heatmap.set_ylabel("Clusters Labels", labelpad=1, fontweight='extra bold') - #plt.title(tit + " Inter-Cluster Distances ") - plt.ylabel("Distance (Min-Max)") - g.savefig(save_path + seq + tit + '_clustermap.png', dpi=600) - #plt.showblock=False) - #print(dir(g)) - #print(g.data2d) + with PLOT_LOCK: + g = sns.clustermap(data=X, row_linkage=z_row, col_linkage=z_col, figsize = (8,8)) + #g.ax_heatmap.set_xlabel("Cluster Labels", labelpad=1, fontweight='extra bold') + #g.ax_heatmap.set_ylabel("Clusters Labels", labelpad=1, fontweight='extra bold') + #plt.title(tit + " Inter-Cluster Distances ") + plt.ylabel("Distance (Min-Max)") + g.savefig(save_path + seq + tit + '_clustermap.png', dpi=600) + #plt.showblock=False) + #print(dir(g)) + #print(g.data2d) return def corrmatrix(seq='HCV', data=None, tit="Kmeans"): - g = sns.heatmap(np.corrcoef(data)) - g.set_xlabel("Reads") - g.set_ylabel("Reads") - g.set_title( "Kmeans Read Correlations") - g.get_figure().savefig(save_path + seq + tit + '_read_corr_matrix.png', dpi=600) - img_path = save_path + seq + tit + '_read_corr_matrix.png' - #plt.showblock=False) - #print(np.corrcoef(data)) + with PLOT_LOCK: + g = sns.heatmap(np.corrcoef(data)) + g.set_xlabel("Reads") + g.set_ylabel("Reads") + g.set_title( "Kmeans Read Correlations") + g.get_figure().savefig(save_path + seq + tit + '_read_corr_matrix.png', dpi=600) + img_path = save_path + seq + tit + '_read_corr_matrix.png' + #plt.showblock=False) + #print(np.corrcoef(data)) return img_path #### kmeans to get centroids #### @@ -164,22 +169,23 @@ def get_centroids(X, clusters, seq): ###### Heatmap of Reads Against Summed Reactivity Across different Metrics ###### def heatmap(df, seq, tit): - fig = plt.gcf() - plt.figure(figsize=(60, 60)) - ax = plt.gca() - df = df[['position', 'contig', 'read_id', 'Reactivity']] - result = df.pivot(index="position", columns="read_id", values="Reactivity") - seq_len = len(df['position'].unique()) - sns.heatmap(result, cmap='crest', - yticklabels=range(0,seq_len), ax=ax) - plt.yticks(fontsize=10) - plt.xticks(fontsize=10) - plt.xlabel("Read Number", fontsize=100, weight='bold') - plt.ylabel("Position", fontsize=100, weight='bold') - plt.title(seq + " Reactivity", fontsize=100, weight='bold') img_path = save_path + seq + tit + '_heatmap.png' - plt.savefig(img_path, dpi=300) - #plt.showblock=False) + with PLOT_LOCK: + fig = plt.gcf() + plt.figure(figsize=(60, 60)) + ax = plt.gca() + df = df[['position', 'contig', 'read_id', 'Reactivity']] + result = df.pivot(index="position", columns="read_id", values="Reactivity") + seq_len = len(df['position'].unique()) + sns.heatmap(result, cmap='crest', + yticklabels=range(0,seq_len), ax=ax) + plt.yticks(fontsize=10) + plt.xticks(fontsize=10) + plt.xlabel("Read Number", fontsize=100, weight='bold') + plt.ylabel("Position", fontsize=100, weight='bold') + plt.title(seq + " Reactivity", fontsize=100, weight='bold') + plt.savefig(img_path, dpi=300) + #plt.showblock=False) return img_path diff --git a/DashML/Landscape/Cluster/Cluster_mode.py b/DashML/Landscape/Cluster/Cluster_mode.py index 4d5fab563..3f21ad2fb 100644 --- a/DashML/Landscape/Cluster/Cluster_mode.py +++ b/DashML/Landscape/Cluster/Cluster_mode.py @@ -5,6 +5,8 @@ import numpy as np import pandas as pd import matplotlib + +from DashML.locks import PLOT_LOCK matplotlib.use("Agg") import matplotlib.pyplot as plt import seaborn as sns @@ -84,16 +86,17 @@ def get_clusters(X, seq, read_list, tit, clust_num, plot=False): df = pd.DataFrame(columns=['read_id', 'cluster']) df['read_id'] = read_list df['cluster'] = model.labels_ + img_path = save_path + seq + tit + "_dendrogram.png" if plot: - # plot the top three levels of the dendrogram - plt.figure(figsize=(10, 7)) - plt.title("Hamming Hierarchical Clustering Dendrogram") - plt.xlabel("Number of points in node (or index of point).") - ax = plt.gca() - plot_dendrogram(model, truncate_mode="level", p=5, ax=ax) - img_path = save_path + seq + tit + "_dendrogram.png" - plt.savefig(img_path, dpi=600) - #plt.showblock=False) + with PLOT_LOCK: + # plot the top three levels of the dendrogram + plt.figure(figsize=(10, 7)) + plt.title("Hamming Hierarchical Clustering Dendrogram") + plt.xlabel("Number of points in node (or index of point).") + ax = plt.gca() + plot_dendrogram(model, truncate_mode="level", p=5, ax=ax) + plt.savefig(img_path, dpi=600) + #plt.showblock=False) return df, img_path # hybrid correlation matrix using distance instance of correlation @@ -104,27 +107,30 @@ def clustermap(X, seq='HCV', tit='Hamming'): print(z_col.shape) z_row = linkage(squareform(X), method='complete') print(z_row.shape) - g = sns.clustermap(data=X, row_linkage=z_row, col_linkage=z_col, figsize = (8,8)) - #g.ax_heatmap.set_xlabel("Cluster Labels", labelpad=1, fontweight='extra bold') - #g.ax_heatmap.set_ylabel("Clusters Labels", labelpad=1, fontweight='extra bold') - #plt.title(tit + " Inter-Cluster Distances ") - plt.ylabel("Distance (Min-Max)") - g.savefig(save_path + seq + tit + '_clustermap.png', dpi=600) - #plt.showblock=False) - #print(dir(g)) - #print(g.data2d) - return + img_path = save_path + seq + tit + '_clustermap.png' + with PLOT_LOCK: + g = sns.clustermap(data=X, row_linkage=z_row, col_linkage=z_col, figsize = (8,8)) + #g.ax_heatmap.set_xlabel("Cluster Labels", labelpad=1, fontweight='extra bold') + #g.ax_heatmap.set_ylabel("Clusters Labels", labelpad=1, fontweight='extra bold') + #plt.title(tit + " Inter-Cluster Distances ") + plt.ylabel("Distance (Min-Max)") + g.savefig(img_path, dpi=600) + #plt.showblock=False) + #print(dir(g)) + #print(g.data2d) + return img_path def corrmatrix(seq='HCV', data=None, tit="Hamming"): - g = sns.heatmap(np.corrcoef(data)) - g.set_xlabel("Reads") - g.set_ylabel("Reads") - g.set_title( "Hamming Read Correlations") - img_path = save_path + seq + tit + '_read_corr_matrix.png' - g.get_figure().savefig(img_path, dpi=600) - #plt.showblock=False) - #print(np.corrcoef(data)) + with PLOT_LOCK: + g = sns.heatmap(np.corrcoef(data)) + g.set_xlabel("Reads") + g.set_ylabel("Reads") + g.set_title( "Hamming Read Correlations") + img_path = save_path + seq + tit + '_read_corr_matrix.png' + g.get_figure().savefig(img_path, dpi=600) + #plt.showblock=False) + #print(np.corrcoef(data)) return img_path #### kmode to get centroids #### @@ -164,22 +170,23 @@ def get_centroids(data, clusters, seq): ###### Heatmap of Reads Against Summed Reactivity Across different Metrics ###### def heatmap(df, seq, tit): - fig = plt.gcf() - plt.figure(figsize=(60, 60)) - ax = plt.gca() - df = df[['position', 'contig', 'read_id', 'Reactivity']] - result = df.pivot(index="position", columns="read_id", values="Reactivity") - seq_len = len(df['position'].unique()) - sns.heatmap(result, cmap='crest', - yticklabels=range(0,seq_len), ax=ax) - plt.yticks(fontsize=10) - plt.xticks(fontsize=10) - plt.xlabel("Read Number", fontsize=100, weight='bold') - plt.ylabel("Position", fontsize=100, weight='bold') - plt.title(seq + " Reactivity", fontsize=100, weight='bold') img_path = save_path + seq + tit + '_heatmap.png' - plt.savefig(img_path, dpi=300) - #plt.showblock=False) + with PLOT_LOCK: + fig = plt.gcf() + plt.figure(figsize=(60, 60)) + ax = plt.gca() + df = df[['position', 'contig', 'read_id', 'Reactivity']] + result = df.pivot(index="position", columns="read_id", values="Reactivity") + seq_len = len(df['position'].unique()) + sns.heatmap(result, cmap='crest', + yticklabels=range(0,seq_len), ax=ax) + plt.yticks(fontsize=10) + plt.xticks(fontsize=10) + plt.xlabel("Read Number", fontsize=100, weight='bold') + plt.ylabel("Position", fontsize=100, weight='bold') + plt.title(seq + " Reactivity", fontsize=100, weight='bold') + plt.savefig(img_path, dpi=300) + #plt.showblock=False) return img_path def den_array(df, clust_num, plot=True): diff --git a/DashML/Landscape/Cluster/Cluster_native.py b/DashML/Landscape/Cluster/Cluster_native.py index e35adf2fd..2b409b1fb 100644 --- a/DashML/Landscape/Cluster/Cluster_native.py +++ b/DashML/Landscape/Cluster/Cluster_native.py @@ -3,6 +3,8 @@ import numpy as np import pandas as pd import matplotlib + +from DashML.locks import PLOT_LOCK matplotlib.use("Agg") import matplotlib.pyplot as plt import seaborn as sns @@ -95,49 +97,56 @@ def get_native_rx(lid): ###### Correlation between Representatives Centroids ###### def corrmatrixh(seq='HCV', data=None, tit="Hamming", clust_num=5): - plt.clf() - plt.figure(figsize=(20, 20)) - print(data.shape) - arr = np.zeros((clust_num, clust_num)) - for i in range(0, clust_num-1): - for j in range(0, clust_num-1): - arr[i,j] = adjusted_mutual_info_score(data[i,:], data[j,:]) - g = sns.heatmap(arr, annot=True) - g.set_xlabel("Clusters") - g.set_ylabel("Clusters") - g.set_title( tit + " Cluster Correlations (Adjusted Mutual Information)") - g.get_figure().savefig(save_path + seq +'_'+ tit + '_corr_matrix.png', dpi=600) - #plt.showblock=False) - #print(np.corrcoef(data)) + img_path = save_path + seq +'_'+ tit + '_corr_matrix.png' + with PLOT_LOCK: + plt.clf() + plt.figure(figsize=(20, 20)) + print(data.shape) + arr = np.zeros((clust_num, clust_num)) + for i in range(0, clust_num-1): + for j in range(0, clust_num-1): + arr[i,j] = adjusted_mutual_info_score(data[i,:], data[j,:]) + g = sns.heatmap(arr, annot=True) + g.set_xlabel("Clusters") + g.set_ylabel("Clusters") + g.set_title( tit + " Cluster Correlations (Adjusted Mutual Information)") + g.get_figure().savefig(img_path, dpi=600) + #plt.showblock=False) + #print(np.corrcoef(data)) + return img_path def corrmatrixk(seq='HCV', data=None, tit="Kmeans", clust_num=5): - plt.clf() - plt.figure(figsize=(20, 20)) - arr = np.zeros((clust_num, clust_num)) - for i in range(0, clust_num - 1): - for j in range(0, clust_num - 1): - arr[i, j] = adjusted_mutual_info_score(data[i, :], data[j, :]) - #g = sns.heatmap(np.nan_to_num(np.corrcoef(data)), annot=True) - g = sns.heatmap(arr, annot=True) - g.set_xlabel("Clusters") - g.set_ylabel("Clusters") - g.set_title( tit + " Cluster Correlations (Adjusted Mutual Information)") img_path = save_path + seq +'_'+ tit + '_corr_matrix.png' - g.get_figure().savefig(img_path, dpi=600) - #plt.showblock=False) + with PLOT_LOCK: + plt.clf() + plt.figure(figsize=(20, 20)) + arr = np.zeros((clust_num, clust_num)) + for i in range(0, clust_num - 1): + for j in range(0, clust_num - 1): + arr[i, j] = adjusted_mutual_info_score(data[i, :], data[j, :]) + #g = sns.heatmap(np.nan_to_num(np.corrcoef(data)), annot=True) + g = sns.heatmap(arr, annot=True) + g.set_xlabel("Clusters") + g.set_ylabel("Clusters") + g.set_title( tit + " Cluster Correlations (Adjusted Mutual Information)") + g.get_figure().savefig(img_path, dpi=600) + #plt.showblock=False) return img_path ###### Heatmap of Cluster Representatives Against native structure ###### def heatmap(X, seq="HCV", method="Kmeans"): - fig = plt.gcf() - plt.figure(figsize=(20, 8)) - plt.title( seq + " " + method + " Centroid Distance from Native Structure.\n") - ax = sns.heatmap(X, cmap='crest', annot=True, square=True) - ax.set(xlabel="Clusters", ylabel=seq) - plt.savefig(save_path + seq + '_' + method + '_heatmap.png', dpi=600) - #plt.show) + img_path = save_path + seq + '_' + method + '_heatmap.png' + with PLOT_LOCK: + fig = plt.gcf() + plt.figure(figsize=(20, 8)) + plt.title( seq + " " + method + " Centroid Distance from Native Structure.\n") + ax = sns.heatmap(X, cmap='crest', annot=True, square=True) + ax.set(xlabel="Clusters", ylabel=seq) + plt.savefig(img_path, dpi=600) + #plt.show) + return img_path def den_array(lids, native_lid, plot=True): diff --git a/DashML/Predict/run_predict.py b/DashML/Predict/run_predict.py index 2ffe378aa..e34bf3513 100644 --- a/DashML/Predict/run_predict.py +++ b/DashML/Predict/run_predict.py @@ -1,6 +1,8 @@ import sys, os import numpy as np import matplotlib + +from DashML.locks import PLOT_LOCK matplotlib.use("Agg") import matplotlib.pyplot as plt import platform @@ -210,27 +212,28 @@ def get_graph_ave(lids): df.rename(columns={'Rnafold_shape_reactivity': 'Reactivity'}, inplace=True) seq_name = 'AverageReactivity_' + df['contig'].unique()[0] + '_' + str.replace(str(lids), ',', '-') filtered_df = df[(df['position'] >= 0) & (df['position'] <= 100)] - ax = filtered_df.plot.bar(x='position', y='Reactivity', legend=False, figsize=(8, 2), width=0.8) - # Set the y-axis minimum - ax.set_ylim(0, 2) - # Set ticks only every 5th position - positions = df['position'].values - tick_indices = np.arange(0, len(positions), 5) # every 5th bar - ax.set_xticks(tick_indices) - ax.set_xticklabels([positions[i] for i in tick_indices], rotation=45, fontsize=6) - ax.tick_params(axis='y', labelsize=6) - ax.set_xlabel('Position', fontsize=8) - ax.set_ylabel('Reactivity', fontsize=8) - ax.set_title('Average Predicted Reactivity', fontsize=9) - subdir = output / "Figures" - subdir.mkdir(parents=True, exist_ok=True) - figname1 = subdir / f"{seq_name}.png" - figname1 = str(figname1.resolve()) - fig = ax.get_figure() - fig.tight_layout() - plt.savefig(figname1) - plt.close(fig) - ##plt.show) + with PLOT_LOCK: + ax = filtered_df.plot.bar(x='position', y='Reactivity', legend=False, figsize=(8, 2), width=0.8) + # Set the y-axis minimum + ax.set_ylim(0, 2) + # Set ticks only every 5th position + positions = df['position'].values + tick_indices = np.arange(0, len(positions), 5) # every 5th bar + ax.set_xticks(tick_indices) + ax.set_xticklabels([positions[i] for i in tick_indices], rotation=45, fontsize=6) + ax.tick_params(axis='y', labelsize=6) + ax.set_xlabel('Position', fontsize=8) + ax.set_ylabel('Reactivity', fontsize=8) + ax.set_title('Average Predicted Reactivity', fontsize=9) + subdir = output / "Figures" + subdir.mkdir(parents=True, exist_ok=True) + figname1 = subdir / f"{seq_name}.png" + figname1 = str(figname1.resolve()) + fig = ax.get_figure() + fig.tight_layout() + plt.savefig(figname1) + plt.close(fig) + ##plt.show) # print current data subdir = output / "Data" @@ -245,31 +248,32 @@ def get_graph_ave(lids): df = df.rename(columns={'Base_pair_prob': 'Base Pairing Prob.'}) filtered_df = filtered_df.rename(columns={'Base_pair_prob': 'Base Pairing Prob.'}) seq_name = 'AveMaxBasePairingProbability_' + df['contig'].unique()[0] + '_' + str.replace(str(lids), ',', '-') - ax = filtered_df.plot.bar( - x='position', y='Base Pairing Prob.', legend=False, - figsize=(8, 2), width=0.8 - ) - # Set the y-axis minimum - ax.set_ylim(0, 1) - # Set ticks only every 5th position - positions = df['position'].values - tick_indices = np.arange(0, len(positions), 5) # every 5th bar - ax.set_xticks(tick_indices) - ax.set_xticklabels([positions[i] for i in tick_indices], rotation=45, fontsize=8) - # Set font size on y-axis ticks - ax.tick_params(axis='y', labelsize=8) - ax.set_xlabel('Position', fontsize=8) - ax.set_ylabel('Probability', fontsize=8) - ax.set_title('Ave. Max Base Pairing Prob. (ViennaRNA)', fontsize=9) - subdir = output / "Figures" - subdir.mkdir(parents=True, exist_ok=True) - figname2 = subdir / f"{seq_name}.png" - figname2 = str(figname2.resolve()) - fig = ax.get_figure() - fig.tight_layout() - plt.savefig(figname2) - plt.close(fig) - # #plt.show) + with PLOT_LOCK: + ax = filtered_df.plot.bar( + x='position', y='Base Pairing Prob.', legend=False, + figsize=(8, 2), width=0.8 + ) + # Set the y-axis minimum + ax.set_ylim(0, 1) + # Set ticks only every 5th position + positions = df['position'].values + tick_indices = np.arange(0, len(positions), 5) # every 5th bar + ax.set_xticks(tick_indices) + ax.set_xticklabels([positions[i] for i in tick_indices], rotation=45, fontsize=8) + # Set font size on y-axis ticks + ax.tick_params(axis='y', labelsize=8) + ax.set_xlabel('Position', fontsize=8) + ax.set_ylabel('Probability', fontsize=8) + ax.set_title('Ave. Max Base Pairing Prob. (ViennaRNA)', fontsize=9) + subdir = output / "Figures" + subdir.mkdir(parents=True, exist_ok=True) + figname2 = subdir / f"{seq_name}.png" + figname2 = str(figname2.resolve()) + fig = ax.get_figure() + fig.tight_layout() + plt.savefig(figname2) + plt.close(fig) + # #plt.show) # print current data subdir = output / "Data" diff --git a/DashML/__init__.py b/DashML/__init__.py index 248842142..d9ad9f78a 100644 --- a/DashML/__init__.py +++ b/DashML/__init__.py @@ -5,4 +5,7 @@ from DashML import Database_fx from DashML import Basecall -__all__ = ["Predict", "GUI", "Landscape", "Basecall", "UI"] +# central locking mechanism for thread-safe plotting +from DashML import locks + +__all__ = ["Predict", "GUI", "Landscape", "Basecall", "UI", "locks"] diff --git a/DashML/locks.py b/DashML/locks.py new file mode 100644 index 000000000..1d4f26d98 --- /dev/null +++ b/DashML/locks.py @@ -0,0 +1,3 @@ +import threading + +PLOT_LOCK = threading.RLock() From dbec4afdbbd07a3c7ba0b553b70e34b9170f5a93 Mon Sep 17 00:00:00 2001 From: AlexanderButyaev Date: Mon, 22 Sep 2025 14:28:11 -0400 Subject: [PATCH 2/2] fixed bugs/typos --- DashML/GUI/DT.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DashML/GUI/DT.py b/DashML/GUI/DT.py index bacaf7553..31ba7d957 100644 --- a/DashML/GUI/DT.py +++ b/DashML/GUI/DT.py @@ -1336,11 +1336,13 @@ def handle_start_worker(self, params): source = params.get('source') if source == 'basecall': worker = BasecallWorker( + source, params['lid'], params['contig'], params['basecall_path'], params['modification'], params['modification2'] ) elif source == 'signal': worker = SignalWorker( + source, params['lid'], params['contig'], params['signal_path'], params['modification'], params['modification2'] ) @@ -1375,7 +1377,7 @@ def on_worker_finished(self, source, gpath1, gpath2): self.load_basecall_section.basecall_graph2 = new_graph2 else: layout = self.load_signal_section.signal_graphs_layout - old1, old2 = self.load_signal_section.signal_graph1, self.load_basecall_section.signal_graph2 + old1, old2 = self.load_signal_section.signal_graph1, self.load_signal_section.signal_graph2 new_graph1 = self.load_signal_section.create_sample_graph( f"{source.capitalize()}: Average Signal Rates by Position", gpath1) new_graph2 = self.load_signal_section.create_sample_graph(