From 9720bdb2dfb613bb31d938d909719ee69110e71a Mon Sep 17 00:00:00 2001 From: Carlin Liao Date: Sat, 16 Dec 2023 19:53:48 -0500 Subject: [PATCH] save adj separately --- spatialprofilingtoolbox/cggnn/util.py | 16 +++++++++++++--- .../workflow/assets/cggnn.nf | 15 +++------------ .../unit_tests/{ => graphs}/feature_names.txt | 0 test/cggnn/unit_tests/graphs/graph_0_adj.npz | Bin 0 -> 1526 bytes test/cggnn/unit_tests/graphs/graph_1_adj.npz | Bin 0 -> 1237 bytes test/cggnn/unit_tests/graphs/graph_2_adj.npz | Bin 0 -> 1179 bytes test/cggnn/unit_tests/graphs/graph_3_adj.npz | Bin 0 -> 1197 bytes test/cggnn/unit_tests/{ => graphs}/graphs.pkl | Bin 36993 -> 26294 bytes test/cggnn/unit_tests/test_plotting.sh | 2 +- 9 files changed, 17 insertions(+), 16 deletions(-) rename test/cggnn/unit_tests/{ => graphs}/feature_names.txt (100%) create mode 100644 test/cggnn/unit_tests/graphs/graph_0_adj.npz create mode 100644 test/cggnn/unit_tests/graphs/graph_1_adj.npz create mode 100644 test/cggnn/unit_tests/graphs/graph_2_adj.npz create mode 100644 test/cggnn/unit_tests/graphs/graph_3_adj.npz rename test/cggnn/unit_tests/{ => graphs}/graphs.pkl (62%) diff --git a/spatialprofilingtoolbox/cggnn/util.py b/spatialprofilingtoolbox/cggnn/util.py index d2302bc23..cf6609dce 100644 --- a/spatialprofilingtoolbox/cggnn/util.py +++ b/spatialprofilingtoolbox/cggnn/util.py @@ -15,7 +15,7 @@ ) from numpy.random import seed as np_seed from numpy.typing import NDArray -from scipy.sparse import spmatrix +from scipy.sparse import spmatrix, save_npz, load_npz # type: ignore SETS = ('train', 'validation', 'test') SETS_type = Literal['train', 'validation', 'test'] @@ -47,19 +47,29 @@ class GraphData(NamedTuple): def save_hs_graphs(graphs_data: list[GraphData], output_directory: str) -> None: - """Save histological structure graphs to a directory.""" + """Save histological structure graphs to a directory. + + Saves the adjacency graph separately from the rest of the graph data for compatibility. + """ makedirs(output_directory, exist_ok=True) + for i, gd in enumerate(graphs_data): + save_npz(join(output_directory, f'graph_{i}_adj.npz'), gd.graph.adj) with open(join(output_directory, 'graphs.pkl'), 'wb') as f: + for gd in graphs_data: + gd.graph.adj = None # type: ignore dump(graphs_data, f) def load_hs_graphs(graph_directory: str) -> tuple[list[GraphData], list[str]]: """Load histological structure graphs from a directory. - Assumes directory contains the files `graphs.pkl` and `feature_names.txt`. + Assumes directory contains the files `graphs.pkl`, `feature_names.txt`, and a sparse array for + every graph in `graphs.pkl`. """ with open(join(graph_directory, 'graphs.pkl'), 'rb') as f: graphs_data: list[GraphData] = load(f) + for i, gd in enumerate(graphs_data): + gd.graph.adj = load_npz(join(graph_directory, f'graph_{i}_adj.npz')) feature_names: list[str] = loadtxt( join(graph_directory, 'feature_names.txt'), dtype=str, diff --git a/spatialprofilingtoolbox/workflow/assets/cggnn.nf b/spatialprofilingtoolbox/workflow/assets/cggnn.nf index c89edf153..0755d54ec 100644 --- a/spatialprofilingtoolbox/workflow/assets/cggnn.nf +++ b/spatialprofilingtoolbox/workflow/assets/cggnn.nf @@ -114,8 +114,6 @@ process finalize_graphs { output: path "results/", emit: working_directory - path "results/feature_names.txt", emit: feature_names_file - path "results/graphs.pkl", emit: graphs_file script: """ @@ -146,8 +144,7 @@ process train { output: path "${working_directory}/importances.csv", emit: importances_csv_path - path "${working_directory}/model/", emit: model_directory - path "${working_directory}/graphs.pkl", optional: true + path "${working_directory}/", emit: working_directory script: """ @@ -282,12 +279,6 @@ workflow { finalize_out.working_directory .set{ working_directory_ch } - finalize_out.feature_names_file - .set{ feature_names_ch } - - finalize_out.graphs_file - .set{ graphs_file_ch } - train( working_directory_ch, in_ram_ch, @@ -303,8 +294,8 @@ workflow { train_out.importances_csv_path .set{ importances_csv_path_ch } - train_out.model_directory - .set{ model_directory_ch } + train_out.working_directory + .set{ working_directory_ch } upload_importance_scores( upload_importances_ch, diff --git a/test/cggnn/unit_tests/feature_names.txt b/test/cggnn/unit_tests/graphs/feature_names.txt similarity index 100% rename from test/cggnn/unit_tests/feature_names.txt rename to test/cggnn/unit_tests/graphs/feature_names.txt diff --git a/test/cggnn/unit_tests/graphs/graph_0_adj.npz b/test/cggnn/unit_tests/graphs/graph_0_adj.npz new file mode 100644 index 0000000000000000000000000000000000000000..9bc3758a3af27a3d4a3595bcfc4dc32a0bfbd9cc GIT binary patch literal 1526 zcmWIWW@gc4U|`??Vnv4J&Z`suLjgB~2t#IGN@j9uv0h$5B_o3X!yZMU2%+ar zI_W2!D9~~|hcQ{~c*9LwmN$lJNn-rn8zcGeIK&j4(A<>d-r2%%Gf*#ZUX!@r_AgsA zdMz45oMPXtTlKC!?%lpS_v+8G)Hm%9+;0BkUccYr6@Rrm9~u1mc;)}y_cnFauga@W z-7k@2vwg^JZ++f&nPPrx8l0 zr=4C1Cg-1?D^o zceGB7yMJMy`R}^b8E?4ySbR54eOU7E#@l`GetmKVRh(z|B; zo4iR2B9)t;<#VpPyH_~xQqlH|NsqZ?Z#(Yfem8r`msffEXUnFYDtFj7Yqo9TwYq$x z@|n8=XX|X-{n2pF-rl?Ke)X2;EWh`xCvN`Yqj~=s15lF1^2I;PVab9Ek}L{JieRZ? z0yH(;h9nAxxhEE8GZiu%{dm8TTgqg|0!K@eKO*NAbVye@9_EprE!M1ERH4qb=&|21 z2OVwgX^Lwi!gRE!iFO}N+8CiD)}0CjuKWLS+x<9Do&P0Zr+&ETN1b1nw#?b3yLr`> z=**QXH(lIXz3t-4(%j2de_a>9jriktxnk3ejc2xsUBA*;RcF%_^B|;Vhl@a>i=Z=; zW{jgdqwAf8JzR2u&7ERa1s|Fi<36@2-uu{SXlHm(eA$mbBxMVtVu%zV|1RM)kJ8x3doR}qfOBcim=d4+`c)`4B@gYG|W{A&^pE8L{sNDOErXOpU zc)E(%5~iP0v#hwba)~gmd-6z$0o4J|-hN#Qa{wnWUWzjk3sPbBPk`F%iO>GL$(aiY z+3#iraRGx~IvYpBMKm`AcK7{(xq$=dhLpsTM3@tPa6#;jrPK+>jxB>YB86Gcjg3QM zsr$>oswr}CDl_U5&{>R3y3D9$8K|HF6r`Q@J$z=Dtq5`+aMMX(U$fri|4NB}a-J+YqqkORZf zkH!%?V%@HMJm!gF^$I7tQeCG^Wz|jIA%Z)-Pvp1zaIaeL zu%$r+jd&cvgj_F{NmI1$9u2-DHE4Bxh>gucb+zPq%E)G zD-Ca9-7Oa##rb|oBrx$C|Cpid&|#{i;xX~yqaLR4I|av?_4j-{C~p6U5!FkJ50^|w z_EK7YQEp-h%uCHsued_#*>A*ONei4j5pXPE?Yv1*b7Gd{EnN^NoU>-#;sx`j#fJn< znIS$ue##^+p>pptntrTZ;^``4OPGF2&9dU!$|b_M?#Ux122=-J4VGL$(aiY+3#iraRGy#DjP?`MKm|G-Z@nOa{~v^4JnBwi7+QzV1?Kn zLa7tlp0Pn3k;1I!#>OGB)cs{()fBm9(RkqbqmGf)Jm5t>}l^?_0WD2_q#jjB(KiGcx;CeZbPA`cWdpm^d0Vi;&( woDS3nk4kiHpzsETCn#(|g%b=kFiHb`3=91LZ&o&t3@Z?d0iC}RD96A60O$R#D*ylh literal 0 HcmV?d00001 diff --git a/test/cggnn/unit_tests/graphs/graph_2_adj.npz b/test/cggnn/unit_tests/graphs/graph_2_adj.npz new file mode 100644 index 0000000000000000000000000000000000000000..dea30c2155e3131b62b880faf69c097b8d4460bc GIT binary patch literal 1179 zcmWIWW@gc4U|`??VnqhVt1YkpLjgB~2t#IGN@j9uv0h$5B_o3XLkAO36%agxPz-bZ zc5@wa5ZLoMx8_*5&-8a9K^jq=TjwSvC#oKF5phu!l>VswC-Hj$e_O)SLkG^iIrDB` z`0S@u+DDyjLM2pxN1U}WwfCL+e&u1)ea8=`miHd^c-{SAj>{7D;}66H<$KhX>t9ss zzkk1JiGYO}lA;rT{d?lQFd{%VipOlY0+x=kjhTz{i*UsD(%CvL%`eJGUsTRZ(gx`6ybG_Brh7o~|Ocgz2Z$EGw?9Tq2C?o;*@wKy|>z!~1^09KZ<-!Qza>f>fCO6QK5b;sHN$~&6DVhKLoEeS4U8ZXDf^*o1f@k#N&}@jE|6L%Xke5B zY6WV9rc88wpdbnorhe#0U`am%UiWpENaRM<6G%zLroeqyjbZwx(1_kB> iV8DWk7#L_^d;-)43;Fv3Egl#BEJ8u`nT%MG9J-H#+tovVRjjr`6r#=^=+_w`bPs z&%c_s^Pbcr&A3kjF24iL#aP<=FL{3Iv2IPvztVDU{>De#|AknOES_A?e}ez`1 z?ri`4T~o@bLqyZ})I^_;)}Ch=g6;U%7fBwwQY&^}by92fJJ~1QOSn#|h2A`4v!gYp zvy$C%A=4k(H$b#OJ66uwmQn$EXtN`jKulBq5(6ZC-K z5%=%!qBcJsyxVs>XY#wKM{07Xb`^Zc(hQ9`Gj-alh@Y#?Q{yUM&$2gs{@};qlQA;Z zvwXjJoR0c8zfU38WNEkDFNuT>j%1&W4n3kmYbGikmb291n!>ST@iAfjJs%J9qk3cU z;gadd-bl+Y%1tbRd7~NXd{-zv`;GW3X@Qd`0*(c&oi{0JPRx?Lr3>PObJna|ykOq6 z_>iC}GsNe|PnpCeRPKF7(~q@FJY7X>3DZxhSyo(IxkMP(J$al-IbS)`F-8Mi7aV zA<;F0(j+Lwfl?nANG%jJFlqp`0yRQYD7rpSA^=4(D7sPgy$9+;BnfnVptu7?4Jev8 zffxoF7z=<-hsPwkHc)Vbf)f<9ph5=*8W_I-wZQ^Ez?+o~B*O}XVnAm!GJ|*kUZs*Z literal 0 HcmV?d00001 diff --git a/test/cggnn/unit_tests/graphs.pkl b/test/cggnn/unit_tests/graphs/graphs.pkl similarity index 62% rename from test/cggnn/unit_tests/graphs.pkl rename to test/cggnn/unit_tests/graphs/graphs.pkl index 33fd27a61896238291cc3726399fcd4bba8fd765..cc0674eea8b09104dbf0f19c6d508c86333362a9 100644 GIT binary patch delta 2736 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