Metadata-Version: 2.4
Name: OBPAnalyser
Version: 0.0.14
Summary: A package for analysation of open beam files (OBP and OBF).
Author-email: Anton Wiberg <wiberg.anton@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/wiberganton/OBAnalyser
Project-URL: Repository, https://github.com/wiberganton/OBAnalyser
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: obplib
Requires-Dist: numpy
Requires-Dist: opencv-python
Requires-Dist: py3mf_slicer
Requires-Dist: matplotlib
Requires-Dist: scipy
Requires-Dist: pandas
Requires-Dist: pyyaml
Requires-Dist: tqdm
Dynamic: license-file

# OBAnalyser
OBAnalyser is a Python package for analysing open beam build data, with support for OBF and OBP workflows.

It can be used to:
- extract build-level information such as layer timing, recoating data, and energy-related summaries
- analyse OBP geometry and melt area information
- rasterize full builds into per-layer image stacks
- export JSON outputs that can be used in downstream processing pipelines

## Installation

Install from PyPI:
```
pip install OBPAnalyser
```

For local development in a virtual environment:
```
python -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txt
```

## Minimum example

```
import obanalyser.analyse_build as analyse_build
import obanalyser.plotters.plot_build_data as plot_build_data

path = r"tests\input\cubes_test\buildInfo.json"
build = analyse_build.analyse_build(path)
build.to_json(r"tests\output\build_info.json")

plot_build_data.plot_build_data(build)
```

This reads the build structure, analyses the layer content, writes a JSON summary, and plots the build data.

## Export layer images

The package writes one grayscale PNG per layer directly into the output folder,
named `layer_0000.png`, `layer_0001.png`, and so on:

- Black (0): melted regions
- Gray (190): sintered-only regions
- Light gray (225): unscanned powder inside the build cylinder, when a radius is set
- White (255): outside the cylinder (or empty space when no radius is set)

Melted regions stay black where scans overlap, regardless of scan order.
No per-file images, subfolders, or metadata sidecars are generated.
Each PNG embeds its pixel size, image dimensions in pixels and millimeters,
physical bounds, zero-based layer index, Z step, cumulative Z height, and cylinder
radius when supplied. Standard PNG physical
resolution is also stored for image software that supports it.

```python
from obanalyser.analyse_obf_geometry import write_obf_image_stack

build_path = r"tests\input\cubes_test\buildInfo.json"
output_dir = r"tests\output\layer_image_stack"

write_obf_image_stack(
    build_path,
    output_dir,
    pixel_um=200,
    close_gap_um=50,
    sinter_close_gap_um="auto",
    cylinder_radius_mm=50,
)
```

`cylinder_radius_mm` is optional and centers the cylinder at `(0, 0)` in build
coordinates. The square image covers its diameter, rounded up to whole pixels;
scans outside the circle are clipped. For example, a 50 mm radius with 200 µm
pixels produces a 500 × 500 image covering 100 × 100 mm. Omitting the radius
fits the image to the scanned regions as before.

Z context comes from `layerFeed.buildPistonDistance` in the build JSON, honoring
layer defaults and per-layer overrides (or `build.layerfeed.build_piston_distance`
in YAML). `z_step_mm` is the negative of the signed piston movement;
`z_height_mm` accumulates these steps, with Z=0 before the first layer feed.
For example, piston movements of -0.1 mm produce layer heights of 0.1, 0.2,
0.3 mm, etc. Zero steps and movements in the opposite direction retain their
sign. These are planned relative build heights from the input, rather than
measured absolute machine coordinates. Missing or invalid movement data stops
export with an error instead of assigning an assumed height.

Sintered regions use automatic gap closing by default. The estimate uses the
typical spacing between parallel preheat tracks (including rotated and reversed
tracks), or nearest-neighbor spacing with a diagonal allowance for point scans,
and the beam focus size.
It includes a pixel-rounding allowance and caps the inferred spacing at four
focus diameters to limit bridging between separate regions. Patterns with too
few neighbors for an estimate retain their original coverage. This is a
geometric representation of the sintered footprint, without an energy or
thermal model.

Set `sinter_close_gap_um` to a number for manual control, `0` to disable sinter
closing, or `None` to reuse `close_gap_um`. `close_gap_um` continues to control
melt closing independently. Automatic closing can fill intentional gaps similar
in size to the hatch spacing; use the manual override for those builds.

To read the embedded information with Pillow:

```python
import json
from PIL import Image

with Image.open(r"tests\output\layer_image_stack\layer_0000.png") as image:
    info = json.loads(image.info["obanalyser"])
    print(info["pixel_size_um"], info["image_size_px"], info["image_size_mm"])
    print(info["layer_index"], info["z_step_mm"], info["z_height_mm"])
```

## Example scripts

The repository includes runnable examples in the `docs` folder:
- `docs/examples.py`
- `docs/example_create_image_stack.py`

## Notes

- Spatial raster resolution for image stacks is controlled with `pixel_um`.
- Melt gap closing is controlled with `close_gap_um`; sinter gap closing uses `sinter_close_gap_um` (default `"auto"`).
- `write_obf_image_stack` returns the image stack after saving the PNG files.
- `analyse_obf_geom` returns a single-channel `uint8` stack with shape `[layer, row, col]`; part colors and scan order do not affect pixel values.
