Source code for sarcasm.meta_data_handler
# -*- coding: utf-8 -*-
# Copyright (c) 2025 University Medical Center Göttingen, Germany.
# All rights reserved.
#
# Patent Pending: DE 10 2024 112 939.5
# SPDX-License-Identifier: LicenseRef-Proprietary-See-LICENSE
#
# This software is licensed under a custom license. See the LICENSE file
# in the root directory for full details.
#
# **Commercial use is prohibited without a separate license.**
# Contact MBM ScienceBridge GmbH (https://sciencebridge.de/en/) for licensing.
import datetime
import json
from dataclasses import dataclass, field, asdict
from pathlib import Path
from typing import Optional, List, Tuple, Any, Dict
import numpy as np
from sarcasm._version import __version__
[docs]
@dataclass
class ImageMetadata:
"""Metadata of tif file."""
# Core image properties (set during read_imgs)
axes: str | None = None
pixelsize: Optional[float] = None
frametime: Optional[float] = None
shape_orig: Tuple[int, ...] = field(default_factory=tuple)
shape: Tuple[int, ...] | None = None
n_stack: int | None = None
size: Tuple[int, int] | None = None
timestamps: Optional[List[float]] = None
# File properties (set during initialization)
file_name: str = ""
file_path: str = ""
# Computed properties (set in __post_init__) and SarcAsM metadata
time: Optional[np.ndarray] = field(init=False, repr=False)
sarcasm_version: str = field(default_factory=lambda: __version__)
timestamp_analysis: str = field(default_factory=lambda: datetime.datetime.now().isoformat())
# User-specified channel with sarcomere signal
channel: Optional[int] = None
# User-supplied metadata (dynamic)
user_info: Dict[str, Any] = field(default_factory=dict)
[docs]
def __post_init__(self):
"""Compute derived fields after initialization."""
if not hasattr(self, 'sarcasm_version') or self.sarcasm_version is None:
self.sarcasm_version = __version__
if not hasattr(self, 'timestamp_analysis') or self.timestamp_analysis is None:
self.timestamp_analysis = datetime.datetime.now().isoformat()
# Create time array if we have both frametime and a stack
if self.frametime is not None and self.n_stack is not None and self.n_stack > 1:
self.time = np.arange(0, self.n_stack * self.frametime, self.frametime)
else:
self.time = None
[docs]
def add_user_info(self, **kwargs):
"""Add arbitrary user metadata after initialization."""
self.user_info.update(kwargs)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Convert to JSON-serializable dictionary."""
result = asdict(self)
# Flatten user_info into the main dict
user_info = result.pop('user_info', {})
result.update(user_info)
return result
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'ImageMetadata':
"""Create from dictionary (for loading from JSON)."""
# Get only fields that can be passed to __init__ (init=True)
dataclass_fields = cls.__dataclass_fields__
init_fields = {name for name, field_obj in dataclass_fields.items() if field_obj.init}
# Separate init fields from user info, excluding init=False fields
known_data = {k: v for k, v in data.items() if k in init_fields}
user_data = {k: v for k, v in data.items() if k not in init_fields}
# Create instance
instance = cls(**known_data)
# Add remaining data as user_info
remaining_user_data = {k: v for k, v in user_data.items()
if k not in dataclass_fields}
instance.add_user_info(**remaining_user_data)
return instance
[docs]
@classmethod
def save_to_file(cls, instance, file_path: Path):
"""Save metadata to JSON file."""
# Convert numpy array to list for JSON serialization
data = instance.to_dict()
if 'time' in data and isinstance(data['time'], np.ndarray):
data['time'] = data['time'].tolist()
with open(file_path, 'w') as f:
json.dump(data, f, indent=2)
[docs]
@classmethod
def load_from_file(cls, file_path: Path) -> 'ImageMetadata':
"""Load metadata from JSON file."""
with open(file_path, 'r') as f:
data = json.load(f)
# Convert time list back to numpy array
if 'time' in data and isinstance(data['time'], list):
data['time'] = np.array(data['time'])
return cls.from_dict(data)