Metadata-Version: 2.4
Name: molspotter
Version: 1.0.0
Summary: Identify silly molecules
Author-email: "Olivier J. M. Béquignon" <olivier.bequignon.maintainer@gmail.com>
Maintainer-email: "Olivier J. M. Béquignon" <olivier.bequignon.maintainer@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/OlivierBeq/molspotter
Project-URL: Repository, https://github.com/OlivierBeq/molspotter
Keywords: cheminformatics
Classifier: Development Status :: 5 - Production/Stable
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: rdkit
Requires-Dist: tqdm
Requires-Dist: click
Provides-Extra: testing
Requires-Dist: pytest; extra == "testing"
Dynamic: license-file

# 🔎 molspotter

<!-- Badges -->
<div align="center">

[![PyPI version](https://img.shields.io/pypi/v/molspotter.svg)](https://pypi.org/project/molspotter/)
[![Supported Python versions](https://img.shields.io/pypi/pyversions/molspotter.svg)](https://pypi.org/project/molspotter/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Tests](https://github.com/OlivierBeq/molspotter/actions/workflows/ci.yml/badge.svg)](https://github.com/OlivierBeq/molspotter/actions/workflows/ci.yml)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
<br>
</div>

Identify **silly molecules** — molecules containing substructures (Morgan fingerprint bits) that were never observed in a reference set of "sound" molecules. `molspotter` handles loading pretrained spotters, scoring and visualizing silly bits, and building your own spotters from any molecular file format.

## ✨ Features

- 🎯 **Silliness scoring** — any Morgan fingerprint bit of a query molecule absent from the reference set counts as "silly"; get a binary flag or a continuous fraction of silly bits.
- 📦 **Three ready-to-use pretrained spotters** — trained on [ChEMBL](https://doi.org/10.1093/nar/gky1075) (v32), [ExCAPE-DB](https://doi.org/10.1186/s13321-017-0203-5), and [Papyrus](https://doi.org/10.1186/s13321-022-00672-x) (v05.6), loadable by name.
- 🧪 **Build your own spotter** — grow a reference set from a SMILES string, an RDKit molecule (or list thereof), or an entire file.
- 📁 **Multi-format streaming I/O** — SMILES, SDF, MOL, MOL2 and Maestro files, transparently reading `.gz`/`.bz2`/`.xz`-compressed inputs without loading everything into memory.
- 🖼️ **Visualize silly bits** — highlight the exact atoms/bonds responsible for a molecule's silly score.
- 💾 **Portable spotter files** — save and reload spotters as compact, LZMA-compressed JSON files.
- 💻 **Command-line interface** — create and score spotters directly from the shell.

## ✍️ Disclaimer

This repository is based on the awesome [`silly_walks`](https://github.com/PatWalters/silly_walks) work of Patrick Walters.

## 📦 Installation

```bash
pip install molspotter
```

Or from source:

```bash
git clone https://github.com/OlivierBeq/molspotter.git
pip install ./molspotter
```

## 🛠️ Requirements

- Python 3.11+
- [RDKit](https://www.rdkit.org/docs/Install.html)

## 💡 Usage

### Quickstart

```python
from molspotter import SillyMolSpotter
from rdkit import Chem

sms = SillyMolSpotter.from_pretrained('chembl')

mol = Chem.MolFromSmiles('C=C=C1CN(C)CCN1Cc5ccc(C(=O)Nc4ccc(C)c(Nc3nccc(c2cccnc2)n3)c4)cc5')
sms.score_mol(mol)
# 1
```

### Scoring molecules

A molecule is scored against the bits of Morgan fingerprint observed in the spotter's reference set. Two flavors of score are available:

| Score        | Call                            | Range        | Meaning                                                        |
|--------------|----------------------------------|--------------|------------------------------------------------------------------|
| Binary       | `sms.score_mol(mol)`             | `{0, 1}`     | `1` if *any* bit of the molecule is silly, `0` otherwise.        |
| Continuous   | `sms.score_mol(mol, binary=False)` | `[0.0, 1.0]` | Fraction of silly bits: $score = \frac{n_{silly\ bits}}{n_{on\ bits}}$ |

```python
sms.score_mol(mol, binary=False)
# 0.023809523809523808
```

A continuous score of `0.0` means the molecule contains no silly bit at all, while `1.0` means every bit of the molecule is silly.

### Visualizing silly bits

Highlight the substructures responsible for a molecule's score:

```python
img = sms.show_mol(mol)
```

![Silly bits highlighted in the molecular structure](images/silly_bits.png)

### Building a custom spotter

Instantiate an empty spotter and grow its reference set from a file, a single molecule, or a SMILES string:

```python
sms = SillyMolSpotter(fp_radius=2)  # radius of the Morgan fingerprint

sms.add_file('PATH_TO_MOLECULAR_FILE')
sms.add_mol(mol)
sms.add_smiles('c1ccccc1')
```

### Saving and loading spotters

Spotters are persisted as compact, LZMA-compressed files:

```python
sms.save('PATH_TO_SAVE_SPOTTER_TO')

sms2 = SillyMolSpotter.from_file('PATH_TO_SAVED_SPOTTER')
```

### Command-line interface

#### Create a spotter

```bash
molspotter create -o examples/chembl_drugs.sp -i examples/chembl_drugs.sd
```

| Option           | Required | Description                                             |
|------------------|:--------:|-----------------------------------------------------------|
| `-o, --outfile`  | ✅        | Path to save the spotter to.                              |
| `-r, --radius`   |          | Radius of the Morgan fingerprint (default: `1`).           |
| `-i, --input`    | ✅        | Input file(s) to parse and add to the spotter. Repeatable. |

#### Score with a spotter

```bash
molspotter score -s chembl -i examples/chembl_drugs.sd
molspotter score -s examples/chembl_drugs.sp -i examples/chembl_drugs.sd -o scores.txt
```

| Option           | Required | Description                                                              |
|------------------|:--------:|-----------------------------------------------------------------------------|
| `-s, --spotter`  | ✅        | Path to a saved spotter, or one of `chembl`, `excape`, `papyrus`.           |
| `-i, --input`    | ✅        | Input file(s) containing molecules to score. Repeatable.                    |
| `-o, --output`   |          | File to write scores to, one per line. Prints to stdout if omitted.         |

## 📄 License

This project is licensed under the MIT License - see the [LICENSE](https://github.com/OlivierBeq/molspotter/blob/main/LICENSE) file for details.

## 📚 API Documentation

### `SillyMolSpotter`

```python
SillyMolSpotter(fp_radius)
```

Spotter identifying silly molecules from the Morgan fingerprint bits of a reference set.

**Parameters**

- ***fp_radius  : int***
  Radius of the Morgan fingerprinter used to identify silly bits.

**Class methods**

| Method                    | Description                                                                          |
|---------------------------|---------------------------------------------------------------------------------------|
| `from_pretrained(name)`    | Load one of the bundled pretrained spotters: `'chembl'`, `'excape'` or `'papyrus'` (case-insensitive). |
| `from_file(filepath)`      | Load a spotter previously saved with `save`.                                          |

**Instance methods**

| Method                                                                                          | Description                                                                          |
|--------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|
| `add_mol(mol)`                                                                                   | Add an RDKit `Chem.Mol`, or list thereof, to the reference set.                        |
| `add_smiles(smiles)`                                                                             | Add a SMILES string, or list thereof, to the reference set.                            |
| `add_file(filepath)`                                                                             | Parse a molecular file and add its content to the reference set.                       |
| `score_mol(mol, binary=True)`                                                                    | Score an RDKit `Chem.Mol`, or list thereof (see [Scoring molecules](#scoring-molecules)). |
| `show_mol(mol, raise_error=False, molsPerRow=3, subImgSize=(500, 300), useSVG=False, returnPNG=False, **kwargs)` | Render the molecule with its silly bits highlighted.                                   |
| `save(filepath)` (alias: `to_file`)                                                              | Save the spotter to disk as an LZMA-compressed file.                                   |

### `MolSupplier`

```python
MolSupplier(source=None, supplier=None, format=None, compression=None, **kwargs)
```

Iterable, context-managed molecular reader handling multiple formats and compressions, with an optional `tqdm` progress bar.

**Parameters**

- ***source  : str | file-like***
  Filename or file-like object. Format and compression are auto-detected from the filename
  unless a file-like object is supplied, in which case `format` must be provided.
- ***supplier  : Iterable[Chem.Mol]***
  An existing molecular supplier (e.g. `rdkit.Chem.ForwardSDMolSupplier`) to wrap directly.
- ***format  : str***
  One of `'smi'`, `'mae'`, `'sd'`, `'mol2'`, `'mol'`.
- ***compression  : str***
  One of `'lzma'`, `'zlib'`, `'bz2'`.
- ***kwargs***
  Forwarded to the underlying RDKit supplier; may also include `total` (expected molecule
  count) and `show_progress` (bool) to control the progress bar shown while iterating.
