brimfile.file v1.7.0rc1
1from __future__ import annotations 2 3import numpy as np 4import warnings 5 6from .data import Data 7 8from .utils import concatenate_paths 9from .constants import brim_obj_names 10from . import units 11from . import subtypes 12 13from .file_abstraction import FileAbstraction, StoreType, sync, Version 14from .validation import validate_json, ValidationError, ValidationLevel 15from .validation.json_descriptor import generate_json_descriptor 16 17# don't import _AbstractFile if running in pyodide (it is defined in js) 18import sys 19if "pyodide" not in sys.modules: 20 from .file_abstraction import _AbstractFile 21 22class File: 23 """ 24 High-level entry point for reading and writing brim files. 25 26 Use this class to open an existing brim file (`File(...)`) or create a new 27 one (`File.create(...)`), then access data groups, metadata, analysis 28 results, and validation helpers. 29 """ 30 31 if "pyodide" in sys.modules: 32 def __init__(self, file): 33 self._file = file 34 self._file.version = self._get_file_version_tuple() 35 if not self.is_valid(): 36 raise ValueError("The brim file is not valid!") 37 else: 38 def __init__(self, filename: str, mode: str = 'r', 39 store_type: StoreType = StoreType.AUTO, * , 40 validate: bool = False) -> None: 41 """ 42 Initialize the File object. 43 44 Args: 45 filename (str): Path to the brim file. 46 mode: {'r', 'r+', 'a', 'w', 'w-'} the mode for opening the file (default is 'r' for read-only). 47 See the definition of `mode` in `brimfile.file_abstraction._zarrFile.__init__()` for more details. 48 'r' means read only (must exist); 'r+' means read/write (must exist); 49 'a' means read/write (create if doesn't exist); 'w' means create (overwrite if exists); 'w-' means create (fail if exists). 50 store_type (StoreType): Type of the store to use, as defined in `brimfile.file_abstraction.StoreType`. Default is 'AUTO'. 51 validate (bool): Whether to validate the file upon initialization. Default is False. 52 """ 53 self._file = _AbstractFile( 54 filename, mode=mode, store_type=store_type) 55 self._file.version = self._get_file_version_tuple() 56 if not self.is_valid(): 57 raise ValueError("The brim file is not valid!") 58 if validate: 59 validation_errors: list[ValidationError] = self.validate() 60 for err in validation_errors: 61 if err.level == ValidationLevel.WARNING or err.level == ValidationLevel.ERROR: 62 warnings.warn(f"Validation warning at {err.path}: {err.message}") 63 elif err.level == ValidationLevel.CRITICAL: 64 raise ValueError(f"Validation error at {err.path}: {err.message}") 65 66 @staticmethod 67 def _parse_version_tuple3(version: str) -> Version: 68 """Normalize a version value into a 3-item tuple (major, minor, patch).""" 69 if isinstance(version, str): 70 parts = [part for part in version.split('.') if part != ''] 71 elif isinstance(version, (list, tuple)): 72 parts = list(version) 73 else: 74 raise ValueError(f"Invalid version format: {version}") 75 76 # convert the first 3 parts to int if they are digits (str), otherwise keep them as they are 77 normalized: list[int | str] = [] 78 for part in parts[:3]: 79 if isinstance(part, str) and part.isdigit(): 80 normalized.append(int(part)) 81 else: 82 try: 83 part = int(part) 84 normalized.append(part) 85 except (ValueError, TypeError): 86 raise ValueError(f"Invalid version part: {part}") 87 88 # add zeros to the end of the list until it has 3 elements 89 while len(normalized) < 3: 90 normalized.append(0) 91 92 return (normalized[0], normalized[1], normalized[2]) 93 94 def _get_file_version_tuple(self) -> Version: 95 """ 96 Read brim_version from file root attributes and return a 3-item tuple. 97 Returns: 98 Version: A tuple representing the version of the brim file in the format (major, minor, patch). 99 If the version can't be read from the file, returns None. 100 """ 101 try: 102 version = sync(self._file.get_attr('/', 'brim_version')) 103 except Exception: 104 return None 105 return self._parse_version_tuple3(version) 106 107 def validate(self) -> list[ValidationError]: 108 """ 109 Validate the brim file and return a list of validation errors. 110 111 Returns: 112 list[ValidationError]: A list of validation errors found in the brim file. 113 If the list is empty, the file is valid. 114 """ 115 json_descriptor = generate_json_descriptor(self._file) 116 validation_errors: list[ValidationError] = validate_json(json_descriptor) 117 return validation_errors 118 119 def __del__(self): 120 try: 121 if hasattr(self, '_file'): 122 self.close() 123 except Exception as e: 124 # don't throw an error if the file cannot be closed 125 warnings.warn(f"Cannot close the file: {e}") 126 127 def close(self) -> None: 128 """Close the underlying store and release file resources.""" 129 self._file.close() 130 131 def is_read_only(self) -> bool: 132 """ 133 Return whether the file was opened in read-only mode. 134 135 Returns: 136 bool: ``True`` when write operations are not allowed. 137 """ 138 return sync(self._file.is_read_only()) 139 140 def is_valid(self) -> bool: 141 """ 142 Check if the file is a valid brim file. 143 144 Returns: 145 bool: True if the file is valid, False otherwise. 146 """ 147 # TODO validate file against https://github.com/brillouin-imaging/Brillouin-standard-file/blob/main/docs/brim_file_specs.md 148 return True 149 150 @classmethod 151 def create(cls, filename: str, store_type: StoreType = StoreType.AUTO, *, 152 brim_version: str = '0.2') -> 'File': 153 """ 154 Create a new brim file with the specified filename. If the file exists already it will generate an error. 155 156 Args: 157 filename (str): Path to the brim file to be created. 158 store_type (StoreType): Type of the store to use, as defined in `brimfile.file_abstraction.StoreType`. Default is 'AUTO'. 159 brim_version (str): Version of the brim file format to use. Default is '0.2'. 160 161 Returns: 162 File: An instance of the File class representing the newly created brim file. 163 store_type (str): Type of the store to use, as defined in `brimfile.file_abstraction.StoreType`. Default is 'AUTO'. 164 """ 165 f = cls(filename, mode='w-', store_type=store_type) 166 167 # File version 168 f._file.version = cls._parse_version_tuple3(brim_version) 169 sync(f._file.create_attr('/', 'brim_version', brim_version)) 170 171 # Root Brillouin_data group 172 sync(f._file.create_group(brim_obj_names.Brillouin_base_path)) 173 174 return f 175 176 def create_data_group(self, PSD: np.ndarray, frequency: np.ndarray, px_size_um: tuple, *, index: int = None, 177 name: str = None, compression: FileAbstraction.Compression = FileAbstraction.Compression()) -> 'Data': 178 """ 179 Adds a new data entry to the file. 180 Parameters: 181 PSD (np.ndarray): The Power Spectral Density (PSD) data to be added. It must be 4D with dimensions z, y, x, spectrum 182 frequency (np.ndarray): The frequency data corresponding to the PSD. It must be broadcastable to the PSD shape (the most common case is frequency being 1D, in which case the frequency axis is assumed the same for all the spatial coordinates) 183 px_size_um (tuple): A tuple of 3 elements, in the order z,y,x, corresponding to the pixel size in um. Unused dimensions can be set to None. 184 index (int, optional): The index for the new data group. If None, the next available index is used. Defaults to None. 185 name (str, optional): The name for the new data group. Defaults to None. 186 compression (FileAbstraction.Compression, optional): The compression method to use for the data. Defaults to FileAbstraction.Compression.DEFAULT. 187 Returns: 188 Data: The newly created Data object. 189 Raises: 190 IndexError: If the specified index already exists in the dataset. 191 ValueError: If any of the data provided is not valid or consistent 192 """ 193 if PSD.ndim != 4: 194 raise ValueError( 195 "'PSD' must have 4 dimensions (z, y, x, spectrum)") 196 try: 197 np.broadcast_shapes(tuple(frequency.shape), tuple(PSD.shape)) 198 except ValueError as e: 199 raise ValueError(f"frequency (shape: {frequency.shape}) is not broadcastable to PSD (shape: {PSD.shape}): {e}") 200 if len(px_size_um) != 3: 201 raise ValueError("'px_size_um' must have 3 elements (z,y,x); unused dimensions can be set to None") 202 203 return self._create_data_group_raw(PSD, frequency, scanning = None, sparse = False, px_size_um=px_size_um, 204 index=index, name=name, compression=compression) 205 206 def create_data_group_sparse(self, PSD: np.ndarray, frequency: np.ndarray, scanning: dict, *, 207 index: int = None, name: str = None, compression: FileAbstraction.Compression = FileAbstraction.Compression()) -> 'Data': 208 """ 209 Adds a new [sparse data entry](https://github.com/brillouin-imaging/Brillouin-standard-file/blob/main/docs/brim_file_specs.md) to the file. 210 211 Sparse data allows storage of spectra in a flattened format (first dimension is the spectrum index), 212 with spatial mapping provided separately. This is efficient for data with irregular sampling or missing pixels. 213 214 Parameters: 215 PSD (np.ndarray): The Power Spectral Density (PSD) data to be added. First dimension is spectrum index, 216 last dimension contains the spectral data. Shape: (n_spectra, ..., n_freq_points). 217 frequency (np.ndarray): The frequency data corresponding to the PSD. Must be broadcastable to the PSD array. 218 scanning (dict): Dictionary defining the spatial mapping. Must include at least 'Spatial_map' or 'Cartesian_visualisation'. 219 See `brimfile.data.Data._add_data` docstring for detailed structure of the scanning dictionary. 220 index (int, optional): The index for the new data group. If None, the next available index is used. Defaults to None. 221 name (str, optional): The name for the new data group. Defaults to None. 222 compression (FileAbstraction.Compression, optional): The compression method to use for the data. Defaults to FileAbstraction.Compression.DEFAULT. 223 Returns: 224 Data: The newly created Data object. 225 Raises: 226 IndexError: If the specified index already exists in the dataset. 227 ValueError: If any of the data provided is not valid or consistent 228 """ 229 return self._create_data_group_raw(PSD, frequency, scanning=scanning, sparse=True, index=index, name=name, compression=compression) 230 231 def _create_data_group_raw(self, PSD: np.ndarray, frequency: np.ndarray, *, scanning: dict = None, px_size_um = None, sparse: bool = False, 232 index: int = None, name: str = None, compression: FileAbstraction.Compression = FileAbstraction.Compression()) -> 'Data': 233 """ 234 Adds a new data entry to the file. Check the documentation for `brimfile.data.Data._add_data` for more details on the parameters. 235 Parameters: 236 PSD (np.ndarray): The Power Spectral Density (PSD) data to be added. The last dimension contains the spectra. 237 frequency (np.ndarray): The frequency data corresponding to the PSD. Must be broadcastable to the PSD array. 238 scanning (dict, optional): Spatial mapping metadata. Required for sparse=True, optional for sparse=False. 239 For sparse data, include either `Spatial_map` or `Cartesian_visualisation`. 240 See `brimfile.data.Data._add_data` for the full accepted structure. 241 px_size_um (tuple, optional): A tuple of 3 elements (z, y, x) for pixel size in μm. For non-sparse data only. 242 sparse (bool): Whether the data is sparse. See https://github.com/brillouin-imaging/Brillouin-standard-file/blob/main/docs/brim_file_specs.md for details. Defaults to False. 243 index (int, optional): The index for the new data group. If None, the next available index is used. Defaults to None. 244 name (str, optional): The name for the new data group. Defaults to None. 245 compression (FileAbstraction.Compression, optional): The compression method to use for the data. Defaults to FileAbstraction.Compression.DEFAULT. 246 Returns: 247 Data: The newly created Data object. 248 Raises: 249 IndexError: If the specified index already exists in the dataset. 250 ValueError: If any of the data provided is not valid or consistent 251 """ 252 if index is not None: 253 if sync(Data._get_existing_group_name_async(self._file, index)) is not None: 254 raise IndexError( 255 f"Data {index} already exists in {self._file.filename}") 256 else: 257 data_groups = self.list_data_groups() 258 indices = [dg['index'] for dg in data_groups] 259 indices.sort() 260 index = indices[-1] + 1 if indices else 0 # Next available index 261 262 # create the data group 263 d = Data._create_new(self._file, index, sparse, name) 264 # add the pixel size as an attribute of the data group 265 if px_size_um is not None: 266 sync(self._file.create_attr(d._group, 'element_size', tuple(px_size_um))) 267 units.add_to_attribute(self._file, d._group, 'element_size', 'um') 268 elif not sparse: 269 warnings.warn("Pixel size is not provided for non-sparse data. It is recommended to provide it for proper spatial calibration and visualization.") 270 # add the data to the data group 271 d._add_data(PSD, frequency, scanning = scanning, compression=compression) 272 return d 273 274 def list_data_groups(self, retrieve_custom_name=False) -> list: 275 """ 276 List all data groups in the brim file. 277 278 Returns: 279 See documentation of brimfile.data.Data.list_data_groups 280 """ 281 return Data.list_data_groups(self._file, retrieve_custom_name) 282 283 def get_data(self, index: int = 0) -> 'Data': 284 """ 285 Retrieve a Data object for the specified index. 286 287 Args: 288 index (int): The index of the data group to retrieve. 289 290 Returns: 291 Data: The Data object corresponding to the specified index. 292 Raises: 293 IndexError: If the specified index does not exist in the dataset. 294 """ 295 return sync(Data.from_existing_async(self._file, index)) 296 297 @property 298 def filename(self) -> str: 299 """ 300 Get the filename of the brim file. 301 302 Returns: 303 str: The filename of the brim file. 304 """ 305 return self._file.filename 306 307 @property 308 def subtype(self) -> subtypes.SubType: 309 """ 310 Get the subtype of the brim file. 311 312 Returns: 313 subtypes.SubType: The subtype of the brim file. 314 """ 315 return subtypes.get_subtype(self._file)
23class File: 24 """ 25 High-level entry point for reading and writing brim files. 26 27 Use this class to open an existing brim file (`File(...)`) or create a new 28 one (`File.create(...)`), then access data groups, metadata, analysis 29 results, and validation helpers. 30 """ 31 32 if "pyodide" in sys.modules: 33 def __init__(self, file): 34 self._file = file 35 self._file.version = self._get_file_version_tuple() 36 if not self.is_valid(): 37 raise ValueError("The brim file is not valid!") 38 else: 39 def __init__(self, filename: str, mode: str = 'r', 40 store_type: StoreType = StoreType.AUTO, * , 41 validate: bool = False) -> None: 42 """ 43 Initialize the File object. 44 45 Args: 46 filename (str): Path to the brim file. 47 mode: {'r', 'r+', 'a', 'w', 'w-'} the mode for opening the file (default is 'r' for read-only). 48 See the definition of `mode` in `brimfile.file_abstraction._zarrFile.__init__()` for more details. 49 'r' means read only (must exist); 'r+' means read/write (must exist); 50 'a' means read/write (create if doesn't exist); 'w' means create (overwrite if exists); 'w-' means create (fail if exists). 51 store_type (StoreType): Type of the store to use, as defined in `brimfile.file_abstraction.StoreType`. Default is 'AUTO'. 52 validate (bool): Whether to validate the file upon initialization. Default is False. 53 """ 54 self._file = _AbstractFile( 55 filename, mode=mode, store_type=store_type) 56 self._file.version = self._get_file_version_tuple() 57 if not self.is_valid(): 58 raise ValueError("The brim file is not valid!") 59 if validate: 60 validation_errors: list[ValidationError] = self.validate() 61 for err in validation_errors: 62 if err.level == ValidationLevel.WARNING or err.level == ValidationLevel.ERROR: 63 warnings.warn(f"Validation warning at {err.path}: {err.message}") 64 elif err.level == ValidationLevel.CRITICAL: 65 raise ValueError(f"Validation error at {err.path}: {err.message}") 66 67 @staticmethod 68 def _parse_version_tuple3(version: str) -> Version: 69 """Normalize a version value into a 3-item tuple (major, minor, patch).""" 70 if isinstance(version, str): 71 parts = [part for part in version.split('.') if part != ''] 72 elif isinstance(version, (list, tuple)): 73 parts = list(version) 74 else: 75 raise ValueError(f"Invalid version format: {version}") 76 77 # convert the first 3 parts to int if they are digits (str), otherwise keep them as they are 78 normalized: list[int | str] = [] 79 for part in parts[:3]: 80 if isinstance(part, str) and part.isdigit(): 81 normalized.append(int(part)) 82 else: 83 try: 84 part = int(part) 85 normalized.append(part) 86 except (ValueError, TypeError): 87 raise ValueError(f"Invalid version part: {part}") 88 89 # add zeros to the end of the list until it has 3 elements 90 while len(normalized) < 3: 91 normalized.append(0) 92 93 return (normalized[0], normalized[1], normalized[2]) 94 95 def _get_file_version_tuple(self) -> Version: 96 """ 97 Read brim_version from file root attributes and return a 3-item tuple. 98 Returns: 99 Version: A tuple representing the version of the brim file in the format (major, minor, patch). 100 If the version can't be read from the file, returns None. 101 """ 102 try: 103 version = sync(self._file.get_attr('/', 'brim_version')) 104 except Exception: 105 return None 106 return self._parse_version_tuple3(version) 107 108 def validate(self) -> list[ValidationError]: 109 """ 110 Validate the brim file and return a list of validation errors. 111 112 Returns: 113 list[ValidationError]: A list of validation errors found in the brim file. 114 If the list is empty, the file is valid. 115 """ 116 json_descriptor = generate_json_descriptor(self._file) 117 validation_errors: list[ValidationError] = validate_json(json_descriptor) 118 return validation_errors 119 120 def __del__(self): 121 try: 122 if hasattr(self, '_file'): 123 self.close() 124 except Exception as e: 125 # don't throw an error if the file cannot be closed 126 warnings.warn(f"Cannot close the file: {e}") 127 128 def close(self) -> None: 129 """Close the underlying store and release file resources.""" 130 self._file.close() 131 132 def is_read_only(self) -> bool: 133 """ 134 Return whether the file was opened in read-only mode. 135 136 Returns: 137 bool: ``True`` when write operations are not allowed. 138 """ 139 return sync(self._file.is_read_only()) 140 141 def is_valid(self) -> bool: 142 """ 143 Check if the file is a valid brim file. 144 145 Returns: 146 bool: True if the file is valid, False otherwise. 147 """ 148 # TODO validate file against https://github.com/brillouin-imaging/Brillouin-standard-file/blob/main/docs/brim_file_specs.md 149 return True 150 151 @classmethod 152 def create(cls, filename: str, store_type: StoreType = StoreType.AUTO, *, 153 brim_version: str = '0.2') -> 'File': 154 """ 155 Create a new brim file with the specified filename. If the file exists already it will generate an error. 156 157 Args: 158 filename (str): Path to the brim file to be created. 159 store_type (StoreType): Type of the store to use, as defined in `brimfile.file_abstraction.StoreType`. Default is 'AUTO'. 160 brim_version (str): Version of the brim file format to use. Default is '0.2'. 161 162 Returns: 163 File: An instance of the File class representing the newly created brim file. 164 store_type (str): Type of the store to use, as defined in `brimfile.file_abstraction.StoreType`. Default is 'AUTO'. 165 """ 166 f = cls(filename, mode='w-', store_type=store_type) 167 168 # File version 169 f._file.version = cls._parse_version_tuple3(brim_version) 170 sync(f._file.create_attr('/', 'brim_version', brim_version)) 171 172 # Root Brillouin_data group 173 sync(f._file.create_group(brim_obj_names.Brillouin_base_path)) 174 175 return f 176 177 def create_data_group(self, PSD: np.ndarray, frequency: np.ndarray, px_size_um: tuple, *, index: int = None, 178 name: str = None, compression: FileAbstraction.Compression = FileAbstraction.Compression()) -> 'Data': 179 """ 180 Adds a new data entry to the file. 181 Parameters: 182 PSD (np.ndarray): The Power Spectral Density (PSD) data to be added. It must be 4D with dimensions z, y, x, spectrum 183 frequency (np.ndarray): The frequency data corresponding to the PSD. It must be broadcastable to the PSD shape (the most common case is frequency being 1D, in which case the frequency axis is assumed the same for all the spatial coordinates) 184 px_size_um (tuple): A tuple of 3 elements, in the order z,y,x, corresponding to the pixel size in um. Unused dimensions can be set to None. 185 index (int, optional): The index for the new data group. If None, the next available index is used. Defaults to None. 186 name (str, optional): The name for the new data group. Defaults to None. 187 compression (FileAbstraction.Compression, optional): The compression method to use for the data. Defaults to FileAbstraction.Compression.DEFAULT. 188 Returns: 189 Data: The newly created Data object. 190 Raises: 191 IndexError: If the specified index already exists in the dataset. 192 ValueError: If any of the data provided is not valid or consistent 193 """ 194 if PSD.ndim != 4: 195 raise ValueError( 196 "'PSD' must have 4 dimensions (z, y, x, spectrum)") 197 try: 198 np.broadcast_shapes(tuple(frequency.shape), tuple(PSD.shape)) 199 except ValueError as e: 200 raise ValueError(f"frequency (shape: {frequency.shape}) is not broadcastable to PSD (shape: {PSD.shape}): {e}") 201 if len(px_size_um) != 3: 202 raise ValueError("'px_size_um' must have 3 elements (z,y,x); unused dimensions can be set to None") 203 204 return self._create_data_group_raw(PSD, frequency, scanning = None, sparse = False, px_size_um=px_size_um, 205 index=index, name=name, compression=compression) 206 207 def create_data_group_sparse(self, PSD: np.ndarray, frequency: np.ndarray, scanning: dict, *, 208 index: int = None, name: str = None, compression: FileAbstraction.Compression = FileAbstraction.Compression()) -> 'Data': 209 """ 210 Adds a new [sparse data entry](https://github.com/brillouin-imaging/Brillouin-standard-file/blob/main/docs/brim_file_specs.md) to the file. 211 212 Sparse data allows storage of spectra in a flattened format (first dimension is the spectrum index), 213 with spatial mapping provided separately. This is efficient for data with irregular sampling or missing pixels. 214 215 Parameters: 216 PSD (np.ndarray): The Power Spectral Density (PSD) data to be added. First dimension is spectrum index, 217 last dimension contains the spectral data. Shape: (n_spectra, ..., n_freq_points). 218 frequency (np.ndarray): The frequency data corresponding to the PSD. Must be broadcastable to the PSD array. 219 scanning (dict): Dictionary defining the spatial mapping. Must include at least 'Spatial_map' or 'Cartesian_visualisation'. 220 See `brimfile.data.Data._add_data` docstring for detailed structure of the scanning dictionary. 221 index (int, optional): The index for the new data group. If None, the next available index is used. Defaults to None. 222 name (str, optional): The name for the new data group. Defaults to None. 223 compression (FileAbstraction.Compression, optional): The compression method to use for the data. Defaults to FileAbstraction.Compression.DEFAULT. 224 Returns: 225 Data: The newly created Data object. 226 Raises: 227 IndexError: If the specified index already exists in the dataset. 228 ValueError: If any of the data provided is not valid or consistent 229 """ 230 return self._create_data_group_raw(PSD, frequency, scanning=scanning, sparse=True, index=index, name=name, compression=compression) 231 232 def _create_data_group_raw(self, PSD: np.ndarray, frequency: np.ndarray, *, scanning: dict = None, px_size_um = None, sparse: bool = False, 233 index: int = None, name: str = None, compression: FileAbstraction.Compression = FileAbstraction.Compression()) -> 'Data': 234 """ 235 Adds a new data entry to the file. Check the documentation for `brimfile.data.Data._add_data` for more details on the parameters. 236 Parameters: 237 PSD (np.ndarray): The Power Spectral Density (PSD) data to be added. The last dimension contains the spectra. 238 frequency (np.ndarray): The frequency data corresponding to the PSD. Must be broadcastable to the PSD array. 239 scanning (dict, optional): Spatial mapping metadata. Required for sparse=True, optional for sparse=False. 240 For sparse data, include either `Spatial_map` or `Cartesian_visualisation`. 241 See `brimfile.data.Data._add_data` for the full accepted structure. 242 px_size_um (tuple, optional): A tuple of 3 elements (z, y, x) for pixel size in μm. For non-sparse data only. 243 sparse (bool): Whether the data is sparse. See https://github.com/brillouin-imaging/Brillouin-standard-file/blob/main/docs/brim_file_specs.md for details. Defaults to False. 244 index (int, optional): The index for the new data group. If None, the next available index is used. Defaults to None. 245 name (str, optional): The name for the new data group. Defaults to None. 246 compression (FileAbstraction.Compression, optional): The compression method to use for the data. Defaults to FileAbstraction.Compression.DEFAULT. 247 Returns: 248 Data: The newly created Data object. 249 Raises: 250 IndexError: If the specified index already exists in the dataset. 251 ValueError: If any of the data provided is not valid or consistent 252 """ 253 if index is not None: 254 if sync(Data._get_existing_group_name_async(self._file, index)) is not None: 255 raise IndexError( 256 f"Data {index} already exists in {self._file.filename}") 257 else: 258 data_groups = self.list_data_groups() 259 indices = [dg['index'] for dg in data_groups] 260 indices.sort() 261 index = indices[-1] + 1 if indices else 0 # Next available index 262 263 # create the data group 264 d = Data._create_new(self._file, index, sparse, name) 265 # add the pixel size as an attribute of the data group 266 if px_size_um is not None: 267 sync(self._file.create_attr(d._group, 'element_size', tuple(px_size_um))) 268 units.add_to_attribute(self._file, d._group, 'element_size', 'um') 269 elif not sparse: 270 warnings.warn("Pixel size is not provided for non-sparse data. It is recommended to provide it for proper spatial calibration and visualization.") 271 # add the data to the data group 272 d._add_data(PSD, frequency, scanning = scanning, compression=compression) 273 return d 274 275 def list_data_groups(self, retrieve_custom_name=False) -> list: 276 """ 277 List all data groups in the brim file. 278 279 Returns: 280 See documentation of brimfile.data.Data.list_data_groups 281 """ 282 return Data.list_data_groups(self._file, retrieve_custom_name) 283 284 def get_data(self, index: int = 0) -> 'Data': 285 """ 286 Retrieve a Data object for the specified index. 287 288 Args: 289 index (int): The index of the data group to retrieve. 290 291 Returns: 292 Data: The Data object corresponding to the specified index. 293 Raises: 294 IndexError: If the specified index does not exist in the dataset. 295 """ 296 return sync(Data.from_existing_async(self._file, index)) 297 298 @property 299 def filename(self) -> str: 300 """ 301 Get the filename of the brim file. 302 303 Returns: 304 str: The filename of the brim file. 305 """ 306 return self._file.filename 307 308 @property 309 def subtype(self) -> subtypes.SubType: 310 """ 311 Get the subtype of the brim file. 312 313 Returns: 314 subtypes.SubType: The subtype of the brim file. 315 """ 316 return subtypes.get_subtype(self._file)
High-level entry point for reading and writing brim files.
Use this class to open an existing brim file (File(...)) or create a new
one (File.create(...)), then access data groups, metadata, analysis
results, and validation helpers.
39 def __init__(self, filename: str, mode: str = 'r', 40 store_type: StoreType = StoreType.AUTO, * , 41 validate: bool = False) -> None: 42 """ 43 Initialize the File object. 44 45 Args: 46 filename (str): Path to the brim file. 47 mode: {'r', 'r+', 'a', 'w', 'w-'} the mode for opening the file (default is 'r' for read-only). 48 See the definition of `mode` in `brimfile.file_abstraction._zarrFile.__init__()` for more details. 49 'r' means read only (must exist); 'r+' means read/write (must exist); 50 'a' means read/write (create if doesn't exist); 'w' means create (overwrite if exists); 'w-' means create (fail if exists). 51 store_type (StoreType): Type of the store to use, as defined in `brimfile.file_abstraction.StoreType`. Default is 'AUTO'. 52 validate (bool): Whether to validate the file upon initialization. Default is False. 53 """ 54 self._file = _AbstractFile( 55 filename, mode=mode, store_type=store_type) 56 self._file.version = self._get_file_version_tuple() 57 if not self.is_valid(): 58 raise ValueError("The brim file is not valid!") 59 if validate: 60 validation_errors: list[ValidationError] = self.validate() 61 for err in validation_errors: 62 if err.level == ValidationLevel.WARNING or err.level == ValidationLevel.ERROR: 63 warnings.warn(f"Validation warning at {err.path}: {err.message}") 64 elif err.level == ValidationLevel.CRITICAL: 65 raise ValueError(f"Validation error at {err.path}: {err.message}")
Initialize the File object.
Arguments:
- filename (str): Path to the brim file.
- mode: {'r', 'r+', 'a', 'w', 'w-'} the mode for opening the file (default is 'r' for read-only).
See the definition of
modeinbrimfile.file_abstraction._zarrFile.__init__()for more details. 'r' means read only (must exist); 'r+' means read/write (must exist); 'a' means read/write (create if doesn't exist); 'w' means create (overwrite if exists); 'w-' means create (fail if exists). - store_type (StoreType): Type of the store to use, as defined in
brimfile.file_abstraction.StoreType. Default is 'AUTO'. - validate (bool): Whether to validate the file upon initialization. Default is False.
108 def validate(self) -> list[ValidationError]: 109 """ 110 Validate the brim file and return a list of validation errors. 111 112 Returns: 113 list[ValidationError]: A list of validation errors found in the brim file. 114 If the list is empty, the file is valid. 115 """ 116 json_descriptor = generate_json_descriptor(self._file) 117 validation_errors: list[ValidationError] = validate_json(json_descriptor) 118 return validation_errors
Validate the brim file and return a list of validation errors.
Returns:
list[ValidationError]: A list of validation errors found in the brim file. If the list is empty, the file is valid.
128 def close(self) -> None: 129 """Close the underlying store and release file resources.""" 130 self._file.close()
Close the underlying store and release file resources.
132 def is_read_only(self) -> bool: 133 """ 134 Return whether the file was opened in read-only mode. 135 136 Returns: 137 bool: ``True`` when write operations are not allowed. 138 """ 139 return sync(self._file.is_read_only())
Return whether the file was opened in read-only mode.
Returns:
bool:
Truewhen write operations are not allowed.
141 def is_valid(self) -> bool: 142 """ 143 Check if the file is a valid brim file. 144 145 Returns: 146 bool: True if the file is valid, False otherwise. 147 """ 148 # TODO validate file against https://github.com/brillouin-imaging/Brillouin-standard-file/blob/main/docs/brim_file_specs.md 149 return True
Check if the file is a valid brim file.
Returns:
bool: True if the file is valid, False otherwise.
151 @classmethod 152 def create(cls, filename: str, store_type: StoreType = StoreType.AUTO, *, 153 brim_version: str = '0.2') -> 'File': 154 """ 155 Create a new brim file with the specified filename. If the file exists already it will generate an error. 156 157 Args: 158 filename (str): Path to the brim file to be created. 159 store_type (StoreType): Type of the store to use, as defined in `brimfile.file_abstraction.StoreType`. Default is 'AUTO'. 160 brim_version (str): Version of the brim file format to use. Default is '0.2'. 161 162 Returns: 163 File: An instance of the File class representing the newly created brim file. 164 store_type (str): Type of the store to use, as defined in `brimfile.file_abstraction.StoreType`. Default is 'AUTO'. 165 """ 166 f = cls(filename, mode='w-', store_type=store_type) 167 168 # File version 169 f._file.version = cls._parse_version_tuple3(brim_version) 170 sync(f._file.create_attr('/', 'brim_version', brim_version)) 171 172 # Root Brillouin_data group 173 sync(f._file.create_group(brim_obj_names.Brillouin_base_path)) 174 175 return f
Create a new brim file with the specified filename. If the file exists already it will generate an error.
Arguments:
- filename (str): Path to the brim file to be created.
- store_type (StoreType): Type of the store to use, as defined in
brimfile.file_abstraction.StoreType. Default is 'AUTO'. - brim_version (str): Version of the brim file format to use. Default is '0.2'.
Returns:
File: An instance of the File class representing the newly created brim file. store_type (str): Type of the store to use, as defined in
brimfile.file_abstraction.StoreType. Default is 'AUTO'.
177 def create_data_group(self, PSD: np.ndarray, frequency: np.ndarray, px_size_um: tuple, *, index: int = None, 178 name: str = None, compression: FileAbstraction.Compression = FileAbstraction.Compression()) -> 'Data': 179 """ 180 Adds a new data entry to the file. 181 Parameters: 182 PSD (np.ndarray): The Power Spectral Density (PSD) data to be added. It must be 4D with dimensions z, y, x, spectrum 183 frequency (np.ndarray): The frequency data corresponding to the PSD. It must be broadcastable to the PSD shape (the most common case is frequency being 1D, in which case the frequency axis is assumed the same for all the spatial coordinates) 184 px_size_um (tuple): A tuple of 3 elements, in the order z,y,x, corresponding to the pixel size in um. Unused dimensions can be set to None. 185 index (int, optional): The index for the new data group. If None, the next available index is used. Defaults to None. 186 name (str, optional): The name for the new data group. Defaults to None. 187 compression (FileAbstraction.Compression, optional): The compression method to use for the data. Defaults to FileAbstraction.Compression.DEFAULT. 188 Returns: 189 Data: The newly created Data object. 190 Raises: 191 IndexError: If the specified index already exists in the dataset. 192 ValueError: If any of the data provided is not valid or consistent 193 """ 194 if PSD.ndim != 4: 195 raise ValueError( 196 "'PSD' must have 4 dimensions (z, y, x, spectrum)") 197 try: 198 np.broadcast_shapes(tuple(frequency.shape), tuple(PSD.shape)) 199 except ValueError as e: 200 raise ValueError(f"frequency (shape: {frequency.shape}) is not broadcastable to PSD (shape: {PSD.shape}): {e}") 201 if len(px_size_um) != 3: 202 raise ValueError("'px_size_um' must have 3 elements (z,y,x); unused dimensions can be set to None") 203 204 return self._create_data_group_raw(PSD, frequency, scanning = None, sparse = False, px_size_um=px_size_um, 205 index=index, name=name, compression=compression)
Adds a new data entry to the file.
Arguments:
- PSD (np.ndarray): The Power Spectral Density (PSD) data to be added. It must be 4D with dimensions z, y, x, spectrum
- frequency (np.ndarray): The frequency data corresponding to the PSD. It must be broadcastable to the PSD shape (the most common case is frequency being 1D, in which case the frequency axis is assumed the same for all the spatial coordinates)
- px_size_um (tuple): A tuple of 3 elements, in the order z,y,x, corresponding to the pixel size in um. Unused dimensions can be set to None.
- index (int, optional): The index for the new data group. If None, the next available index is used. Defaults to None.
- name (str, optional): The name for the new data group. Defaults to None.
- compression (FileAbstraction.Compression, optional): The compression method to use for the data. Defaults to FileAbstraction.Compression.DEFAULT.
Returns:
Data: The newly created Data object.
Raises:
- IndexError: If the specified index already exists in the dataset.
- ValueError: If any of the data provided is not valid or consistent
207 def create_data_group_sparse(self, PSD: np.ndarray, frequency: np.ndarray, scanning: dict, *, 208 index: int = None, name: str = None, compression: FileAbstraction.Compression = FileAbstraction.Compression()) -> 'Data': 209 """ 210 Adds a new [sparse data entry](https://github.com/brillouin-imaging/Brillouin-standard-file/blob/main/docs/brim_file_specs.md) to the file. 211 212 Sparse data allows storage of spectra in a flattened format (first dimension is the spectrum index), 213 with spatial mapping provided separately. This is efficient for data with irregular sampling or missing pixels. 214 215 Parameters: 216 PSD (np.ndarray): The Power Spectral Density (PSD) data to be added. First dimension is spectrum index, 217 last dimension contains the spectral data. Shape: (n_spectra, ..., n_freq_points). 218 frequency (np.ndarray): The frequency data corresponding to the PSD. Must be broadcastable to the PSD array. 219 scanning (dict): Dictionary defining the spatial mapping. Must include at least 'Spatial_map' or 'Cartesian_visualisation'. 220 See `brimfile.data.Data._add_data` docstring for detailed structure of the scanning dictionary. 221 index (int, optional): The index for the new data group. If None, the next available index is used. Defaults to None. 222 name (str, optional): The name for the new data group. Defaults to None. 223 compression (FileAbstraction.Compression, optional): The compression method to use for the data. Defaults to FileAbstraction.Compression.DEFAULT. 224 Returns: 225 Data: The newly created Data object. 226 Raises: 227 IndexError: If the specified index already exists in the dataset. 228 ValueError: If any of the data provided is not valid or consistent 229 """ 230 return self._create_data_group_raw(PSD, frequency, scanning=scanning, sparse=True, index=index, name=name, compression=compression)
Adds a new sparse data entry to the file.
Sparse data allows storage of spectra in a flattened format (first dimension is the spectrum index), with spatial mapping provided separately. This is efficient for data with irregular sampling or missing pixels.
Arguments:
- PSD (np.ndarray): The Power Spectral Density (PSD) data to be added. First dimension is spectrum index, last dimension contains the spectral data. Shape: (n_spectra, ..., n_freq_points).
- frequency (np.ndarray): The frequency data corresponding to the PSD. Must be broadcastable to the PSD array.
- scanning (dict): Dictionary defining the spatial mapping. Must include at least 'Spatial_map' or 'Cartesian_visualisation'.
See
brimfile.data.Data._add_datadocstring for detailed structure of the scanning dictionary. - index (int, optional): The index for the new data group. If None, the next available index is used. Defaults to None.
- name (str, optional): The name for the new data group. Defaults to None.
- compression (FileAbstraction.Compression, optional): The compression method to use for the data. Defaults to FileAbstraction.Compression.DEFAULT.
Returns:
Data: The newly created Data object.
Raises:
- IndexError: If the specified index already exists in the dataset.
- ValueError: If any of the data provided is not valid or consistent
275 def list_data_groups(self, retrieve_custom_name=False) -> list: 276 """ 277 List all data groups in the brim file. 278 279 Returns: 280 See documentation of brimfile.data.Data.list_data_groups 281 """ 282 return Data.list_data_groups(self._file, retrieve_custom_name)
List all data groups in the brim file.
Returns:
See documentation of brimfile.data.Data.list_data_groups
284 def get_data(self, index: int = 0) -> 'Data': 285 """ 286 Retrieve a Data object for the specified index. 287 288 Args: 289 index (int): The index of the data group to retrieve. 290 291 Returns: 292 Data: The Data object corresponding to the specified index. 293 Raises: 294 IndexError: If the specified index does not exist in the dataset. 295 """ 296 return sync(Data.from_existing_async(self._file, index))
Retrieve a Data object for the specified index.
Arguments:
- index (int): The index of the data group to retrieve.
Returns:
Data: The Data object corresponding to the specified index.
Raises:
- IndexError: If the specified index does not exist in the dataset.
298 @property 299 def filename(self) -> str: 300 """ 301 Get the filename of the brim file. 302 303 Returns: 304 str: The filename of the brim file. 305 """ 306 return self._file.filename
Get the filename of the brim file.
Returns:
str: The filename of the brim file.
308 @property 309 def subtype(self) -> subtypes.SubType: 310 """ 311 Get the subtype of the brim file. 312 313 Returns: 314 subtypes.SubType: The subtype of the brim file. 315 """ 316 return subtypes.get_subtype(self._file)
Get the subtype of the brim file.
Returns:
subtypes.SubType: The subtype of the brim file.