pedotri 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pedotri-0.1.0/LICENSE +21 -0
- pedotri-0.1.0/PKG-INFO +338 -0
- pedotri-0.1.0/README.md +292 -0
- pedotri-0.1.0/pyproject.toml +190 -0
- pedotri-0.1.0/src/pedotri/__init__.py +70 -0
- pedotri-0.1.0/src/pedotri/_data/__init__.py +0 -0
- pedotri-0.1.0/src/pedotri/_data/classifications/__init__.py +0 -0
- pedotri-0.1.0/src/pedotri/_data/classifications/avery.toml +166 -0
- pedotri-0.1.0/src/pedotri/_data/classifications/china.toml +102 -0
- pedotri-0.1.0/src/pedotri/_data/classifications/embrapa.toml +81 -0
- pedotri-0.1.0/src/pedotri/_data/classifications/fao.toml +57 -0
- pedotri-0.1.0/src/pedotri/_data/classifications/geppa.toml +205 -0
- pedotri-0.1.0/src/pedotri/_data/classifications/hypres.toml +89 -0
- pedotri-0.1.0/src/pedotri/_data/classifications/international.toml +153 -0
- pedotri-0.1.0/src/pedotri/_data/classifications/isss.toml +165 -0
- pedotri-0.1.0/src/pedotri/_data/classifications/jamagne.toml +191 -0
- pedotri-0.1.0/src/pedotri/_data/classifications/ka5.toml +408 -0
- pedotri-0.1.0/src/pedotri/_data/classifications/kachinsky.toml +131 -0
- pedotri-0.1.0/src/pedotri/_data/classifications/northcote.toml +223 -0
- pedotri-0.1.0/src/pedotri/_data/classifications/ptg.toml +100 -0
- pedotri-0.1.0/src/pedotri/_data/classifications/usda.toml +165 -0
- pedotri-0.1.0/src/pedotri/_pandas_accessor.py +83 -0
- pedotri-0.1.0/src/pedotri/_polars_namespace.py +67 -0
- pedotri-0.1.0/src/pedotri/_types.py +27 -0
- pedotri-0.1.0/src/pedotri/ai.py +591 -0
- pedotri-0.1.0/src/pedotri/classifier.py +440 -0
- pedotri-0.1.0/src/pedotri/cli.py +215 -0
- pedotri-0.1.0/src/pedotri/errors.py +27 -0
- pedotri-0.1.0/src/pedotri/geometry.py +142 -0
- pedotri-0.1.0/src/pedotri/loader.py +188 -0
- pedotri-0.1.0/src/pedotri/mcp_server.py +114 -0
- pedotri-0.1.0/src/pedotri/plot/__init__.py +47 -0
- pedotri-0.1.0/src/pedotri/plot/mpl.py +333 -0
- pedotri-0.1.0/src/pedotri/plot/plotly_backend.py +266 -0
- pedotri-0.1.0/src/pedotri/plot/svg.py +514 -0
- pedotri-0.1.0/src/pedotri/psd.py +262 -0
- pedotri-0.1.0/src/pedotri/ptf/__init__.py +31 -0
- pedotri-0.1.0/src/pedotri/ptf/saxton_rawls.py +254 -0
- pedotri-0.1.0/src/pedotri/ptf/wosten.py +306 -0
- pedotri-0.1.0/src/pedotri/py.typed +0 -0
- pedotri-0.1.0/src/pedotri/registry.py +156 -0
- pedotri-0.1.0/src/pedotri/schema.py +251 -0
- pedotri-0.1.0/src/pedotri/units.py +177 -0
pedotri-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ivan Kolomiets
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pedotri-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pedotri
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Modern, extensible soil texture classification and pedotransfer functions for Python
|
|
5
|
+
Keywords: soil,texture,classification,agronomy,pedology,agriculture,agritech,pedotransfer,hydrology,USDA,FAO,ISSS,GEPPA,Jamagne,Kachinsky,HYPRES,KA5,Embrapa,Northcote,mcp,llm,claude,agent,tool-use
|
|
6
|
+
Author: Ivan Kolomiets
|
|
7
|
+
Author-email: Ivan Kolomiets <ieee802@yandex.ru>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Hydrology
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Dist: numpy>=1.24
|
|
24
|
+
Requires-Dist: matplotlib>=3.7 ; extra == 'all'
|
|
25
|
+
Requires-Dist: plotly>=5.18 ; extra == 'all'
|
|
26
|
+
Requires-Dist: pandas>=2.0 ; extra == 'all'
|
|
27
|
+
Requires-Dist: polars>=0.20 ; extra == 'all'
|
|
28
|
+
Requires-Dist: mcp>=1.0 ; extra == 'all'
|
|
29
|
+
Requires-Dist: matplotlib>=3.7 ; extra == 'matplotlib'
|
|
30
|
+
Requires-Dist: mcp>=1.0 ; extra == 'mcp'
|
|
31
|
+
Requires-Dist: pandas>=2.0 ; extra == 'pandas'
|
|
32
|
+
Requires-Dist: plotly>=5.18 ; extra == 'plotly'
|
|
33
|
+
Requires-Dist: polars>=0.20 ; extra == 'polars'
|
|
34
|
+
Requires-Python: >=3.11
|
|
35
|
+
Project-URL: Homepage, https://github.com/ivanfeanor/pedotri
|
|
36
|
+
Project-URL: Repository, https://github.com/ivanfeanor/pedotri
|
|
37
|
+
Project-URL: Issues, https://github.com/ivanfeanor/pedotri/issues
|
|
38
|
+
Project-URL: Documentation, https://ivanfeanor.github.io/pedotri/
|
|
39
|
+
Provides-Extra: all
|
|
40
|
+
Provides-Extra: matplotlib
|
|
41
|
+
Provides-Extra: mcp
|
|
42
|
+
Provides-Extra: pandas
|
|
43
|
+
Provides-Extra: plotly
|
|
44
|
+
Provides-Extra: polars
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
|
|
47
|
+
# pedotri
|
|
48
|
+
|
|
49
|
+
[](https://pypi.org/project/pedotri/)
|
|
50
|
+
[](https://pypi.org/project/pedotri/)
|
|
51
|
+
[](https://github.com/ivanfeanor/pedotri/blob/main/LICENSE)
|
|
52
|
+
|
|
53
|
+
Modern, extensible soil texture classification and pedotransfer functions for Python.
|
|
54
|
+
|
|
55
|
+
- **Vectorized** point-in-polygon classification via numpy — fast on large arrays.
|
|
56
|
+
- **Extensible.** Define your own classifications in TOML and load them at runtime, or ship them as plugins via `entry_points`.
|
|
57
|
+
- **Multilingual.** Class names available in English, French, German, Spanish, Russian, and Portuguese, with locale fallback.
|
|
58
|
+
- **Comprehensive.** Built-in classifications from around the world: USDA, FAO, GEPPA/Jamagne, KA5, HYPRES, Kachinsky, Embrapa, and more.
|
|
59
|
+
- **Beyond classification.** Pedotransfer functions (Saxton-Rawls, Wösten / HYPRES), particle-size standard conversions, class hierarchies, distance-to-boundary, and a pure-SVG triangle renderer.
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install pedotri # core only (numpy)
|
|
65
|
+
pip install "pedotri[matplotlib]" # static plots
|
|
66
|
+
pip install "pedotri[plotly]" # interactive plots
|
|
67
|
+
pip install "pedotri[pandas]" # DataFrame accessor
|
|
68
|
+
pip install "pedotri[polars]" # Polars accessor
|
|
69
|
+
pip install "pedotri[mcp]" # MCP server for Claude Desktop / Cursor
|
|
70
|
+
pip install "pedotri[all]" # everything
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Quick start
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import pedotri
|
|
77
|
+
|
|
78
|
+
# Single point — returns the class key
|
|
79
|
+
pedotri.classify(13, 50, "USDA")
|
|
80
|
+
# 'clay'
|
|
81
|
+
|
|
82
|
+
# Batch (vectorized)
|
|
83
|
+
pedotri.classify([13, 45, 70], [50, 24, 10], "FAO")
|
|
84
|
+
# ['fine', 'medium', 'coarse']
|
|
85
|
+
|
|
86
|
+
# Localized class names
|
|
87
|
+
pedotri.classify(13, 50, "USDA", locale="fr")
|
|
88
|
+
# 'argile'
|
|
89
|
+
pedotri.classify(13, 50, "GEPPA", locale="fr")
|
|
90
|
+
# 'argile lourde'
|
|
91
|
+
|
|
92
|
+
# 1-D classifications (single axis)
|
|
93
|
+
pedotri.classify(35, "KACHINSKY")
|
|
94
|
+
# 'medium_loam'
|
|
95
|
+
|
|
96
|
+
# Rich result with hierarchy and signed distance to boundary
|
|
97
|
+
result = pedotri.classify(13, 50, "USDA", detailed=True)
|
|
98
|
+
result.key # 'clay'
|
|
99
|
+
result.name # 'clay' (localized name; identical to key for USDA en)
|
|
100
|
+
result.group # 'fine'
|
|
101
|
+
result.distance # positive when strictly inside the class polygon
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Built-in classifications
|
|
105
|
+
|
|
106
|
+
| Key | Region | Axes | Classes | Reference |
|
|
107
|
+
|-----------------|-----------------|-------------------|---------|----------------------------------------------------|
|
|
108
|
+
| `USDA` | USA (global) | sand, clay | 12 | USDA Soil Survey Manual (Handbook 18, 1993) |
|
|
109
|
+
| `FAO` | International | sand, clay | 3 | Verheye & Ameryckx 1984 (FAO grouping) |
|
|
110
|
+
| `INTERNATIONAL` | International | sand, clay | 11 | Leeper & Uren 1993 |
|
|
111
|
+
| `ISSS` | International | sand, clay | 12 | ISSS / Verheye & Ameryckx 1984 |
|
|
112
|
+
| `AVERY` | UK | sand, clay | 12 | Avery 1980 (Soil Survey of England and Wales) |
|
|
113
|
+
| `JAMAGNE` | France | sand, clay | 13 | Jamagne 1967 (original) |
|
|
114
|
+
| `GEPPA` | France | sand, clay | 14 | Baize & Jamagne 1995 (GEPPA-Aisne refinement) |
|
|
115
|
+
| `HYPRES` | Europe | sand, clay | 5 | Wösten et al. 1999 |
|
|
116
|
+
| `KA5` | Germany | sand, clay | 31 | Bodenkundliche Kartieranleitung 5 (full subdivision) |
|
|
117
|
+
| `PTG` | Poland | sand, clay | 6 | Polskie Towarzystwo Gleboznawcze 2008 |
|
|
118
|
+
| `NORTHCOTE` | Australia | sand, clay | 16 | Northcote 1979 (Factual Key, fine clay subdivisions) |
|
|
119
|
+
| `CHINA` | China | sand, clay | 6 | GB/T 17296-2009 (Chinese national standard) |
|
|
120
|
+
| `EMBRAPA` | Brazil | sand, clay | 5 | Embrapa, SiBCS 5ª ed. 2018 |
|
|
121
|
+
| `KACHINSKY` | Russia / CIS | physical_clay | 9 | Качинский 1965 (1-D classification by <0.01 mm fraction) |
|
|
122
|
+
|
|
123
|
+
## API for one- and two-axis classifications
|
|
124
|
+
|
|
125
|
+
Most classifications are defined on the sand-silt-clay simplex (2-D); a few — like Kachinsky — are defined on a single axis (e.g. percent particles <0.01 mm). `classify()` adapts to the chosen classification:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
pedotri.classify(70, 20, "USDA") # 2-D positional
|
|
129
|
+
pedotri.classify(10, "KACHINSKY") # 1-D positional
|
|
130
|
+
pedotri.classify(sand=70, clay=20, classification="USDA") # 2-D kwargs
|
|
131
|
+
pedotri.classify(physical_clay=10, classification="KACHINSKY") # 1-D kwargs
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Custom classifications
|
|
135
|
+
|
|
136
|
+
Define your own classification in TOML and load it:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
pedotri.register_classification("path/to/my_classification.toml")
|
|
140
|
+
pedotri.classify(40, 30, classification="MY_CLASSIFICATION")
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
See [docs/custom-classifications](https://ivanfeanor.github.io/pedotri/custom/) for the schema.
|
|
144
|
+
|
|
145
|
+
## Texture diagrams
|
|
146
|
+
|
|
147
|
+
Three backends share the same API and accept the same arguments. Pick whichever fits your output target.
|
|
148
|
+
|
|
149
|
+
### Built-in SVG (no plotting dependency)
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from pedotri.plot import render_svg, TextureDiagram
|
|
153
|
+
|
|
154
|
+
svg = render_svg("USDA", title="USDA Soil Texture Triangle")
|
|
155
|
+
TextureDiagram("GEPPA", locale="fr").save("geppa.svg")
|
|
156
|
+
|
|
157
|
+
# Inline in Jupyter via _repr_svg_
|
|
158
|
+
TextureDiagram("USDA", points=[(40, 25), (15, 60)], point_labels=["A", "B"])
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Matplotlib (`pip install pedotri[matplotlib]`)
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from pedotri.plot import render_mpl
|
|
165
|
+
|
|
166
|
+
fig = render_mpl("USDA", points=[(40, 25)], point_labels=["sample"])
|
|
167
|
+
fig.savefig("usda.pdf")
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Plotly (`pip install pedotri[plotly]`)
|
|
171
|
+
|
|
172
|
+
Uses Plotly's native ternary subplot — full interactivity, hover tooltips, export to interactive HTML.
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from pedotri.plot import render_plotly
|
|
176
|
+
|
|
177
|
+
fig = render_plotly("USDA", points=[(40, 25)], point_labels=["sample"])
|
|
178
|
+
fig.write_html("usda.html")
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
1-D classifications (e.g. Kachinsky) render as a horizontal banded axis in all three backends.
|
|
182
|
+
|
|
183
|
+
## DataFrame accessors
|
|
184
|
+
|
|
185
|
+
When `pandas` or `polars` is installed, `import pedotri` registers a `.soil` accessor on DataFrames:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
import pandas as pd
|
|
189
|
+
import pedotri # registers df.soil
|
|
190
|
+
|
|
191
|
+
df = pd.DataFrame({"sand": [60, 20, 70], "clay": [10, 50, 5]})
|
|
192
|
+
df["texture"] = df.soil.classify("USDA")
|
|
193
|
+
|
|
194
|
+
# Localized names
|
|
195
|
+
df["fr"] = df.soil.classify("GEPPA", locale="fr")
|
|
196
|
+
|
|
197
|
+
# Different column names
|
|
198
|
+
df2 = pd.DataFrame({"S": [60], "C": [10]})
|
|
199
|
+
df2.soil.classify("USDA", columns={"sand": "S", "clay": "C"})
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Polars works identically:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
import polars as pl
|
|
206
|
+
df = pl.DataFrame({"sand": [60, 20], "clay": [10, 50]})
|
|
207
|
+
df.with_columns(texture=df.soil.classify("USDA"))
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Command-line interface
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
pedotri list # list registered classifications
|
|
214
|
+
pedotri info USDA # show details for one
|
|
215
|
+
pedotri classify --sand 60 --clay 20 -c USDA # single sample
|
|
216
|
+
pedotri classify -c KACHINSKY --physical-clay 35 # 1-D
|
|
217
|
+
pedotri classify --csv samples.csv --output out.csv -c USDA # batch
|
|
218
|
+
pedotri render -c USDA --title "USDA" -o usda.svg # write an SVG
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Use as an LLM tool
|
|
222
|
+
|
|
223
|
+
pedotri ships ready-made JSON tool schemas and an MCP server so models can call it directly.
|
|
224
|
+
|
|
225
|
+
### Anthropic API / OpenAI tool use
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
import pedotri.ai
|
|
229
|
+
from anthropic import Anthropic
|
|
230
|
+
|
|
231
|
+
client = Anthropic()
|
|
232
|
+
response = client.messages.create(
|
|
233
|
+
model="claude-sonnet-4-5",
|
|
234
|
+
max_tokens=1024,
|
|
235
|
+
tools=pedotri.ai.tool_schemas(),
|
|
236
|
+
messages=[{"role": "user", "content": "What's the texture of a 60/20/20 sand/clay/silt sample under USDA?"}],
|
|
237
|
+
)
|
|
238
|
+
# When the model returns tool_use blocks, dispatch them:
|
|
239
|
+
for block in response.content:
|
|
240
|
+
if block.type == "tool_use":
|
|
241
|
+
result = pedotri.ai.run(block.name, block.input)
|
|
242
|
+
# → {"key": "sandy_clay_loam", "name": "sandy clay loam", "group": "moderately_fine", ...}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Eight tools are exposed: `classify_soil`, `classify_soil_1d`, `list_classifications`, `classification_info`, `saxton_rawls`, `wosten`, `convert_particle_size`, `render_diagram`. Errors come back as structured envelopes (not raised) so the model can self-correct in a tool-use loop.
|
|
246
|
+
|
|
247
|
+
### Claude Desktop (MCP)
|
|
248
|
+
|
|
249
|
+
Install with the MCP extra and add to your Claude Desktop config:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
pip install "pedotri[mcp]"
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
On macOS, edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
256
|
+
|
|
257
|
+
```json
|
|
258
|
+
{
|
|
259
|
+
"mcpServers": {
|
|
260
|
+
"pedotri": {
|
|
261
|
+
"command": "pedotri-mcp"
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Claude will then see all eight pedotri tools in every conversation and can call them while reasoning about soil samples.
|
|
268
|
+
|
|
269
|
+
## Units
|
|
270
|
+
|
|
271
|
+
**Every fraction input in pedotri is *percent* by default** (0-100 range). Lab reports commonly use g/kg (especially for organic matter) or g/g (mass fraction); pass `units="g/kg"` or `units="g/g"` to convert at the boundary:
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
# Same soil sample, three input conventions
|
|
275
|
+
pedotri.classify(60, 20, "USDA") # percent (default)
|
|
276
|
+
pedotri.classify(600, 200, "USDA", units="g/kg") # g/kg
|
|
277
|
+
pedotri.classify(0.6, 0.2, "USDA", units="g/g") # mass fraction
|
|
278
|
+
|
|
279
|
+
# Saxton-Rawls with organic-matter in g/kg
|
|
280
|
+
saxton_rawls(sand=40, clay=20, organic_matter=20, units="g/kg")
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The `units=` keyword is accepted by `classify()`, `saxton_rawls()`, `wosten()`, and `psd.convert()`.
|
|
284
|
+
|
|
285
|
+
**Organic carbon vs. organic matter:** PTFs (Saxton-Rawls, Wösten) expect organic *matter*, but most lab reports give organic *carbon*. Convert with `pedotri.units.organic_carbon_to_organic_matter()` (Van Bemmelen factor 1.724) first:
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
from pedotri.units import organic_carbon_to_organic_matter
|
|
289
|
+
|
|
290
|
+
oc_pct = 1.16 # from lab report (% organic carbon)
|
|
291
|
+
om_pct = float(organic_carbon_to_organic_matter(oc_pct)[0]) # ~2.0 %
|
|
292
|
+
saxton_rawls(sand=40, clay=20, organic_matter=om_pct)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
`pedotri.units` also exposes `g_per_kg_to_percent`, `percent_to_g_per_kg`, `g_per_g_to_percent`, `percent_to_g_per_g`, `organic_matter_to_organic_carbon`, and `to_percent` / `from_percent` for arbitrary unit identifiers.
|
|
296
|
+
|
|
297
|
+
## Pedotransfer functions
|
|
298
|
+
|
|
299
|
+
Two PTFs ship in `pedotri.ptf`:
|
|
300
|
+
|
|
301
|
+
### Saxton-Rawls (2006)
|
|
302
|
+
|
|
303
|
+
Water retention + saturated conductivity from sand, clay, and organic matter.
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
from pedotri.ptf import saxton_rawls
|
|
307
|
+
|
|
308
|
+
r = saxton_rawls(sand=40, clay=20, organic_matter=2.0)
|
|
309
|
+
r.wilting_point # theta_1500 (m³/m³)
|
|
310
|
+
r.field_capacity # theta_33 (m³/m³)
|
|
311
|
+
r.saturation # theta_s (m³/m³)
|
|
312
|
+
r.available_water # FC - WP
|
|
313
|
+
r.saturated_conductivity # K_s (mm/h)
|
|
314
|
+
r.bulk_density # g/cm³
|
|
315
|
+
r.air_entry_tension # psi_e (kPa)
|
|
316
|
+
|
|
317
|
+
# Compaction-corrected output (Saxton-Rawls 2006 Eq. 6-7)
|
|
318
|
+
r_compacted = saxton_rawls(40, 20, 2.0, density_factor=1.10)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Wösten (1999) — HYPRES Mualem-van Genuchten
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
from pedotri.ptf import wosten
|
|
325
|
+
|
|
326
|
+
w = wosten(sand=40, silt=40, clay=20, organic_matter=2.0, bulk_density=1.4, topsoil=True)
|
|
327
|
+
w.theta_s # saturated water content
|
|
328
|
+
w.alpha # van Genuchten alpha (1/cm)
|
|
329
|
+
w.n # van Genuchten n
|
|
330
|
+
w.saturated_conductivity # K_s (cm/day)
|
|
331
|
+
w.l # Mualem pore-connectivity parameter
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Both functions accept either scalar inputs (returning one result object) or array-like inputs (returning a list).
|
|
335
|
+
|
|
336
|
+
## License
|
|
337
|
+
|
|
338
|
+
MIT
|
pedotri-0.1.0/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# pedotri
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/pedotri/)
|
|
4
|
+
[](https://pypi.org/project/pedotri/)
|
|
5
|
+
[](https://github.com/ivanfeanor/pedotri/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
Modern, extensible soil texture classification and pedotransfer functions for Python.
|
|
8
|
+
|
|
9
|
+
- **Vectorized** point-in-polygon classification via numpy — fast on large arrays.
|
|
10
|
+
- **Extensible.** Define your own classifications in TOML and load them at runtime, or ship them as plugins via `entry_points`.
|
|
11
|
+
- **Multilingual.** Class names available in English, French, German, Spanish, Russian, and Portuguese, with locale fallback.
|
|
12
|
+
- **Comprehensive.** Built-in classifications from around the world: USDA, FAO, GEPPA/Jamagne, KA5, HYPRES, Kachinsky, Embrapa, and more.
|
|
13
|
+
- **Beyond classification.** Pedotransfer functions (Saxton-Rawls, Wösten / HYPRES), particle-size standard conversions, class hierarchies, distance-to-boundary, and a pure-SVG triangle renderer.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install pedotri # core only (numpy)
|
|
19
|
+
pip install "pedotri[matplotlib]" # static plots
|
|
20
|
+
pip install "pedotri[plotly]" # interactive plots
|
|
21
|
+
pip install "pedotri[pandas]" # DataFrame accessor
|
|
22
|
+
pip install "pedotri[polars]" # Polars accessor
|
|
23
|
+
pip install "pedotri[mcp]" # MCP server for Claude Desktop / Cursor
|
|
24
|
+
pip install "pedotri[all]" # everything
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import pedotri
|
|
31
|
+
|
|
32
|
+
# Single point — returns the class key
|
|
33
|
+
pedotri.classify(13, 50, "USDA")
|
|
34
|
+
# 'clay'
|
|
35
|
+
|
|
36
|
+
# Batch (vectorized)
|
|
37
|
+
pedotri.classify([13, 45, 70], [50, 24, 10], "FAO")
|
|
38
|
+
# ['fine', 'medium', 'coarse']
|
|
39
|
+
|
|
40
|
+
# Localized class names
|
|
41
|
+
pedotri.classify(13, 50, "USDA", locale="fr")
|
|
42
|
+
# 'argile'
|
|
43
|
+
pedotri.classify(13, 50, "GEPPA", locale="fr")
|
|
44
|
+
# 'argile lourde'
|
|
45
|
+
|
|
46
|
+
# 1-D classifications (single axis)
|
|
47
|
+
pedotri.classify(35, "KACHINSKY")
|
|
48
|
+
# 'medium_loam'
|
|
49
|
+
|
|
50
|
+
# Rich result with hierarchy and signed distance to boundary
|
|
51
|
+
result = pedotri.classify(13, 50, "USDA", detailed=True)
|
|
52
|
+
result.key # 'clay'
|
|
53
|
+
result.name # 'clay' (localized name; identical to key for USDA en)
|
|
54
|
+
result.group # 'fine'
|
|
55
|
+
result.distance # positive when strictly inside the class polygon
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Built-in classifications
|
|
59
|
+
|
|
60
|
+
| Key | Region | Axes | Classes | Reference |
|
|
61
|
+
|-----------------|-----------------|-------------------|---------|----------------------------------------------------|
|
|
62
|
+
| `USDA` | USA (global) | sand, clay | 12 | USDA Soil Survey Manual (Handbook 18, 1993) |
|
|
63
|
+
| `FAO` | International | sand, clay | 3 | Verheye & Ameryckx 1984 (FAO grouping) |
|
|
64
|
+
| `INTERNATIONAL` | International | sand, clay | 11 | Leeper & Uren 1993 |
|
|
65
|
+
| `ISSS` | International | sand, clay | 12 | ISSS / Verheye & Ameryckx 1984 |
|
|
66
|
+
| `AVERY` | UK | sand, clay | 12 | Avery 1980 (Soil Survey of England and Wales) |
|
|
67
|
+
| `JAMAGNE` | France | sand, clay | 13 | Jamagne 1967 (original) |
|
|
68
|
+
| `GEPPA` | France | sand, clay | 14 | Baize & Jamagne 1995 (GEPPA-Aisne refinement) |
|
|
69
|
+
| `HYPRES` | Europe | sand, clay | 5 | Wösten et al. 1999 |
|
|
70
|
+
| `KA5` | Germany | sand, clay | 31 | Bodenkundliche Kartieranleitung 5 (full subdivision) |
|
|
71
|
+
| `PTG` | Poland | sand, clay | 6 | Polskie Towarzystwo Gleboznawcze 2008 |
|
|
72
|
+
| `NORTHCOTE` | Australia | sand, clay | 16 | Northcote 1979 (Factual Key, fine clay subdivisions) |
|
|
73
|
+
| `CHINA` | China | sand, clay | 6 | GB/T 17296-2009 (Chinese national standard) |
|
|
74
|
+
| `EMBRAPA` | Brazil | sand, clay | 5 | Embrapa, SiBCS 5ª ed. 2018 |
|
|
75
|
+
| `KACHINSKY` | Russia / CIS | physical_clay | 9 | Качинский 1965 (1-D classification by <0.01 mm fraction) |
|
|
76
|
+
|
|
77
|
+
## API for one- and two-axis classifications
|
|
78
|
+
|
|
79
|
+
Most classifications are defined on the sand-silt-clay simplex (2-D); a few — like Kachinsky — are defined on a single axis (e.g. percent particles <0.01 mm). `classify()` adapts to the chosen classification:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
pedotri.classify(70, 20, "USDA") # 2-D positional
|
|
83
|
+
pedotri.classify(10, "KACHINSKY") # 1-D positional
|
|
84
|
+
pedotri.classify(sand=70, clay=20, classification="USDA") # 2-D kwargs
|
|
85
|
+
pedotri.classify(physical_clay=10, classification="KACHINSKY") # 1-D kwargs
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Custom classifications
|
|
89
|
+
|
|
90
|
+
Define your own classification in TOML and load it:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
pedotri.register_classification("path/to/my_classification.toml")
|
|
94
|
+
pedotri.classify(40, 30, classification="MY_CLASSIFICATION")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
See [docs/custom-classifications](https://ivanfeanor.github.io/pedotri/custom/) for the schema.
|
|
98
|
+
|
|
99
|
+
## Texture diagrams
|
|
100
|
+
|
|
101
|
+
Three backends share the same API and accept the same arguments. Pick whichever fits your output target.
|
|
102
|
+
|
|
103
|
+
### Built-in SVG (no plotting dependency)
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from pedotri.plot import render_svg, TextureDiagram
|
|
107
|
+
|
|
108
|
+
svg = render_svg("USDA", title="USDA Soil Texture Triangle")
|
|
109
|
+
TextureDiagram("GEPPA", locale="fr").save("geppa.svg")
|
|
110
|
+
|
|
111
|
+
# Inline in Jupyter via _repr_svg_
|
|
112
|
+
TextureDiagram("USDA", points=[(40, 25), (15, 60)], point_labels=["A", "B"])
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Matplotlib (`pip install pedotri[matplotlib]`)
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from pedotri.plot import render_mpl
|
|
119
|
+
|
|
120
|
+
fig = render_mpl("USDA", points=[(40, 25)], point_labels=["sample"])
|
|
121
|
+
fig.savefig("usda.pdf")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Plotly (`pip install pedotri[plotly]`)
|
|
125
|
+
|
|
126
|
+
Uses Plotly's native ternary subplot — full interactivity, hover tooltips, export to interactive HTML.
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from pedotri.plot import render_plotly
|
|
130
|
+
|
|
131
|
+
fig = render_plotly("USDA", points=[(40, 25)], point_labels=["sample"])
|
|
132
|
+
fig.write_html("usda.html")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
1-D classifications (e.g. Kachinsky) render as a horizontal banded axis in all three backends.
|
|
136
|
+
|
|
137
|
+
## DataFrame accessors
|
|
138
|
+
|
|
139
|
+
When `pandas` or `polars` is installed, `import pedotri` registers a `.soil` accessor on DataFrames:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import pandas as pd
|
|
143
|
+
import pedotri # registers df.soil
|
|
144
|
+
|
|
145
|
+
df = pd.DataFrame({"sand": [60, 20, 70], "clay": [10, 50, 5]})
|
|
146
|
+
df["texture"] = df.soil.classify("USDA")
|
|
147
|
+
|
|
148
|
+
# Localized names
|
|
149
|
+
df["fr"] = df.soil.classify("GEPPA", locale="fr")
|
|
150
|
+
|
|
151
|
+
# Different column names
|
|
152
|
+
df2 = pd.DataFrame({"S": [60], "C": [10]})
|
|
153
|
+
df2.soil.classify("USDA", columns={"sand": "S", "clay": "C"})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Polars works identically:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
import polars as pl
|
|
160
|
+
df = pl.DataFrame({"sand": [60, 20], "clay": [10, 50]})
|
|
161
|
+
df.with_columns(texture=df.soil.classify("USDA"))
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Command-line interface
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
pedotri list # list registered classifications
|
|
168
|
+
pedotri info USDA # show details for one
|
|
169
|
+
pedotri classify --sand 60 --clay 20 -c USDA # single sample
|
|
170
|
+
pedotri classify -c KACHINSKY --physical-clay 35 # 1-D
|
|
171
|
+
pedotri classify --csv samples.csv --output out.csv -c USDA # batch
|
|
172
|
+
pedotri render -c USDA --title "USDA" -o usda.svg # write an SVG
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Use as an LLM tool
|
|
176
|
+
|
|
177
|
+
pedotri ships ready-made JSON tool schemas and an MCP server so models can call it directly.
|
|
178
|
+
|
|
179
|
+
### Anthropic API / OpenAI tool use
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
import pedotri.ai
|
|
183
|
+
from anthropic import Anthropic
|
|
184
|
+
|
|
185
|
+
client = Anthropic()
|
|
186
|
+
response = client.messages.create(
|
|
187
|
+
model="claude-sonnet-4-5",
|
|
188
|
+
max_tokens=1024,
|
|
189
|
+
tools=pedotri.ai.tool_schemas(),
|
|
190
|
+
messages=[{"role": "user", "content": "What's the texture of a 60/20/20 sand/clay/silt sample under USDA?"}],
|
|
191
|
+
)
|
|
192
|
+
# When the model returns tool_use blocks, dispatch them:
|
|
193
|
+
for block in response.content:
|
|
194
|
+
if block.type == "tool_use":
|
|
195
|
+
result = pedotri.ai.run(block.name, block.input)
|
|
196
|
+
# → {"key": "sandy_clay_loam", "name": "sandy clay loam", "group": "moderately_fine", ...}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Eight tools are exposed: `classify_soil`, `classify_soil_1d`, `list_classifications`, `classification_info`, `saxton_rawls`, `wosten`, `convert_particle_size`, `render_diagram`. Errors come back as structured envelopes (not raised) so the model can self-correct in a tool-use loop.
|
|
200
|
+
|
|
201
|
+
### Claude Desktop (MCP)
|
|
202
|
+
|
|
203
|
+
Install with the MCP extra and add to your Claude Desktop config:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
pip install "pedotri[mcp]"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
On macOS, edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
210
|
+
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"mcpServers": {
|
|
214
|
+
"pedotri": {
|
|
215
|
+
"command": "pedotri-mcp"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Claude will then see all eight pedotri tools in every conversation and can call them while reasoning about soil samples.
|
|
222
|
+
|
|
223
|
+
## Units
|
|
224
|
+
|
|
225
|
+
**Every fraction input in pedotri is *percent* by default** (0-100 range). Lab reports commonly use g/kg (especially for organic matter) or g/g (mass fraction); pass `units="g/kg"` or `units="g/g"` to convert at the boundary:
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
# Same soil sample, three input conventions
|
|
229
|
+
pedotri.classify(60, 20, "USDA") # percent (default)
|
|
230
|
+
pedotri.classify(600, 200, "USDA", units="g/kg") # g/kg
|
|
231
|
+
pedotri.classify(0.6, 0.2, "USDA", units="g/g") # mass fraction
|
|
232
|
+
|
|
233
|
+
# Saxton-Rawls with organic-matter in g/kg
|
|
234
|
+
saxton_rawls(sand=40, clay=20, organic_matter=20, units="g/kg")
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The `units=` keyword is accepted by `classify()`, `saxton_rawls()`, `wosten()`, and `psd.convert()`.
|
|
238
|
+
|
|
239
|
+
**Organic carbon vs. organic matter:** PTFs (Saxton-Rawls, Wösten) expect organic *matter*, but most lab reports give organic *carbon*. Convert with `pedotri.units.organic_carbon_to_organic_matter()` (Van Bemmelen factor 1.724) first:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
from pedotri.units import organic_carbon_to_organic_matter
|
|
243
|
+
|
|
244
|
+
oc_pct = 1.16 # from lab report (% organic carbon)
|
|
245
|
+
om_pct = float(organic_carbon_to_organic_matter(oc_pct)[0]) # ~2.0 %
|
|
246
|
+
saxton_rawls(sand=40, clay=20, organic_matter=om_pct)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`pedotri.units` also exposes `g_per_kg_to_percent`, `percent_to_g_per_kg`, `g_per_g_to_percent`, `percent_to_g_per_g`, `organic_matter_to_organic_carbon`, and `to_percent` / `from_percent` for arbitrary unit identifiers.
|
|
250
|
+
|
|
251
|
+
## Pedotransfer functions
|
|
252
|
+
|
|
253
|
+
Two PTFs ship in `pedotri.ptf`:
|
|
254
|
+
|
|
255
|
+
### Saxton-Rawls (2006)
|
|
256
|
+
|
|
257
|
+
Water retention + saturated conductivity from sand, clay, and organic matter.
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
from pedotri.ptf import saxton_rawls
|
|
261
|
+
|
|
262
|
+
r = saxton_rawls(sand=40, clay=20, organic_matter=2.0)
|
|
263
|
+
r.wilting_point # theta_1500 (m³/m³)
|
|
264
|
+
r.field_capacity # theta_33 (m³/m³)
|
|
265
|
+
r.saturation # theta_s (m³/m³)
|
|
266
|
+
r.available_water # FC - WP
|
|
267
|
+
r.saturated_conductivity # K_s (mm/h)
|
|
268
|
+
r.bulk_density # g/cm³
|
|
269
|
+
r.air_entry_tension # psi_e (kPa)
|
|
270
|
+
|
|
271
|
+
# Compaction-corrected output (Saxton-Rawls 2006 Eq. 6-7)
|
|
272
|
+
r_compacted = saxton_rawls(40, 20, 2.0, density_factor=1.10)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Wösten (1999) — HYPRES Mualem-van Genuchten
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
from pedotri.ptf import wosten
|
|
279
|
+
|
|
280
|
+
w = wosten(sand=40, silt=40, clay=20, organic_matter=2.0, bulk_density=1.4, topsoil=True)
|
|
281
|
+
w.theta_s # saturated water content
|
|
282
|
+
w.alpha # van Genuchten alpha (1/cm)
|
|
283
|
+
w.n # van Genuchten n
|
|
284
|
+
w.saturated_conductivity # K_s (cm/day)
|
|
285
|
+
w.l # Mualem pore-connectivity parameter
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Both functions accept either scalar inputs (returning one result object) or array-like inputs (returning a list).
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
MIT
|