@@ -29,6 +29,7 @@ def split_clusters(
2929 recursive = False ,
3030 recursive_depth = None ,
3131 returns_split_count = False ,
32+ debug_folder = None ,
3233 ** job_kwargs ,
3334):
3435 """
@@ -42,7 +43,7 @@ def split_clusters(
4243 Recording object
4344 features_dict_or_folder: dict or folder
4445 A dictionary of features precomputed with peak_pipeline or a folder containing npz file for features
45- method: str, default: "hdbscan_on_local_pca "
46+ method: str, default: "local_feature_clustering "
4647 The method name
4748 method_kwargs: dict, default: dict()
4849 The method option
@@ -70,7 +71,7 @@ def split_clusters(
7071 original_labels = peak_labels
7172 peak_labels = peak_labels .copy ()
7273 split_count = np .zeros (peak_labels .size , dtype = int )
73-
74+ recursion_level = 1
7475 Executor = get_poolexecutor (n_jobs )
7576
7677 with Executor (
@@ -82,19 +83,33 @@ def split_clusters(
8283 labels_set = np .setdiff1d (peak_labels , [- 1 ])
8384 current_max_label = np .max (labels_set ) + 1
8485 jobs = []
86+
87+ if debug_folder is not None :
88+ if debug_folder .exists ():
89+ import shutil
90+
91+ shutil .rmtree (debug_folder )
92+ debug_folder .mkdir (parents = True , exist_ok = True )
93+
8594 for label in labels_set :
8695 peak_indices = np .flatnonzero (peak_labels == label )
96+ if debug_folder is not None :
97+ sub_folder = str (debug_folder / f"split_{ label } " )
98+
99+ else :
100+ sub_folder = None
87101 if peak_indices .size > 0 :
88- jobs .append (pool .submit (split_function_wrapper , peak_indices , 1 ))
102+ jobs .append (pool .submit (split_function_wrapper , peak_indices , recursion_level , sub_folder ))
89103
90104 if progress_bar :
91- iterator = tqdm (jobs , desc = f"split_clusters with { method } " , total = len (labels_set ))
92- else :
93- iterator = jobs
105+ pbar = tqdm (desc = f"split_clusters with { method } " , total = len (labels_set ))
106+
107+ for res in jobs :
108+ is_split , local_labels , peak_indices , sub_folder = res .result ()
109+
110+ if progress_bar :
111+ pbar .update (1 )
94112
95- for res in iterator :
96- is_split , local_labels , peak_indices = res .result ()
97- # print(is_split, local_labels, peak_indices)
98113 if not is_split :
99114 continue
100115
@@ -117,11 +132,21 @@ def split_clusters(
117132 new_labels_set = np .setdiff1d (peak_labels [peak_indices ], [- 1 ])
118133 for label in new_labels_set :
119134 peak_indices = np .flatnonzero (peak_labels == label )
135+ if sub_folder is not None :
136+ new_sub_folder = sub_folder + f"_{ label } "
137+ else :
138+ new_sub_folder = None
120139 if peak_indices .size > 0 :
121140 # print('Relaunched', label, len(peak_indices), recursion_level)
122- jobs .append (pool .submit (split_function_wrapper , peak_indices , recursion_level ))
141+ jobs .append (
142+ pool .submit (split_function_wrapper , peak_indices , recursion_level , new_sub_folder )
143+ )
123144 if progress_bar :
124- iterator .total += 1
145+ pbar .total += 1
146+
147+ if progress_bar :
148+ pbar .close ()
149+ del pbar
125150
126151 if returns_split_count :
127152 return peak_labels , split_count
@@ -149,13 +174,13 @@ def split_worker_init(
149174 _ctx ["peaks" ] = _ctx ["features" ]["peaks" ]
150175
151176
152- def split_function_wrapper (peak_indices , recursion_level ):
177+ def split_function_wrapper (peak_indices , recursion_level , debug_folder ):
153178 global _ctx
154179 with threadpool_limits (limits = _ctx ["max_threads_per_worker" ]):
155180 is_split , local_labels = _ctx ["method_class" ].split (
156- peak_indices , _ctx ["peaks" ], _ctx ["features" ], recursion_level , ** _ctx ["method_kwargs" ]
181+ peak_indices , _ctx ["peaks" ], _ctx ["features" ], recursion_level , debug_folder , ** _ctx ["method_kwargs" ]
157182 )
158- return is_split , local_labels , peak_indices
183+ return is_split , local_labels , peak_indices , debug_folder
159184
160185
161186class LocalFeatureClustering :
@@ -178,14 +203,14 @@ def split(
178203 peaks ,
179204 features ,
180205 recursion_level = 1 ,
206+ debug_folder = None ,
181207 clusterer = "hdbscan" ,
182208 feature_name = "sparse_tsvd" ,
183209 neighbours_mask = None ,
184210 waveforms_sparse_mask = None ,
185211 clusterer_kwargs = {"min_cluster_size" : 25 },
186212 min_size_split = 25 ,
187213 n_pca_features = 2 ,
188- scale_n_pca_by_depth = False ,
189214 minimum_overlap_ratio = 0.25 ,
190215 ):
191216 local_labels = np .zeros (peak_indices .size , dtype = np .int64 )
@@ -213,26 +238,23 @@ def split(
213238
214239 local_labels [dont_have_channels ] = - 2
215240 kept = np .flatnonzero (~ dont_have_channels )
216- # print(recursion_level, kept.size, min_size_split)
241+
217242 if kept .size < min_size_split :
218243 return False , None
219244
220245 aligned_wfs = aligned_wfs [kept , :, :]
221-
222246 flatten_features = aligned_wfs .reshape (aligned_wfs .shape [0 ], - 1 )
223247
248+ is_split = False
249+
224250 if flatten_features .shape [1 ] > n_pca_features :
225251 from sklearn .decomposition import PCA
226252
227253 # from sklearn.decomposition import TruncatedSVD
228-
229- if scale_n_pca_by_depth :
230- # tsvd = TruncatedSVD(n_pca_features * recursion_level)
231- tsvd = PCA (n_pca_features * recursion_level , whiten = True )
232- else :
233- # tsvd = TruncatedSVD(n_pca_features)
234- tsvd = PCA (n_pca_features , whiten = True )
254+ # tsvd = TruncatedSVD(n_pca_features)
255+ tsvd = PCA (n_pca_features , whiten = True )
235256 final_features = tsvd .fit_transform (flatten_features )
257+ del tsvd
236258 else :
237259 final_features = flatten_features
238260
@@ -243,6 +265,7 @@ def split(
243265 clust .fit (final_features )
244266 possible_labels = clust .labels_
245267 is_split = np .setdiff1d (possible_labels , [- 1 ]).size > 1
268+ del clust
246269 elif clusterer == "isocut5" :
247270 min_cluster_size = clusterer_kwargs ["min_cluster_size" ]
248271 dipscore , cutpoint = isocut5 (final_features [:, 0 ])
@@ -255,32 +278,43 @@ def split(
255278 else :
256279 is_split = False
257280 else :
258- raise ValueError (f"wrong clusterer { clusterer } " )
281+ raise ValueError (f"wrong clusterer { clusterer } . Possible options are 'hdbscan' or 'isocut5'." )
282+
283+ DEBUG = False # only for Sam or dirty hacking
259284
260- # DEBUG = True
261- DEBUG = False
262- if DEBUG :
285+ if debug_folder is not None or DEBUG :
263286 import matplotlib .pyplot as plt
264287
265288 labels_set = np .setdiff1d (possible_labels , [- 1 ])
266289 colors = plt .colormaps ["tab10" ].resampled (len (labels_set ))
267290 colors = {k : colors (i ) for i , k in enumerate (labels_set )}
268291 colors [- 1 ] = "k"
269- fix , axs = plt .subplots (nrows = 2 )
292+ fig , axs = plt .subplots (nrows = 2 )
270293
271294 flatten_wfs = aligned_wfs .swapaxes (1 , 2 ).reshape (aligned_wfs .shape [0 ], - 1 )
272295
273- sl = slice (None , None , 10 )
296+ sl = slice (None , None , 100 )
274297 for k in np .unique (possible_labels ):
275298 mask = possible_labels == k
276299 ax = axs [0 ]
277- ax .scatter (final_features [:, 0 ][mask ][sl ], final_features [:, 1 ][mask ][sl ], s = 5 , color = colors [k ])
278-
300+ ax .scatter (final_features [:, 0 ][mask ], final_features [:, 1 ][mask ], s = 5 , color = colors [k ])
301+ if k > - 1 :
302+ centroid = final_features [:, :2 ][mask ].mean (axis = 0 )
303+ ax .text (centroid [0 ], centroid [1 ], f"Label { k } " , fontsize = 10 , color = "k" )
279304 ax = axs [1 ]
280305 ax .plot (flatten_wfs [mask ][sl ].T , color = colors [k ], alpha = 0.5 )
281-
282- axs [0 ].set_title (f"{ clusterer } { is_split } { peak_indices [0 ]} { np .unique (possible_labels )} " )
283- plt .show ()
306+ if k > - 1 :
307+ ax .plot (np .median (flatten_wfs [mask ].T , axis = 1 ), color = colors [k ], lw = 2 )
308+ ax .set_xlabel ("PCA features" )
309+ ymin , ymax = ax .get_ylim ()
310+ ax .plot ([n_pca_features , n_pca_features ], [ymin , ymax ], "k--" )
311+
312+ axs [0 ].set_title (f"{ clusterer } level={ recursion_level } " )
313+ if not DEBUG :
314+ fig .savefig (str (debug_folder ) + ".png" )
315+ plt .close (fig )
316+ else :
317+ plt .show ()
284318
285319 if not is_split :
286320 return is_split , None
0 commit comments