Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #140

Merged
merged 7 commits into from
Aug 28, 2024
20 changes: 17 additions & 3 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,26 @@ pip install siapy

### Alternative Installation Methods

__Using Conda__
__Python package and dependency managers__

You can also install siapy using conda:
You can also install siapy using other popular Python package and dependency managers:

- PDM:

```bash
pdm add siapy
```

- Poetry:

```bash
poetry add siapy
```

- uv:

```bash
conda install -c conda-forge siapy
uv add siapy
```

__Manually__
Expand Down
6 changes: 3 additions & 3 deletions siapy/entities/imagesets.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Iterator
from typing import Any, Iterator, Sequence

import numpy as np
from rich.progress import track
Expand Down Expand Up @@ -31,8 +31,8 @@ def __getitem__(self, index) -> SpectralImage:
def from_paths(
cls,
*,
header_paths: list[str | Path],
image_paths: list[str | Path] | None = None,
header_paths: Sequence[str | Path],
image_paths: Sequence[str | Path] | None = None,
):
if image_paths is not None and len(header_paths) != len(image_paths):
raise ValueError("The length of hdr_paths and img_path must be equal.")
Expand Down
11 changes: 10 additions & 1 deletion siapy/entities/pixels.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dataclasses import dataclass
from pathlib import Path
from typing import Annotated, ClassVar, Iterable, NamedTuple

import numpy as np
Expand Down Expand Up @@ -28,10 +29,15 @@ def from_iterable(
Annotated[int, "v coordinate on the image"],
]
],
):
) -> "Pixels":
df = pd.DataFrame(iterable, columns=[Pixels.coords.U, Pixels.coords.V])
return cls(df)

@classmethod
def load_from_parquet(cls, filepath: str | Path) -> "Pixels":
df = pd.read_parquet(filepath)
return cls(df)

@property
def df(self) -> pd.DataFrame:
return self._data
Expand All @@ -49,3 +55,6 @@ def v(self) -> pd.Series:

def to_numpy(self) -> np.ndarray:
return self.df.to_numpy()

def save_to_parquet(self, filepath: str | Path) -> None:
self.df.to_parquet(filepath, index=True)
17 changes: 17 additions & 0 deletions siapy/entities/signatures.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dataclasses import dataclass
from pathlib import Path
from typing import Any

import numpy as np
Expand All @@ -13,6 +14,11 @@
class Signals:
_data: pd.DataFrame

@classmethod
def load_from_parquet(cls, filepath: str | Path) -> "Signals":
df = pd.read_parquet(filepath)
return cls(df)

@property
def df(self) -> pd.DataFrame:
return self._data
Expand All @@ -23,6 +29,9 @@ def to_numpy(self) -> np.ndarray:
def mean(self) -> np.ndarray:
return np.nanmean(self.to_numpy(), axis=0)

def save_to_parquet(self, filepath: str | Path) -> None:
self.df.to_parquet(filepath, index=True)


@dataclass
class SignaturesFilter:
Expand Down Expand Up @@ -84,6 +93,11 @@ def from_dataframe(cls, dataframe: pd.DataFrame) -> "Signatures":
signals = Signals(dataframe.drop(columns=[Pixels.coords.U, Pixels.coords.V]))
return cls._create(pixels, signals)

@classmethod
def load_from_parquet(cls, filepath: str | Path) -> "Signatures":
df = pd.read_parquet(filepath)
return cls.from_dataframe(df)

@property
def pixels(self) -> Pixels:
return self._pixels
Expand All @@ -100,3 +114,6 @@ def to_numpy(self) -> np.ndarray:

def filter(self) -> SignaturesFilter:
return SignaturesFilter(self.pixels, self.signals)

def save_to_parquet(self, filepath: str | Path) -> None:
self.to_dataframe().to_parquet(filepath, index=True)
12 changes: 10 additions & 2 deletions siapy/utils/plots.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import sys
from typing import Any

import matplotlib.pyplot as plt
import numpy as np
Expand Down Expand Up @@ -60,7 +61,9 @@ def onexit(event):
return Pixels.from_iterable(coordinates)


def pixels_select_lasso(image: ImageType) -> list[Pixels]:
def pixels_select_lasso(
image: ImageType, selector_props: dict[str, Any] | None = None
) -> list[Pixels]:
image_display = validate_image_to_numpy_3channels(image)

x, y = np.meshgrid(
Expand Down Expand Up @@ -100,7 +103,12 @@ def accept(event):
enter_clicked = 1
plt.close()

lasso = LassoSelector(ax, onselect) # noqa: F841
props = (
selector_props
if selector_props is not None
else {"color": "red", "linewidth": 2, "linestyle": "-"}
)
lasso = LassoSelector(ax, onselect, props=props) # noqa: F841
fig.canvas.mpl_connect("button_release_event", onrelease)
fig.canvas.mpl_connect("close_event", onexit)
fig.canvas.mpl_connect("key_press_event", accept)
Expand Down
15 changes: 15 additions & 0 deletions tests/entities/test_entities_pixels.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import os
from pathlib import Path
from tempfile import TemporaryDirectory

import numpy as np
import pandas as pd

Expand Down Expand Up @@ -51,3 +55,14 @@ def test_to_numpy():
pixels = Pixels(df)
expected_array = df.to_numpy()
assert np.array_equal(pixels.to_numpy(), expected_array)


def test_save_and_load_to_parquet():
pixels = Pixels.from_iterable(iterable)
with TemporaryDirectory() as tmpdir:
parquet_file = Path(tmpdir, "test_pixels.parquet")
pixels.save_to_parquet(parquet_file)
assert os.path.exists(parquet_file)
loaded_pixels = Pixels.load_from_parquet(parquet_file)
assert isinstance(loaded_pixels, Pixels)
assert loaded_pixels.df.equals(pixels.df)
28 changes: 28 additions & 0 deletions tests/entities/test_entities_signatures.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import os
from pathlib import Path
from tempfile import TemporaryDirectory

import numpy as np
import pandas as pd
import pytest
Expand All @@ -22,6 +26,18 @@ def test_signals_mean():
assert signals_mean.shape == (4,)


def test_signals_save_and_load_to_parquet():
signals_df = pd.DataFrame([[1, 2, 4, 6], [3, 4, 3, 5]])
signals = Signals(signals_df)
with TemporaryDirectory() as tmpdir:
parquet_file = Path(tmpdir, "test_signals.parquet")
signals.save_to_parquet(parquet_file)
assert os.path.exists(parquet_file)
loaded_signals = Signals.load_from_parquet(parquet_file)
assert isinstance(loaded_signals, Signals)
assert loaded_signals.df.equals(signals.df)


def test_signatures_filter_create():
pixels_df = pd.DataFrame({"u": [0, 1], "v": [0, 1]})
signals_df = pd.DataFrame([[1, 2], [3, 4]])
Expand Down Expand Up @@ -140,3 +156,15 @@ def test_signatures_from_dataframe():

with pytest.raises(ValueError):
Signatures.from_dataframe(df_missing_v)


def test_signatures_save_and_load_to_parquet():
df = pd.DataFrame({"u": [0, 1], "v": [0, 1], "0": [1, 2], "1": [3, 4]})
signatures = Signatures.from_dataframe(df)
with TemporaryDirectory() as tmpdir:
parquet_file = Path(tmpdir, "test_signatures.parquet")
signatures.save_to_parquet(parquet_file)
assert os.path.exists(parquet_file)
loaded_signatures = Signatures.load_from_parquet(parquet_file)
assert isinstance(loaded_signatures, Signatures)
assert loaded_signatures.to_dataframe().equals(signatures.to_dataframe())
2 changes: 1 addition & 1 deletion tests/utils/test_utils_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_pixels_select_click_manual(spectral_images):
@pytest.mark.manual
def test_pixels_select_lasso_manual(spectral_images):
image_vnir = spectral_images.vnir
selected_areas = pixels_select_lasso(image_vnir)
selected_areas = pixels_select_lasso(image_vnir, selector_props={"color": "blue"})
display_image_with_areas(image_vnir, selected_areas, color="blue")


Expand Down
Loading