11from __future__ import annotations
2- from typing import Literal , Optional
2+ from typing import Literal , Optional , Any
33
44from pathlib import Path
55from itertools import chain
@@ -223,13 +223,13 @@ class SortingAnalyzer:
223223
224224 def __init__ (
225225 self ,
226- sorting = None ,
227- recording = None ,
228- rec_attributes = None ,
229- format = None ,
230- sparsity = None ,
231- return_scaled = True ,
232- backend_options = None ,
226+ sorting : BaseSorting ,
227+ recording : BaseRecording | None = None ,
228+ rec_attributes : dict | None = None ,
229+ format : str | None = None ,
230+ sparsity : ChannelSparsity | None = None ,
231+ return_scaled : bool = True ,
232+ backend_options : dict | None = None ,
233233 ):
234234 # very fast init because checks are done in load and create
235235 self .sorting = sorting
@@ -239,6 +239,7 @@ def __init__(
239239 self .format = format
240240 self .sparsity = sparsity
241241 self .return_scaled = return_scaled
242+ self .folder : str | Path | None = None
242243
243244 # this is used to store temporary recording
244245 self ._temporary_recording = None
@@ -742,6 +743,75 @@ def set_temporary_recording(self, recording: BaseRecording, check_dtype: bool =
742743 warnings .warn ("SortingAnalyzer recording is already set. The current recording is temporarily replaced." )
743744 self ._temporary_recording = recording
744745
746+ def set_sorting_property (
747+ self ,
748+ key ,
749+ values : list | np .ndarray | tuple ,
750+ ids : list | np .ndarray | tuple | None = None ,
751+ missing_value : Any = None ,
752+ save : bool = True ,
753+ ) -> None :
754+ """
755+ Set property vector for unit ids.
756+
757+ If the SortingAnalyzer backend is in memory, the property will be only set in memory.
758+ If the SortingAnalyzer backend is `binary_folder` or `zarr`, the property will also
759+ be saved to to the backend.
760+
761+ Parameters
762+ ----------
763+ key : str
764+ The property name
765+ values : np.array
766+ Array of values for the property
767+ ids : list/np.array, default: None
768+ List of subset of ids to set the values.
769+ if None all the ids are set or changed
770+ missing_value : Any, default: None
771+ In case the property is set on a subset of values ("ids" not None),
772+ This argument specifies how to fill missing values.
773+ The `missing_value` is required for types int and unsigned int.
774+ save : bool, default: True
775+ If True, the property is saved to the backend if possible.
776+ """
777+ self .sorting .set_property (key , values , ids = ids , missing_value = missing_value )
778+ if not self .is_read_only () and save :
779+ if self .format == "binary_folder" :
780+ np .save (self .folder / "sorting" / "properties" / f"{ key } .npy" , self .sorting .get_property (key ))
781+ elif self .format == "zarr" :
782+ import zarr
783+
784+ zarr_root = self ._get_zarr_root (mode = "r+" )
785+ prop_values = self .sorting .get_property (key )
786+ if prop_values .dtype .kind == "O" :
787+ warnings .warn (f"Property { key } not saved because it is a python Object type" )
788+ else :
789+ if key in zarr_root ["sorting" ]["properties" ]:
790+ zarr_root ["sorting" ]["properties" ][key ][:] = prop_values
791+ else :
792+ zarr_root ["sorting" ]["properties" ].create_dataset (name = key , data = prop_values , compressor = None )
793+ # IMPORTANT: we need to re-consolidate the zarr store!
794+ zarr .consolidate_metadata (zarr_root .store )
795+
796+ def get_sorting_property (self , key : str , ids : Optional [Iterable ] = None ) -> np .ndarray :
797+ """
798+ Get property vector for unit ids.
799+
800+ Parameters
801+ ----------
802+ key : str
803+ The property name
804+ ids : list/np.array, default: None
805+ List of subset of ids to get the values.
806+ if None all the ids are returned
807+
808+ Returns
809+ -------
810+ values : np.array
811+ Array of values for the property
812+ """
813+ return self .sorting .get_property (key , ids = ids )
814+
745815 def _save_or_select_or_merge (
746816 self ,
747817 format = "binary_folder" ,
@@ -1177,6 +1247,9 @@ def get_sorting_provenance(self):
11771247 filename = self .folder / f"sorting_provenance.{ type } "
11781248 sorting_provenance = None
11791249 if filename .exists ():
1250+ # try-except here is because it's not required to be able
1251+ # to load the sorting provenance, as the user might have deleted
1252+ # the original sorting folder
11801253 try :
11811254 sorting_provenance = load (filename , base_folder = self .folder )
11821255 break
@@ -1186,11 +1259,16 @@ def get_sorting_provenance(self):
11861259
11871260 elif self .format == "zarr" :
11881261 zarr_root = self ._get_zarr_root (mode = "r" )
1262+ sorting_provenance = None
11891263 if "sorting_provenance" in zarr_root .keys ():
1190- sort_dict = zarr_root ["sorting_provenance" ][0 ]
1191- sorting_provenance = load (sort_dict , base_folder = self .folder )
1192- else :
1193- sorting_provenance = None
1264+ # try-except here is because it's not required to be able
1265+ # to load the sorting provenance, as the user might have deleted
1266+ # the original sorting folder
1267+ try :
1268+ sort_dict = zarr_root ["sorting_provenance" ][0 ]
1269+ sorting_provenance = load (sort_dict , base_folder = self .folder )
1270+ except :
1271+ pass
11941272
11951273 return sorting_provenance
11961274
0 commit comments