|
1 | | -# Subplot Generator - Implementation Summary |
2 | | - |
3 | | -## 20260206 Session: Subplot Generator Feature |
4 | | - |
5 | | -### What Was Done |
6 | | - |
7 | | -Implemented a subplot generator for EasyIDP that creates grid-based subplots within field boundary polygons. |
8 | | - |
9 | | -### New Files |
10 | | - |
11 | | -| File | Description | |
12 | | -|------|-------------| |
13 | | -| subplot.py | Core module with `generate_subplots()` function | |
14 | | -| test_subplot.py | 19 test cases for subplot generation | |
15 | | - |
16 | | -### Modified Files |
17 | | - |
18 | | -| File | Changes | |
19 | | -|------|---------| |
20 | | -| roi.py | Added `save_shp()` method | |
21 | | -| visualize.py | Added `show_subplots()` function | |
22 | | -| \_\_init\_\_.py | Added subplot module imports | |
23 | | - |
24 | | -### API Usage |
| 1 | +# Subplot Generator Feature Implementation |
| 2 | + |
| 3 | +## Overview |
| 4 | +Implemented a subplot generator for EasyIDP that creates grid-based subplots within field boundary polygons. This feature supports both grid-count and fixed-size modes, handles non-rectangular boundaries with configurable filtering, and includes visualization tools. |
| 5 | + |
| 6 | +During implementation, the code was refactored to integrate closely with existing modules (`geotools`, `shp`) rather than standing alone. |
| 7 | + |
| 8 | +## Implementation Details |
| 9 | + |
| 10 | +### 1. Subplot Generation (`src/easyidp/geotools.py`) |
| 11 | +- **Function**: `generate_subplots(boundary, ...)` |
| 12 | +- **Logic**: |
| 13 | + - Calculates Minimum Area Rectangle (MAR) to align grid with field orientation. |
| 14 | + - Generates grid cells based on `row_num`/`col_num` OR `width`/`height`. |
| 15 | + - Classifies subplots as `inside`, `touch`, or `outside` relative to the boundary. |
| 16 | + - Filters results based on `keep` parameter (`"all"`, `"touch"`, `"inside"`). |
| 17 | +- **Refactoring**: |
| 18 | + - Initially created in `subplot.py`, then merged into `geotools.py` to consolidate geometric utilities. |
| 19 | + - Deleted `subplot.py` and removed references from `__init__.py`. |
| 20 | + |
| 21 | +### 2. Visualization (`src/easyidp/visualize.py`) |
| 22 | +- **Function**: `show_subplots(boundary_roi, subplot_roi, ...)` |
| 23 | +- **Features**: |
| 24 | + - Visualizes boundary and subplots on a matplotlib axis. |
| 25 | + - Color-codes subplots by status: |
| 26 | + - **Green**: Inside |
| 27 | + - **Orange**: Touch |
| 28 | + - **Red**: Outside |
| 29 | + - Supports saving to file via `save_as`. |
| 30 | + |
| 31 | +### 3. File Saving (`src/easyidp/shp.py` & `src/easyidp/roi.py`) |
| 32 | +- **Refactoring**: |
| 33 | + - Extracted Shapefile writing logic from `ROI` class to a standalone function `write_shp` in `src/easyidp/shp.py`. |
| 34 | + - Updated `ROI.save_shp` to delegate to `idp.shp.write_shp`. |
| 35 | + - Added generic `ROI.save()` method. |
| 36 | +- **Features**: Does not just save geometry but also subplot metadata (`row`, `col`, `status`) to the Shapefile attributes (`.dbf`). |
| 37 | + |
| 38 | +## API Usage |
25 | 39 |
|
26 | 40 | ```python |
27 | 41 | import easyidp as idp |
28 | 42 |
|
29 | | -# Load boundary |
| 43 | +# 1. Load boundary |
30 | 44 | boundary = idp.ROI("field_boundary.shp") |
31 | 45 |
|
32 | | -# Generate by grid (row x col) |
33 | | -subplots = idp.generate_subplots( |
34 | | - boundary, row_num=4, col_num=6, |
35 | | - x_interval=0.5, y_interval=0.5, |
36 | | - keep="touch" # "all" | "touch" | "inside" |
| 46 | +# 2. Generate Subplots |
| 47 | +# Option A: By Grid (e.g., 4 rows x 6 cols) |
| 48 | +subplots = idp.geotools.generate_subplots( |
| 49 | + boundary, |
| 50 | + row_num=4, |
| 51 | + col_num=6, |
| 52 | + x_interval=0.5, |
| 53 | + y_interval=0.5, |
| 54 | + keep="touch" # Options: "all" | "touch" | "inside" |
37 | 55 | ) |
38 | 56 |
|
39 | | -# Or generate by size (width x height in meters) |
40 | | -subplots = idp.generate_subplots( |
41 | | - boundary, width=2.0, height=3.0, |
42 | | - x_interval=0.3, y_interval=0.3 |
| 57 | +# Option B: By Size (e.g., 2m x 3m plots) |
| 58 | +subplots_size = idp.geotools.generate_subplots( |
| 59 | + boundary, |
| 60 | + width=2.0, |
| 61 | + height=3.0 |
43 | 62 | ) |
44 | 63 |
|
45 | | -# Visualize |
46 | | -idp.visualize.show_subplots(boundary, subplots) |
| 64 | +# 3. Visualize |
| 65 | +idp.visualize.show_subplots( |
| 66 | + boundary, |
| 67 | + subplots, |
| 68 | + title="Field Subplots", |
| 69 | + save_as="subplots_vis.png" |
| 70 | +) |
47 | 71 |
|
48 | | -# Save to shapefile |
49 | | -subplots.save_shp("output_subplots.shp") |
| 72 | +# 4. Save to Shapefile |
| 73 | +subplots.save("output_subplots.shp") |
| 74 | +# Or: subplots.save_shp("output_subplots.shp") |
50 | 75 | ``` |
51 | 76 |
|
52 | | -### Key Features |
53 | | - |
54 | | -- **Dual input modes**: Grid (row_num/col_num) or size (width/height) |
55 | | -- **MAR-based orientation**: Automatically aligns to field direction |
56 | | -- **Keep mode filtering**: `"all"`, `"touch"`, `"inside"` for non-rectangular boundaries |
57 | | -- **Status tracking**: Each subplot has `inside`/`touch`/`outside` status |
58 | | -- **Visualization**: Color-coded by status (green=inside, orange=touch, red=outside) |
59 | | - |
60 | | -### Test Results |
61 | | - |
62 | | -``` |
63 | | -tests/test_subplot.py: 19 passed ✓ |
64 | | -``` |
| 77 | +## Testing |
| 78 | + |
| 79 | +### Unit Tests |
| 80 | +- **Geotools Tests**: `tests/test_geotools.py` (Migrated from `test_subplot.py`) |
| 81 | + - Validates grid generation, naming conventions (`R1C1`), size calculations, and filtering logic. |
| 82 | + - 20 tests passed. |
| 83 | +- **Visualization Tests**: `tests/test_visualize.py` (`TestShowSubplots` class) |
| 84 | + - Generates sample images to verify rendering of different `keep` modes. |
| 85 | + - 4 tests passed. |
| 86 | + |
| 87 | +### Visual Verification |
| 88 | +Generated test outputs in `tests/out/visual_test/`: |
| 89 | +- `rect_grid.png`: Standard grid on rectangular boundary. |
| 90 | +- `l_shape_keep_all.png`: L-shaped boundary showing all MAR subplots. |
| 91 | +- `l_shape_keep_touch.png`: Filtered to exclude fully distinct subplots. |
| 92 | +- `l_shape_keep_inside.png`: Filtered to include only fully contained subplots. |
| 93 | + |
| 94 | +## Files Modified |
| 95 | +| File | Status | Description | |
| 96 | +|------|--------|-------------| |
| 97 | +| `src/easyidp/geotools.py` | Modified | Added `generate_subplots` and helpers | |
| 98 | +| `src/easyidp/shp.py` | Modified | Added `write_shp` function | |
| 99 | +| `src/easyidp/roi.py` | Modified | Refactored `save_shp` and added `save` | |
| 100 | +| `src/easyidp/visualize.py` | Modified | Added `show_subplots` | |
| 101 | +| `src/easyidp/__init__.py` | Modified | Removed old `subplot` references | |
| 102 | +| `tests/test_geotools.py` | Created | New home for subplot logic tests | |
| 103 | +| `tests/test_visualize.py` | Modified | Added visualization tests | |
| 104 | +| `src/easyidp/subplot.py` | Deleted | Merged into geotools | |
| 105 | +| `tests/test_subplot.py` | Deleted | Merged into test_geotools | |
0 commit comments