Source code for pykrait.io.files
import os
import numpy as np
import pandas as pd
from pathlib import Path
from typing import List, get_origin, get_args, Union
from importlib.metadata import version
[docs]
def get_files_from_folder(
folder: str,
extension: str = ".czi",
) -> list[str]:
"""returns all files from a folder with specified extensions.
:param folder: path to the folder
:type folder: str
:param extensions: str of file extension (default is ".czi")
:type extensions: str
:return: list of file paths with the specified extensions
:rtype: list[str]
"""
if not os.path.isdir(folder):
raise NotADirectoryError(f"Provided path is not a directory: {folder}")
files = [
os.path.join(folder, f)
for f in os.listdir(folder)
if os.path.isfile(os.path.join(folder, f))
and os.path.splitext(f)[-1].lower() == extension
]
files = [f for f in files if not os.path.basename(f).startswith(".")]
return files
[docs]
def get_pykrait_version() -> str:
"""returns the current version of pykrait."""
try:
__version__ = version("pykrait")
return __version__
except Exception as e:
print(f"Could not retrieve pykrait version: {e}")
return None # Default version if not found
[docs]
def save_Txnrois(array: np.ndarray, frame_interval: float, filepath: str) -> None:
"""
Saves a T x n_roi array to a CSV file with a header to denote the ROIs and a time index
:param array: T x n_roi array to save
:type array: np.ndarray
:param filename: name of the output file
:type filename: str
:param frame_interval: frame interval in seconds
:type frame_interval: float
"""
if not isinstance(array, np.ndarray):
raise TypeError("Input must be a numpy ndarray")
if array.ndim != 2:
raise ValueError("Input array must be 2D (T x n_roi)")
n_frames, n_rois = array.shape
timepoints = np.round((np.arange(n_frames) * frame_interval), 2)
columns = [f"ROI_{i}" for i in range(n_rois)]
df = pd.DataFrame(array, columns=columns, index=timepoints)
df.index.name = "Time (s)"
os.makedirs(os.path.dirname(filepath), exist_ok=True)
df.to_csv(filepath, index=True, compression="zstd")
[docs]
def read_Txnrois(filepath: str, n_frames: int = None, n_rois: int = None) -> np.ndarray:
"""
loads a T x n_roi array to a CSV file with a header to denote the ROIs and a time index
:param filepath: path to the CSV file
:type filepath: str
:param n_frames: number of frames in the timelapse
:type n_frames: int
:return: numpy array of shape (n_frames, n_rois)
:rtype: np.ndarray
"""
df = pd.read_csv(filepath, index_col=0)
if n_frames is not None and df.shape[0] != n_frames:
raise ValueError(
f"Expected {n_frames} frames, but got {df.shape[0]} in {filepath}"
)
if n_rois is not None and df.shape[1] != n_rois:
raise ValueError(f"Expected {n_rois} ROIs, but got {df.shape[1]} in {filepath}")
return df.to_numpy()
[docs]
def save_NroisxF(
array: np.ndarray,
filepath: str,
header: List[str] = None,
) -> None:
"""
Saves a Nrois x F array to a CSV file with an index to denote the ROIs and a header for the features
:param array: T x n_roi array to save
:type array: np.ndarray
:param filename: name of the output file
:type filename: str
:param frame_interval: frame interval in seconds
:type frame_interval: float
"""
if not isinstance(array, np.ndarray):
raise TypeError("Input must be a numpy ndarray")
if len(header) != array.shape[1]:
raise ValueError("Header length must match the number of columns in the array")
df = pd.DataFrame(array, columns=header)
df.index.name = "ROIs"
os.makedirs(os.path.dirname(filepath), exist_ok=True)
df.to_csv(filepath, index=True, compression="zstd")
[docs]
def concat_analysis_files(root_folder: str, filetype:str="output") -> None:
"""
Concatenates all *_analysis_output.csv files in the given root folder and its subdirectories into a single CSV file named experiment_overview.csv.
:param root_folder: folder where *_analysis_output.csv files are located
:type root_folder: str
:raises FileNotFoundError: if no *_analysis_output.csv files are found
"""
root = Path(root_folder)
# Search recursively for *_analysis_output.csv
if filetype == "output":
csv_files = [
f
for f in root.rglob("*_analysis_output.csv")
if not f.name.startswith(".") # skip hidden files
]
if not csv_files:
raise FileNotFoundError(
"No *_analysis_output.csv files found in the directory or subdirectories."
)
output_path = root / "analysis_output_overview.csv"
elif filetype == "parameters":
csv_files = [
f
for f in root.rglob("*_analysis_parameters.csv")
if not f.name.startswith(".") # skip hidden files
]
if not csv_files:
raise FileNotFoundError(
"No *_analysis_parameters.csv files found in the directory or subdirectories."
)
output_path = root / "analysis_params_overview.csv"
else:
raise ValueError(f"Unexpected filetype: {filetype}, use 'parameters' or 'output'")
dfs = []
for f in csv_files:
df = pd.read_csv(f)
# Record the relative path for context
df["source_file"] = str(f.relative_to(root))
dfs.append(df)
combined = pd.concat(dfs, ignore_index=True)
combined.to_csv(output_path, index=False)
print(f"Saved combined CSV to {output_path}")
def _auto_cast(value: str, target_type):
"""Safely cast a CSV string to the given target type."""
if value is None:
return None
value = value.strip()
if value == "":
return None
# Handle Optional[T]
if get_origin(target_type) is Union:
args = [t for t in get_args(target_type) if t is not type(None)]
if args:
target_type = args[0]
try:
if target_type is bool:
return value.lower() in ("1", "true", "yes", "y", "t")
elif target_type is int:
return int(value)
elif target_type is float:
return float(value)
elif target_type is str:
return value
else:
# fallback: return as string
return value
except Exception:
raise ValueError(f"Cannot convert '{value}' to {target_type}")