chart-direction 1.0.0
Detect UP/DOWN trend direction in financial line-chart screenshots using computer vision.
License: MIT
Requires Python: >=3.8
Keywords: finance, chart, trading, computer-vision, opencv, trend

<h1 align="center">📈 chart-direction</h1>

<p align="center">
  <strong>Detect the trend direction of a financial line-chart screenshot — in one line of Python.</strong>
</p>

<p align="center">
  <a href="https://github.com/MahyudeenShahid/chart-direction/blob/main/LICENSE">
    <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License">
  </a>
  <a href="https://pypi.org/project/chart-direction/">
    <img src="https://img.shields.io/pypi/v/chart-direction.svg" alt="PyPI version">
  </a>
  <a href="https://pypi.org/project/chart-direction/">
    <img src="https://img.shields.io/pypi/pyversions/chart-direction.svg" alt="Python versions">
  </a>
  <a href="https://github.com/MahyudeenShahid/chart-direction/actions">
    <img src="https://img.shields.io/github/actions/workflow/status/MahyudeenShahid/chart-direction/ci.yml?label=CI" alt="CI">
  </a>
</p>

---

## Preview

> **Web UI** — drag-and-drop a chart screenshot, get an instant UP / DOWN signal with full debug pipeline images.

<p align="center">
  <img src="image.jpg" alt="Chart Direction Analyzer — Web UI screenshot" width="900">
</p>

---

## What it does

`chart-direction` takes a **screenshot of any financial line chart** and tells you whether the line at the right-hand end is going **UP** or **DOWN** — no manual cropping, no hardcoded colours, no ML model to install.

```python
from chart_direction import ChartDirectionDetector

detector = ChartDirectionDetector()
print(detector("screenshot.png"))   # "UP"  📈
```

It handles real-world chart challenges:

| Challenge | How it's handled |
|-----------|-----------------|
| Dotted grid lines | Removed via Hough line detection before component analysis |
| Broken/anti-aliased strokes | Reconnected with morphological gap bridging |
| UI panels with large bounding boxes | Rejected using **density scoring** — chart lines are sparse, panels are filled |
| Short visible tail at chart end | 3× zoom + end-extension band search |

---

## Installation

```bash
pip install chart-direction
```

**Requirements:** Python ≥ 3.8, `opencv-python`, `numpy`

Install from source:

```bash
git clone https://github.com/MahyudeenShahid/chart-direction.git
cd chart-direction
pip install -e .
```

---

## Quick Start

### One-liner

```python
from chart_direction import ChartDirectionDetector

detector = ChartDirectionDetector()
print(detector("chart.png"))   # "UP" or "DOWN"
```

### Full result dict

```python
result = detector.analyze_with_details("chart.png")

if result["success"]:
    print(result["direction"])      # "UP"
    print(result["end_dir"])        # +1
    print(result["trend_start_x"])  # 312  (pixel x where last trend began)
    print(result["roi"])            # ROI(x0=780, y0=40, x1=1024, y1=600, w=244, h=560)
```

### Save debug images

```python
result = detector.analyze_with_details("chart.png", outdir="debug/")
```

This generates 11 images showing every step of the pipeline:

```
debug/
├── full_edges_raw.png          Canny edges on full image
├── full_edges_clean.png        After removing horizontal grid lines
├── full_edges_bridged.png      After bridging gaps
├── full_component.png          Selected chart component
├── full_component_dilated.png  Dilated for tracing
├── full_traced.png             Traced path + END marker
├── original_with_roi.png       Original with red ROI box
├── zoom.png                    3× zoomed right-end ROI
├── edges.png                   Edges in zoomed ROI
├── traced.png                  Final trace on zoom + "Direction: UP"
└── edges_traced.png            Trace on edge image
```

### Command-line interface

```bash
chart-direction --image chart.png --outdir debug/
# 📈  Direction: UP
#    Debug images → debug/
```

---

## How It Works

The pipeline has **10 stages**, all tunable via constructor parameters:

```
Input image
 ↓
 1. Crop vertical margins          (removes chart title / footer UI)
 ↓
 2. Canny edge detection           (finds all edges)
 ↓
 3. Remove horizontal artifacts    (erases grid lines via Hough)
 ↓
 4. Bridge gaps                    (reconnects dotted/anti-aliased lines)
 ↓
 5. Component selection            (density-aware scoring picks the chart line)
 ↓
 6. Trace  y(x)  +  extend end     (converts mask → 1-D function)
 ↓
 7. Build zoomed ROI               (frame the last ~28% of the chart)
 ↓
 8. Repeat steps 2-5 on zoom       (sub-pixel accuracy at 3× magnification)
 ↓
 9. Gradient → smooth → classify   (UP / DOWN / FLAT per pixel)
 ↓
10. Find last direction change      → "UP" or "DOWN"
```

### The density trick (v14 fix)

The key insight that makes this work on full-screen charts:

```
density = component_area / (bbox_width × bbox_height)
```

- A **chart line** spanning the whole image: `density ≈ 0.01 – 0.05` (sparse)
- A **UI panel** filling the screen: `density ≈ 0.3 – 1.0` (dense)

Only reject a huge component when **all three hold**:
```
x_span > 95% W  AND  y_span > 80% H  AND  density > 0.18
```

---

## Configuration

```python
detector = ChartDirectionDetector(
    # ── Edge detection ──────────────────────────────
    canny_low            = 30,    # lower Canny threshold
    canny_high           = 120,   # upper Canny threshold

    # ── Horizontal artifact removal ─────────────────
    hough_threshold      = 40,    # Hough votes needed
    hough_max_gap        = 14,    # max gap in line segment
    horizontal_slope_max = 0.08,  # |dy/dx| < this → horizontal

    # ── Component selection ─────────────────────────
    min_component_area   = 160,   # ignore tiny blobs
    density_threshold    = 0.18,  # UI panel detector

    # ── End-extension ───────────────────────────────
    band_half_height     = 22,    # ± pixel search band
    max_end_gap          = 18,    # stop after this many blank columns

    # ── Direction analysis ──────────────────────────
    slope_threshold      = 0.15,  # gradient < this → flat
    smooth_win_trace     = 9,     # smoothing window on y(x)
    smooth_win_grad      = 7,     # smoothing window on dy/dx

    # ── ROI & zoom ──────────────────────────────────
    last_w_frac_graph    = 0.28,  # analyse last 28% of chart
    full_y_margin_frac   = 0.02,  # crop 2% top & bottom
    zoom_factor          = 3.0,   # 3× magnification on ROI
)
```

### Tuning tips

| Goal | Change |
|------|--------|
| More sensitive to shallow trends | Lower `slope_threshold` (e.g. `0.08`) |
| Charts with thin/faint lines | Lower `canny_low` (e.g. `15`) |
| Analyse a wider end section | Increase `last_w_frac_graph` (e.g. `0.40`) |
| Very high-resolution images | Increase `zoom_factor` (e.g. `4.0`) |
| Noisy images with many components | Increase `min_component_area` (e.g. `300`) |

---

## Documentation

| Doc | Description |
|-----|-------------|
| [Quick Start](docs/QUICKSTART.md) | Installation, basic usage, CLI |
| [API Reference](docs/API.md) | All classes, methods, parameters |
| [File Reference](docs/FILES.md) | Every file explained — how it works and how it connects |
| [Changelog](CHANGELOG.md) | Version history |
| [Contributing](CONTRIBUTING.md) | How to contribute |

---

## Contributing

Contributions are very welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) first.

```bash
# Fork → clone → create branch
git checkout -b feat/my-feature

# Install dev dependencies
pip install -e ".[dev]"

# Run tests
pytest tests/ -v

# Format
black chart_direction/
ruff check chart_direction/

# Open a PR 🎉
```

---

## License

[MIT](LICENSE) © 2026 Mahyudeen Shahid

---

<p align="center">
  Made with ❤️ and OpenCV
</p>