Skip to content

Commit 64fc0a3

Browse files
authored
Merge pull request #223 from siapy/fix
2 parents c8b66a6 + 92c09c0 commit 64fc0a3

File tree

8 files changed

+1196
-10
lines changed

8 files changed

+1196
-10
lines changed

CONTRIBUTING.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ Most commands use PDM (Python package and dependency manager) internally, which
100100

101101
## Development
102102

103+
### Quick design pattern guide
104+
105+
For consistent code quality across the project, refer to [copilot-instructions](https://github.com/siapy/siapy-lib/blob/main/.github/copilot-instructions.md), which concisely describes key design patterns.
106+
103107
### Git
104108

105109
_[Why use Git?](https://www.git-scm.com/about)_ Git enables creation of multiple versions of a code repository called branches, with the ability to track and undo changes in detail.
@@ -113,16 +117,14 @@ Contribute by following:
113117
- Commit your changes with a [properly-formatted Git commit message](https://chris.beams.io/posts/git-commit/).
114118
- Create a [pull request (PR)](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) to incorporate your changes into the upstream project you forked.
115119

116-
### Project organization around git
117-
118-
### Branch Structure
120+
### Branch structure
119121

120122
| Branch | Purpose | Protection Rules |
121123
|-----------|-----------------------------|---------------------------------|
122124
| `main` | Stable production code | • Signed commits required<br>• No force pushing<br>• Status checks required<br>• Admin included |
123125
| `develop` | Integration branch | • Signed commits required<br>• Force pushing allowed<br>• Admin included |
124126

125-
### Workflow Rules
127+
### Workflow rules
126128

127129
- The default branch is `main`
128130
- Feature branches must target `develop` for pull requests
@@ -149,7 +151,7 @@ _Type_ must be one of the following:
149151
- `build`: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm), hidden
150152
- `ci`: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs), hidden
151153

152-
## GitHub Actions workflows
154+
## GitHub actions workflows
153155

154156
[GitHub Actions](https://github.com/features/actions) is a continuous integration/continuous deployment (CI/CD) service that runs on GitHub repos. Actions are grouped into workflows and stored in _.github/workflows_.
155157

docs/concepts/entities.md

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
# Entities
22

3+
??? note "API Documentation"
4+
`siapy.entities`
5+
36
Entities serve as the foundational data structures in SiaPy, representing key elements of spectral image analysis and processing workflows. They implement consistent, strongly-typed interfaces that allow seamless interaction between spectral data, spatial coordinates, and geometric information.
47

5-
## Design principles
8+
## Module architecture
9+
10+
The relationships between components are shown in the following diagram:
11+
12+
![Entities Schematics](images/entities_schematics.svg)
13+
14+
### Design principles
615

716
SiaPy's architecture follows several key design principles:
817

@@ -20,9 +29,23 @@ SiaPy's architecture follows several key design principles:
2029
- The `SpectralImage` class supports multiple data sources through *spectral* or *rasterio* libraries, however, custom data loading can be implemented by creating your own driver
2130
- Basic geometric shapes (e.g. points, lines, polygons) are implemented using the *shapely* library, which could also be extended through base abstraction
2231

23-
The class structure is depicted in the following diagram:
32+
### Visual representation of spectral entities
2433

25-
![Entities Schematics](images/entities_schematics.svg)
34+
The components follow standard naming conventions for spectral image analysis. A `SpectralImage` represents a three-dimensional data cube (height × width × bands). Each pixel in this cube has spatial coordinates *(x, y)* and a corresponding spectral signal with values across all bands.
35+
36+
A signature combines a pixel's spatial location with its spectral signal, creating a complete spatial-spectral data point. The `Signatures` class serves as a container for multiple such data points, enabling analysis across collections of pixels.
37+
38+
Since spectral images often contain distinct objects with different spectral properties, `Shapes` (regions of connected pixels) allow for extraction and analysis of specific areas. The `SpectralImage` class therefore integrates all primitive structures: `Pixels` and `Signals` by definition, and `Shapes` when attached to the image.
39+
40+
![Entities Relations](images/entities_relations.png)
41+
42+
| Name | What it represents | Shape / size |
43+
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
44+
| **`Spectral image`** | A single hyperspectral or multispectral data cube. | **`(H, W, B)`***height × width × bands* |
45+
| **`Spectral image set`** | An ordered collection of `SpectralImage` objects. Think of it as a “dataset” with convenience methods that loop internally instead of in user code. | *N* × `SpectralImage` for *N* spectral images |
46+
| **`Pixels`** | One Cartesian coordinate **`(x, y)`**. | **`(N, 2)`** for *N* pixels |
47+
| **`Shapes`** | A geometric region of interest (e.g. rectangle, polygon, circle), serving as a *container of pixels*. | Vector geometry |
48+
| **`Signatures`** | A collection of 1-D spectral signals **`(B,)`** tied to pixel values **`(x, y)`** or aggregated over a shape. | **`(N, B + 2)`***N* signatures, each with *B* spectral bands plus **`(x, y)`** coordinates |
2649

2750
## Pixels
2851

@@ -125,6 +148,19 @@ For specialized file formats or custom processing needs, you can extend the Imag
125148
--8<-- "docs/concepts/src/spectral_image_04.py"
126149
```
127150

151+
### Data conversion methods
152+
153+
The example below demonstrates two key conversion methods of `SpectralImage` instance:
154+
155+
1. `to_signatures()`: Extracts spectral data from specific pixel coordinates and returns a `Signatures` object that maintains the spatial-spectral relationship
156+
2. `to_subarray()`: Converts selected pixel data to a NumPy array for numerical processing or integration with other scientific libraries
157+
158+
```python
159+
--8<-- "docs/concepts/src/spectral_image_05.py"
160+
```
161+
162+
<!-- ### Manipulation of shapes -->
163+
128164
## Spectral Image Set
129165

130166
??? api "API Documentation"
1.31 MB
Loading

docs/concepts/images/entities_relations.svg

Lines changed: 998 additions & 0 deletions
Loading
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0" version="26.2.14">
2+
<diagram name="Page-1" id="KD9Rf0FGmBdKboop7PPn">
3+
<mxGraphModel grid="0" page="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" pageScale="1" pageWidth="1200" pageHeight="1600" math="0" shadow="0" adaptiveColors="auto">
4+
<root>
5+
<mxCell id="0" />
6+
<mxCell id="1" parent="0" />
7+
<mxCell id="Q9c8q2dsdVY6IwAu6Xcm-1" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;strokeColor=#331A00;strokeWidth=0.5;" vertex="1" parent="1">
8+
<mxGeometry x="227" y="272" width="785" height="414" as="geometry" />
9+
</mxCell>
10+
<mxCell id="2ScwjqWDhc_dIcK86GyV-1" value="&lt;font style=&quot;font-size: 14px;&quot;&gt;Spectral Image Set&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#B2C9AB;strokeColor=#788AA3;fontColor=#46495D;" vertex="1" parent="1">
11+
<mxGeometry x="500" y="307" width="120" height="60" as="geometry" />
12+
</mxCell>
13+
<mxCell id="2ScwjqWDhc_dIcK86GyV-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="2ScwjqWDhc_dIcK86GyV-2" target="2ScwjqWDhc_dIcK86GyV-1">
14+
<mxGeometry relative="1" as="geometry" />
15+
</mxCell>
16+
<mxCell id="2ScwjqWDhc_dIcK86GyV-2" value="&lt;font style=&quot;font-size: 14px;&quot;&gt;Spectral Image&amp;nbsp;&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#B2C9AB;strokeColor=#788AA3;fontColor=#46495D;" vertex="1" parent="1">
17+
<mxGeometry x="500" y="404" width="120" height="60" as="geometry" />
18+
</mxCell>
19+
<mxCell id="2ScwjqWDhc_dIcK86GyV-14" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="2ScwjqWDhc_dIcK86GyV-3" target="2ScwjqWDhc_dIcK86GyV-2">
20+
<mxGeometry relative="1" as="geometry" />
21+
</mxCell>
22+
<mxCell id="2ScwjqWDhc_dIcK86GyV-3" value="&lt;font style=&quot;font-size: 14px;&quot;&gt;Shapes&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#B2C9AB;strokeColor=#788AA3;fontColor=#46495D;" vertex="1" parent="1">
23+
<mxGeometry x="654" y="404" width="120" height="60" as="geometry" />
24+
</mxCell>
25+
<mxCell id="2ScwjqWDhc_dIcK86GyV-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="2ScwjqWDhc_dIcK86GyV-4" target="2ScwjqWDhc_dIcK86GyV-6">
26+
<mxGeometry relative="1" as="geometry" />
27+
</mxCell>
28+
<mxCell id="2ScwjqWDhc_dIcK86GyV-4" value="&lt;font style=&quot;font-size: 14px;&quot;&gt;Signals&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#B2C9AB;strokeColor=#788AA3;fontColor=#46495D;" vertex="1" parent="1">
29+
<mxGeometry x="500" y="597" width="120" height="60" as="geometry" />
30+
</mxCell>
31+
<mxCell id="2ScwjqWDhc_dIcK86GyV-13" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="2ScwjqWDhc_dIcK86GyV-5" target="2ScwjqWDhc_dIcK86GyV-6">
32+
<mxGeometry relative="1" as="geometry" />
33+
</mxCell>
34+
<mxCell id="2ScwjqWDhc_dIcK86GyV-5" value="&lt;font style=&quot;font-size: 14px;&quot;&gt;Pixels&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#B2C9AB;strokeColor=#788AA3;fontColor=#46495D;" vertex="1" parent="1">
35+
<mxGeometry x="654" y="500" width="120" height="60" as="geometry" />
36+
</mxCell>
37+
<mxCell id="2ScwjqWDhc_dIcK86GyV-11" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="2ScwjqWDhc_dIcK86GyV-6" target="2ScwjqWDhc_dIcK86GyV-2">
38+
<mxGeometry relative="1" as="geometry" />
39+
</mxCell>
40+
<mxCell id="2ScwjqWDhc_dIcK86GyV-6" value="&lt;font style=&quot;font-size: 14px;&quot;&gt;Signatures&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#B2C9AB;strokeColor=#788AA3;fontColor=#46495D;" vertex="1" parent="1">
41+
<mxGeometry x="500" y="500" width="120" height="60" as="geometry" />
42+
</mxCell>
43+
<mxCell id="2ScwjqWDhc_dIcK86GyV-15" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="2ScwjqWDhc_dIcK86GyV-8" target="2ScwjqWDhc_dIcK86GyV-3">
44+
<mxGeometry relative="1" as="geometry" />
45+
</mxCell>
46+
<mxCell id="2ScwjqWDhc_dIcK86GyV-8" value="&lt;font style=&quot;font-size: 14px;&quot;&gt;Base Geometry&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#B2C9AB;strokeColor=#788AA3;fontColor=#46495D;" vertex="1" parent="1">
47+
<mxGeometry x="807" y="404" width="120" height="60" as="geometry" />
48+
</mxCell>
49+
<mxCell id="2ScwjqWDhc_dIcK86GyV-18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="2ScwjqWDhc_dIcK86GyV-9" target="2ScwjqWDhc_dIcK86GyV-2">
50+
<mxGeometry relative="1" as="geometry" />
51+
</mxCell>
52+
<mxCell id="2ScwjqWDhc_dIcK86GyV-9" value="&lt;font style=&quot;font-size: 14px;&quot;&gt;Base Image&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#B2C9AB;strokeColor=#788AA3;fontColor=#46495D;" vertex="1" parent="1">
53+
<mxGeometry x="347" y="404" width="120" height="60" as="geometry" />
54+
</mxCell>
55+
<mxCell id="ZhcXIKDLDVEHgMZw6Vce-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=diamond;endFill=1;" edge="1" parent="1" source="ZhcXIKDLDVEHgMZw6Vce-4" target="2ScwjqWDhc_dIcK86GyV-8">
56+
<mxGeometry relative="1" as="geometry" />
57+
</mxCell>
58+
<mxCell id="ZhcXIKDLDVEHgMZw6Vce-4" value="&lt;div style=&quot;text-align: center;&quot;&gt;&lt;br&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&amp;nbsp; &amp;nbsp;Shapely library:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));&quot;&gt;Points&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));&quot;&gt;Lines&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));&quot;&gt;Polygons&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));&quot;&gt;...&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;dashed=1;align=left;imageHeight=27;" vertex="1" parent="1">
59+
<mxGeometry x="807" y="490" width="121" height="107" as="geometry" />
60+
</mxCell>
61+
<mxCell id="ZhcXIKDLDVEHgMZw6Vce-10" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;endArrow=diamond;endFill=1;" edge="1" parent="1" source="ZhcXIKDLDVEHgMZw6Vce-8" target="2ScwjqWDhc_dIcK86GyV-9">
62+
<mxGeometry relative="1" as="geometry" />
63+
</mxCell>
64+
<mxCell id="ZhcXIKDLDVEHgMZw6Vce-8" value="&lt;ul&gt;&lt;li&gt;Spectral library&lt;/li&gt;&lt;li&gt;Rasterio library&lt;/li&gt;&lt;li&gt;...&lt;/li&gt;&lt;/ul&gt;" style="rounded=0;whiteSpace=wrap;html=1;dashed=1;align=left;imageHeight=27;" vertex="1" parent="1">
65+
<mxGeometry x="314" y="490" width="153" height="70" as="geometry" />
66+
</mxCell>
67+
</root>
68+
</mxGraphModel>
69+
</diagram>
70+
</mxfile>

docs/concepts/overview.md

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,58 @@
1-
# Library Overview
1+
# Library overview
2+
3+
## API design principles
4+
5+
SiaPy follows consistent method naming conventions to make the API intuitive and predictable. Understanding these conventions helps you navigate the library more effectively and write code that aligns with the project's style.
6+
7+
**Simple properties**: Noun form for direct attribute access
8+
9+
- Examples: `image.geometric_shapes`, `pixels.df`
10+
- When to use: For quick, computationally inexpensive property access
11+
12+
**Expensive computations**: Prefixed with `get_` for methods that require significant processing
13+
14+
- Examples: `pixels.get_coordinate()`
15+
- When to use: When the operation involves complex calculations or data retrieval
16+
17+
**Alternative constructors**: Prefixed with `from_` for methods that create objects from different data sources
18+
19+
- Examples: `from_numpy()`, `from_dataframe()`, `from_shapefile()`
20+
- When to use: When creating an object from an existing data structure
21+
22+
**Data converters**: Prefixed with `to_` for methods that transform data to another format
23+
24+
- Examples: `to_numpy()`, `to_dataframe()`, `to_geojson()`
25+
- When to use: When exporting data to another representation
26+
27+
**File operations**:
28+
29+
Prefixed with `open_` for reading data from file
30+
31+
- Examples: `open_envi()`, `open_shapefile()`, `open_csv()`
32+
33+
Prefixed with `save_` for writing data to file
34+
35+
- Examples: `save_to_csv()`, `save_to_geotiff()`, `save_as_json()`
36+
37+
**Actions and processing**:
38+
39+
Verbs for operations that modify data or perform calculations
40+
41+
- Examples: `normalize()`, `calculate_ndvi()`, `extract_features()`
42+
43+
Plural forms for batch operations on multiple items
44+
45+
- Examples: `process_images()`, `extract_signatures()`, `calculate_indices()`
46+
47+
**Boolean queries**: Prefixed with `is_`, `has_`, or `can_` for methods returning boolean values
48+
49+
- Examples: `is_valid()`, `has_metadata()`, `can_transform()`
50+
- When to use: For methods that check conditions or properties
51+
52+
**Factory methods**: Prefixed with `create_` for methods that generate new instances
53+
54+
- Examples: `create_mask()`, `create_subset()`, `create_transformer()`
55+
- When to use: When creating new objects based on specific parameters
256

357
## Architecture
458

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import numpy as np
2+
from rich import print
3+
4+
from siapy.entities import Pixels, SpectralImage
5+
6+
# Create mock image
7+
rng = np.random.default_rng(seed=42)
8+
array = rng.random((100, 100, 10)) # height, width, bands
9+
image = SpectralImage.from_numpy(array)
10+
11+
# Define pixels
12+
iterable = [(1, 2), (3, 4), (2, 4)]
13+
pixels = Pixels.from_iterable(iterable)
14+
15+
# Get signatures
16+
signatures = image.to_signatures(pixels)
17+
print(f"Signatures:\n{signatures}")
18+
19+
# Get numpy array
20+
subarray = image.to_subarray(pixels)
21+
print(f"Subarray:\n{subarray}")
22+
"""
23+
? Note:
24+
The extracted block has shape (3, 3, 10): a 3 × 3 pixel window across 10 spectral bands.
25+
Values are populated only at the requested pixel coordinates; all other elements are set to NaN.
26+
"""

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,6 @@ keep-runtime-typing = true
147147

148148
[tool.codespell]
149149
ignore-words-list = "janezlapajne"
150-
skip = 'dist/*, ./docs/images/*, htmlcov, LICENCE, *.lock, *.toml, CHANGELOG.md, *.cff'
150+
skip = 'dist/*, htmlcov, LICENCE, *.lock, *.toml, CHANGELOG.md, *.cff, *.svg'
151151
count = true
152152
check-hidden = false

0 commit comments

Comments
 (0)