Skip to content

Signatures

siapy.entities.signatures

Signals dataclass

Signals(_data: DataFrame)

df property

df: DataFrame

from_iterable classmethod

from_iterable(iterable: Iterable) -> Signals
Source code in siapy/entities/signatures.py
43
44
45
46
@classmethod
def from_iterable(cls, iterable: Iterable) -> "Signals":
    df = pd.DataFrame(iterable)
    return cls(df)

load_from_parquet classmethod

load_from_parquet(filepath: str | Path) -> Signals
Source code in siapy/entities/signatures.py
48
49
50
51
@classmethod
def load_from_parquet(cls, filepath: str | Path) -> "Signals":
    df = pd.read_parquet(filepath)
    return cls(df)

to_numpy

to_numpy() -> NDArray[floating[Any]]
Source code in siapy/entities/signatures.py
57
58
def to_numpy(self) -> NDArray[np.floating[Any]]:
    return self.df.to_numpy()

average_signal

average_signal(
    axis: int | tuple[int, ...] | Sequence[int] | None = 0,
) -> NDArray[floating[Any]]
Source code in siapy/entities/signatures.py
60
61
def average_signal(self, axis: int | tuple[int, ...] | Sequence[int] | None = 0) -> NDArray[np.floating[Any]]:
    return np.nanmean(self.to_numpy(), axis=axis)

save_to_parquet

save_to_parquet(filepath: str | Path) -> None
Source code in siapy/entities/signatures.py
63
64
def save_to_parquet(self, filepath: str | Path) -> None:
    self.df.to_parquet(filepath, index=True)

Signatures dataclass

Signatures(pixels: Pixels, signals: Signals)

pixels instance-attribute

pixels: Pixels

signals instance-attribute

signals: Signals

from_dict classmethod

from_dict(data: dict[str, Any]) -> Signatures
Source code in siapy/entities/signatures.py
126
127
128
129
130
131
132
133
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "Signatures":
    pixels_df = pd.DataFrame(data["pixels"])
    signals_df = pd.DataFrame(data["signals"])
    pixels = Pixels(pixels_df)
    signals = Signals(signals_df)
    validate_inputs(pixels, signals)
    return cls(pixels, signals)

from_array_and_pixels classmethod

from_array_and_pixels(
    array: NDArray[floating[Any]],
    pixels: Pixels | DataFrame | Iterable[CoordinateInput],
) -> Signatures
Source code in siapy/entities/signatures.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
@classmethod
def from_array_and_pixels(
    cls, array: NDArray[np.floating[Any]], pixels: Pixels | pd.DataFrame | Iterable[CoordinateInput]
) -> "Signatures":
    pixels = validate_pixel_input(pixels)
    if pd.api.types.is_float_dtype(pixels.df.dtypes.x) or pd.api.types.is_float_dtype(pixels.df.dtypes.y):
        logger.warning("Pixel DataFrame contains float values. Converting to integers.")
        pixels = pixels.as_type(int)

    x = pixels.x()
    y = pixels.y()

    if array.ndim != 3:
        raise InvalidInputError(f"Expected a 3-dimensional array, but got {array.ndim}-dimensional array.")
    if np.max(x) >= array.shape[1] or np.max(y) >= array.shape[0]:
        raise InvalidInputError(
            f"Pixel coordinates exceed image dimensions: "
            f"image shape is {array.shape}, but max u={np.max(x)}, max v={np.max(y)}."
        )

    signals_list = array[y, x, :]
    signals = Signals(pd.DataFrame(signals_list))
    validate_inputs(pixels, signals)
    return cls(pixels, signals)

from_signals_and_pixels classmethod

from_signals_and_pixels(
    signals: Signals | DataFrame | Iterable[Any],
    pixels: Pixels | DataFrame | Iterable[CoordinateInput],
) -> Signatures
Source code in siapy/entities/signatures.py
160
161
162
163
164
165
166
167
168
169
@classmethod
def from_signals_and_pixels(
    cls,
    signals: Signals | pd.DataFrame | Iterable[Any],
    pixels: Pixels | pd.DataFrame | Iterable[CoordinateInput],
) -> "Signatures":
    pixels = validate_pixel_input(pixels)
    signals = validate_signal_input(signals)
    validate_inputs(pixels, signals)
    return cls(pixels, signals)

from_dataframe classmethod

from_dataframe(dataframe: DataFrame) -> Signatures
Source code in siapy/entities/signatures.py
171
172
173
174
175
176
177
178
179
180
181
@classmethod
def from_dataframe(cls, dataframe: pd.DataFrame) -> "Signatures":
    if not all(coord in dataframe.columns for coord in [Pixels.coords.X, Pixels.coords.Y]):
        raise InvalidInputError(
            dataframe.columns.tolist(),
            f"DataFrame must include columns for both '{Pixels.coords.X}' and '{Pixels.coords.Y}' coordinates.",
        )
    pixels = Pixels(dataframe[[Pixels.coords.X, Pixels.coords.Y]])
    signals = Signals(dataframe.drop(columns=[Pixels.coords.X, Pixels.coords.Y]))
    validate_inputs(pixels, signals)
    return cls(pixels, signals)

from_dataframe_multiindex classmethod

from_dataframe_multiindex(df: DataFrame) -> Signatures
Source code in siapy/entities/signatures.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
@classmethod
def from_dataframe_multiindex(cls, df: pd.DataFrame) -> "Signatures":
    if not isinstance(df.columns, pd.MultiIndex):
        raise InvalidInputError(
            type(df.columns),
            "DataFrame must have MultiIndex columns",
        )

    pixel_data = df.xs("pixel", axis=1, level="category")
    signal_data = df.xs("signal", axis=1, level="category")

    assert isinstance(pixel_data, pd.DataFrame)
    assert isinstance(signal_data, pd.DataFrame)

    pixels = Pixels(pixel_data)
    signals = Signals(signal_data)
    validate_inputs(pixels, signals)
    return cls(pixels, signals)

open_parquet classmethod

open_parquet(filepath: str | Path) -> Signatures
Source code in siapy/entities/signatures.py
202
203
204
205
@classmethod
def open_parquet(cls, filepath: str | Path) -> "Signatures":
    df = pd.read_parquet(filepath)
    return cls.from_dataframe(df)

to_dataframe

to_dataframe() -> DataFrame
Source code in siapy/entities/signatures.py
207
208
def to_dataframe(self) -> pd.DataFrame:
    return pd.concat([self.pixels.df, self.signals.df], axis=1)

to_dataframe_multiindex

to_dataframe_multiindex() -> DataFrame
Source code in siapy/entities/signatures.py
210
211
212
213
214
215
216
217
218
219
220
221
222
def to_dataframe_multiindex(self) -> pd.DataFrame:
    pixel_columns = pd.MultiIndex.from_tuples(
        [("pixel", "x"), ("pixel", "y")],
        names=["category", "coordinate"],
    )
    signal_columns = pd.MultiIndex.from_tuples(
        [("signal", col) for col in self.signals.df.columns],
        names=["category", "channel"],
    )

    pixel_df = pd.DataFrame(self.pixels.df.values, columns=pixel_columns)
    signal_df = pd.DataFrame(self.signals.df.values, columns=signal_columns)
    return pd.concat([pixel_df, signal_df], axis=1)

to_numpy

to_numpy() -> tuple[
    NDArray[floating[Any]], NDArray[floating[Any]]
]
Source code in siapy/entities/signatures.py
224
225
def to_numpy(self) -> tuple[NDArray[np.floating[Any]], NDArray[np.floating[Any]]]:
    return self.pixels.to_numpy(), self.signals.to_numpy()

to_dict

to_dict() -> dict[str, Any]
Source code in siapy/entities/signatures.py
227
228
229
230
231
def to_dict(self) -> dict[str, Any]:
    return {
        "pixels": self.pixels.df.to_dict(),
        "signals": self.signals.df.to_dict(),
    }

reset_index

reset_index() -> Signatures
Source code in siapy/entities/signatures.py
233
234
235
236
def reset_index(self) -> "Signatures":
    return Signatures(
        Pixels(self.pixels.df.reset_index(drop=True)), Signals(self.signals.df.reset_index(drop=True))
    )

save_to_parquet

save_to_parquet(filepath: str | Path) -> None
Source code in siapy/entities/signatures.py
238
239
def save_to_parquet(self, filepath: str | Path) -> None:
    self.to_dataframe().to_parquet(filepath, index=True)

copy

copy() -> Signatures
Source code in siapy/entities/signatures.py
241
242
243
244
def copy(self) -> "Signatures":
    pixels_df = self.pixels.df.copy()
    signals_df = self.signals.df.copy()
    return Signatures(Pixels(pixels_df), Signals(signals_df))

validate_signal_input

validate_signal_input(
    input_data: Signals
    | DataFrame
    | Iterable[Sequence[float]],
) -> Signals

Validates and converts various input types to Signals object.

Source code in siapy/entities/signatures.py
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def validate_signal_input(input_data: Signals | pd.DataFrame | Iterable[Sequence[float]]) -> Signals:
    """Validates and converts various input types to Signals object."""
    try:
        if isinstance(input_data, Signals):
            return input_data

        if isinstance(input_data, pd.DataFrame):
            return Signals(input_data)

        if isinstance(input_data, np.ndarray):
            if input_data.ndim not in (1, 2):
                raise InvalidInputError(
                    input_value=input_data.shape,
                    message=f"NumPy array must be 1D or 2D, got shape {input_data.shape}",
                )
            if input_data.ndim == 1:
                input_data = input_data.reshape(1, -1)  # Reshape 1D array to 2D if needed
            return Signals(pd.DataFrame(input_data))

        if isinstance(input_data, Iterable):
            return Signals.from_iterable(input_data)

        raise InvalidTypeError(
            input_value=input_data,
            allowed_types=(Signals, pd.DataFrame, np.ndarray, Iterable),
            message=f"Unsupported input type: {type(input_data).__name__}",
        )

    except Exception as e:
        if isinstance(e, (InvalidTypeError, InvalidInputError)):
            raise

        raise InvalidInputError(
            input_value=input_data,
            message=f"Failed to convert input to Signals: {str(e)}"
            f"\nExpected a Signals instance or an iterable (e.g. list, np.array, pd.DataFrame)."
            f"\nThe input must contain spectral signal values.",
        )

validate_inputs

validate_inputs(pixels: Pixels, signals: Signals) -> None
Source code in siapy/entities/signatures.py
247
248
249
250
251
def validate_inputs(pixels: Pixels, signals: Signals) -> None:
    if len(pixels) != len(signals):
        raise InvalidInputError(
            f"Pixels and signals must have the same number of rows: {len(pixels)} pixels, {len(signals)} signals."
        )