Metadata-Version: 2.4
Name: hypertool
Version: 0.4.2
Summary: Command-line tool for hyperspectral image processing
Author: Daniel Pérez Rodríguez
License-Expression: MIT
Project-URL: Homepage, https://github.com/dannyzimm/hypertool
Project-URL: Repository, https://github.com/dannyzimm/hypertool
Keywords: hyperspectral,imaging,segmentation,classification,napari
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: Image Processing
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Requires-Dist: spectral>=0.23.1
Requires-Dist: napari[all]>=0.4.18
Requires-Dist: numpy>=1.24.0
Requires-Dist: pandas>=2.0.0
Requires-Dist: click>=8.1.0
Requires-Dist: scikit-image>=0.21.0
Requires-Dist: matplotlib>=3.10.8
Requires-Dist: tifffile>=2023.0.0
Provides-Extra: test
Requires-Dist: pytest>=8.0; extra == "test"
Requires-Dist: pytest-cov>=6.0; extra == "test"
Dynamic: license-file

# Hypertool

A command-line tool for visualization and processing of hyperspectral images in ENVI format (HDR/RAW).

## Features

- Interactive visualization of hyperspectral images using napari
- RGB composite generation with automatic or custom band selection
- Multi-band layer visualization
- **Classification workflow**: ROI-based masking and cropping
- **Segmentation workflow**: Pixel-level annotation and paired image+mask cropping
- **Analysis workflow**: Pixel sampling across spectral bands within a mask
- Support for ENVI, NumPy, and TIFF formats

See [docs/architecture.md](docs/architecture.md) for the project architecture,
module relationships, and extension guide.

## Table of Contents
- [Installation](#installation)
  - [Quick Install (Development)](#quick-install-development)
  - [From Git (Production/Pipeline)](#from-git-productionpipeline)
- [Usage](#usage)
  - [View Command](#view-command)
  - [Preview Command](#preview-command)
  - [Sample Command](#sample-command)
- [Workflows](#workflows)
  - [Classification Workflow (ROI-based)](#classification-workflow-roi-based)
  - [Segmentation Workflow (Pixel-level)](#segmentation-workflow-pixel-level)
- [Command Summary](#command-summary)
- [Sample Input Auto-Detection](#sample-input-auto-detection)
- [Crop Command Auto-Detection](#crop-command-auto-detection)
- [CSV Format (Classification Workflow)](#csv-format-classification-workflow)
- [Requirements](#requirements)
- [Documentation](#documentation)
- [Notes](#notes)


## Installation

### Quick Install (Development)

```bash
pip install -e .
```

### From Git (Production/Pipeline)

```bash
# Latest version
pip install git+https://github.com/your-username/hypertool.git

# Specific version
pip install git+https://github.com/your-username/hypertool.git@v2.1.0
```

For detailed installation instructions (private repositories, CI/CD, etc.), see [INSTALL.md](INSTALL.md).

## Usage

### View Command

Visualize hyperspectral images in napari with different display modes.

```bash
# RGB mode with default bands from HDR metadata
python hypertool.py view image.hdr

# RGB mode with custom bands (0-based indices)
python hypertool.py view image.hdr --bands "50,100,150"

# View all bands as separate layers
python hypertool.py view image.hdr --mode all

# View a specific range of bands
python hypertool.py view image.hdr --mode range --start 50 --end 100

# Disable automatic normalization
python hypertool.py view image.hdr --no-normalize
```

### Preview Command

Generate a PNG preview of a hyperspectral image using the default RGB bands
from the HDR metadata.

```bash
# Default bands from HDR metadata
hypertool preview image.hdr

# Custom output path
hypertool preview image.hdr -o thumbnails/sample.png

# Custom band selection (0-based indices)
hypertool preview image.hdr --bands "50,100,150"

# Raw output without normalization
hypertool preview image.hdr --no-normalize
```

**Parameters:**
- `--output/-o`: Output PNG path (default: `<image>_preview.png`)
- `--bands`: Custom RGB band indices as comma-separated 0-based integers
- `--no-normalize`: Disable percentile (2-98) normalization

### Sample Command

Take N pixel samples per spectral band within a mask area. Two strategies are
available: **random** (pick random pixel groups anywhere within the mask) and
**patch** (place N square patches once, then sample within each).

```bash
# Random sampling (default mode)
hypertool sample image.hdr mask.npy --samples-per-layer 20

# Patch sampling with custom patch size
hypertool sample image.hdr mask.npy --mode patch \\
    --samples-per-layer 10 --patch-size 15

# Allow pixel reuse and overlapping patches
hypertool sample image.hdr mask.npy --mode patch \\
    --allow-repeat --allow-overlap --output results.csv

# Use NumPy arrays instead of HDR
hypertool sample image.npy mask.npy --px-per-sample 8
```

**Input formats** (auto-detected by extension):

| Argument | Supported extensions |
|----------|---------------------|
| `IMAGE` | `.hdr` (ENVI), `.npy` (NumPy) |
| `MASK` | `.npy` (NumPy), `.tif` (TIFF) |

**Parameters:**
- `--samples-per-layer`: Number of samples N per spectral band (default: 10)
- `--px-per-sample`: Number of pixels averaged per sample (default: 5)
- `--mode`: Sampling strategy — `random` or `patch` (default: random)
- `--output/-o`: Output CSV path (default: `sample_results.csv`)
- `--allow-repeat`: Allow the same pixel to be reused across samples
- `--patch-size`: Side length of square patch in pixels, patch mode only (default: 20)
- `--allow-overlap`: Allow patches to overlap, patch mode only

**Output:** A CSV table with N rows (one per sample) and one column per
spectral band (`band_0`, `band_1`, ...).

## Workflows

### Classification Workflow (ROI-based)

For image classification tasks where each crop gets a single label.

#### 1. ROI-Mask Command

Create standardized ROI masks using geometric shapes.

```bash
# Create 6 circular masks (manual save)
python hypertool.py roi-mask image.hdr --shape circle --size 35 --num 6

# Create with auto-save on close
python hypertool.py roi-mask image.hdr --shape circle --size 35 --num 6 -o rois.csv

# Other shapes
python hypertool.py roi-mask image.hdr --shape square --size 30 --num 5 -o rois.csv
python hypertool.py roi-mask image.hdr --shape triangle --size 20 --num 3 -o rois.csv
```

**Parameters:**
- `--shape`: Shape type (square, circle, triangle)
- `--size`: Size on 1-100 scale (automatically converted to pixels)
- `--num`: Number of shapes to create (distributed in grid layout)
- `--output/-o`: Optional auto-save path (saves on napari close)

**Steps:**
1. Adjust mask positions in napari
2. **Option A:** Close napari (auto-saves if -o specified)
   **Option B:** Export manually via File > Save Selected Layer(s)
3. Create a `labels.csv` mapping each ROI index to its class

#### 2. Crop Command (ROI mode)

Extract ROI crops from CSV.

```bash
# Crop using ROIs (bounding box only)
python hypertool.py crop image.hdr rois.csv ./output

# Apply shape mask (pixels outside shape = 0)
python hypertool.py crop image.hdr rois.csv ./output --apply-mask

# Custom prefix and NumPy format
python hypertool.py crop image.hdr rois.csv ./output --prefix sample --format numpy
```

### Segmentation Workflow (Pixel-level)

For semantic segmentation tasks where each pixel gets a label.

#### Option A: Geometric Shapes (Recommended for uniform regions)

For regions with consistent geometric shapes (e.g., petri dishes, samples in trays).

**Step 1: Create ROI shapes**
```bash
python hypertool.py roi-mask image.hdr --shape circle --size 35 --num 6 -o rois.csv
```
- Move circles to position in napari
- Close napari (auto-saves to rois.csv)

**Step 2: Convert ROIs to pixel mask**
```bash
python hypertool.py roi-to-pixel-mask image.hdr rois.csv \
    --classes "tipo_A,tipo_B" -o masks.npy --save-metadata
```
- A reference image opens showing numbered ROIs
- Enter ROI numbers for each class interactively
- Background is automatically assigned to everything else
- `--save-metadata` creates `masks.json` with class mapping

**Step 3: Crop image+mask pairs**
```bash
python hypertool.py crop image.hdr masks.npy -o ./output
```

**Result:**
```
output/
├── images/          # Hyperspectral crops
│   ├── crop_0.npy
│   └── ...
└── masks/           # Segmentation masks (0=bg, 1=tipo_A, 2=tipo_B)
    ├── crop_0.npy
    └── ...

masks.json           # Class mapping metadata
```

**Example interactive session:**
```
ROI reference displayed...

tipo_A (label=1) ROI numbers (comma-separated): 0,1,2
✓ tipo_A → ROIs: [0, 1, 2]

tipo_B (label=2) ROI numbers (comma-separated): 3,4,5
✓ tipo_B → ROIs: [3, 4, 5]

Proceed with mask generation? [Y/n]: y
✓ Pixel mask saved to: masks.npy
```

#### Option B: Free Annotation (For irregular shapes)

For complex or irregular regions that can't be represented by geometric shapes.

**Step 1: Create pixel masks manually**
```bash
python hypertool.py pixel-mask image.hdr --classes "background,healthy,diseased"
```
- Opens napari with empty Labels layer
- Use paint/fill tools to annotate regions
- Save via File > Save Selected Layer(s) or use `--output`

**Step 2: Crop image+mask pairs**
```bash
python hypertool.py crop image.hdr masks.npy ./output
```

**Output structure:**
```
output/
├── images/
│   ├── crop_0.npy
│   ├── crop_1.npy
│   └── ...
└── masks/
    ├── crop_0.npy
    ├── crop_1.npy
    └── ...
```

## Command Summary

| Command | Purpose | Workflow |
|---------|---------|----------|
| `view` | Visualize hyperspectral images | Both |
| `preview` | Export PNG preview of an image | Both |
| `sample` | Sample pixels across spectral bands | Analysis |
| `roi-mask` | Create geometric ROI shapes | Classification / Segmentation |
| `roi-to-pixel-mask` | Convert ROI shapes to pixel masks | Segmentation |
| `pixel-mask` | Free-hand pixel annotation | Segmentation |
| `crop` | Extract crops (auto-detects mode) | Both |

## Sample Input Auto-Detection

The `sample` command automatically detects the input format:

- **IMAGE** `.hdr` → ENVI image loaded via spectral
- **IMAGE** `.npy` → NumPy array loaded directly
- **MASK** `.npy` → NumPy mask loaded via NpyMaskIO
- **MASK** `.tif` / `.tiff` → TIFF mask loaded via TifMaskIO

The mask and image must have matching spatial dimensions (H, W).

### Sampling Strategies

**Random mode:**
Picks random pixel groups anywhere within the mask area. Each sample averages
`--px-per-sample` pixels selected from any valid position in the mask. With
`--no-allow-repeat` (default), each pixel is used at most once across all
samples. With `--allow-repeat`, pixels may be reused.

**Patch mode:**
Places `--samples-per-layer` square patches of side `--patch-size` within the
mask. Patch positions are determined once and shared across all spectral bands.
Pixels are then sampled randomly within each patch. `--no-allow-overlap`
(default) guarantees patches do not overlap. `--allow-repeat` controls pixel
reuse within each patch.

## Crop Command Auto-Detection

The `crop` command automatically detects the workflow:

- **CSV input** → Classification workflow (ROI-based)
  - Creates: Individual cropped images
  
- **NPY/TIF input** → Segmentation workflow (pixel-mask)
  - Creates: Paired image + mask crops in separate directories

## CSV Format (Classification Workflow)

The ROI CSV file exported from napari must contain:
- `index`: ROI identifier
- `shape-type`: Type of shape (rectangle, ellipse, polygon)
- `vertex-index`: Vertex number within the shape
- `axis-0`: Y coordinate
- `axis-1`: X coordinate

## Requirements

- Python 3.8+
- spectral
- napari
- numpy
- pandas
- click
- scikit-image
- tifffile (optional, for TIF export)
- matplotlib

## Documentation

- [Architecture & Extension Guide](docs/architecture.md)

## Notes

- Band indices are 0-based in the CLI
- The tool automatically detects BIL/BSQ/BIP interleave formats
- Percentile normalization (2nd-98th) is applied by default for visualization
- Segmentation masks use integer labels: 0=background, 1=class1, 2=class2, etc.
- For segmentation, connected regions are automatically detected and cropped
