Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ build

# other typical case file names
*.vti
*morphology.csv

# vscode settings cache
.vscode
5 changes: 5 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Contributing to Raptor is easy: just open a pull request. Make main the destination branch on the Raptor repository and allow edits from maintainers.

Your pull request must work with all current Raptor tutorial examples and be reviewed by at least one of the main developers.

We use `pre-commit` to fix formatting into a consistent style. You can install `pre-commit` through `pip`.
66 changes: 48 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

---

RAPTOR is a Python-based simulation tool for estimating porosity-related defects in Laser Powder Bed Fusion (LPBF) additive manufacturing processes. It uses a computationally efficient geometric approach to model the dynamic melt pool and identify regions of unmelted material, which correspond to lack-of-fusion pores. The core of RAPTOR is a geometric model of the melt pool cross-section whose dimensions (width, depth, and height) oscillate over time. By analyzing the volume swept by this dynamic melt pool along the laser scan paths, RAPTOR generates a 3D map of the final part's porosity.
Raptor is a Python-based simulation tool for estimating porosity-related defects in Laser Powder Bed Fusion (LPBF) additive manufacturing processes. It uses a computationally efficient geometric approach to model the dynamic melt pool and identify regions of unmelted material, which correspond to lack-of-fusion pores. The core of Raptor is a geometric model of the melt pool cross-section whose dimensions (width, depth, and height) oscillate over time. By analyzing the volume swept by this dynamic melt pool along the laser scan paths, Raptor generates a 3D map of the final part's porosity.

## License

Expand All @@ -17,7 +17,7 @@ This project is licensed under the BSD 3-Clause [License](LICENSE).

## How It Works

**RAPTOR predicts porosity by following a multi-step process:**
**Raptor predicts porosity by following a multi-step process:**

* **Domain Voxelization**: A 3D bounding box, or Representative Volume Element (RVE), is defined and discretized into a uniform grid of voxels.
* **Scan Path Ingestion**: Scan path data is used to calculating the timing and trajectory for each laser vector.
Expand All @@ -26,19 +26,25 @@ This project is licensed under the BSD 3-Clause [License](LICENSE).
* **Porosity Prediction**: Any voxel that is not melted by the end of the simulation is flagged as porosity.
* **Analysis and Output**: The final 3D porosity field is saved in the binary VTK ImageData (`.vti`) format. The morphological characteristics (e.g., volume, surface area, equivalent diameter) of contiguous pore structures can be quantified using the `scikit-image` library, and saved to a `.csv` file.

<figure>
<img src="https://raw.githubusercontent.com/ORNL-MDF/raptor-media/main/images/example_defects.png" alt="example figure">
<figcaption>Stochastic undermelting defects occurring between tracks due to melt pool fluctuations.</figcaption>
</figure>

## Installation

RAPTOR requires requires Python 3 (tested with Python 3.8+). The following Python packages are necessary:
Raptor requires requires Python 3 (tested with Python 3.8+). The following Python packages are necessary:
```bash
numpy, numba, pyyaml, vtk, scikit-image
numpy, numba, pyyaml, vtk, scikit-image, pandas, pyvista
```

* **NumPy**: For numerical operations and array manipulation.

* **Numba**: For JIT compilation and performance acceleration.
* **PyYAML**: For reading and parsing YAML configuration files
* **VTK**: For writing the output porosity map in `.vti` format
* **scikit-image**: For calculating pore morphologies.
* **pandas**: For writing morphology information to .csv
* **pyvista**: For visualization of `.vti` results.

You can install all dependencies and Raptor itself by running ```pip install .``` in the cloned Raptor directory.

Expand All @@ -55,29 +61,29 @@ The project is organized into several modules:
* `io.py`: Contains functions for reading and parsing input files (scan paths, melt pool data).
* `utilities.py`: Includes helper classes, such as the `ScanPathBuilder` for generating scan strategies.

RAPTOR can be used in two primary ways: through its Command-Line Interface (CLI) for quick, configuration-driven simulations, or as a Python Library (API) for integration into custom scripts and more complex workflows.
Raptor can be used in two primary ways: through its Command-Line Interface (CLI) for quick, configuration-driven simulations, or as a Python Library (API) for integration into custom scripts and simulation workflows.

### 1. Command-Line Interface (CLI)

The CLI is the simplest way to run a simulation. It is controlled by a single YAML configuration file that defines all inputs, parameters, and outputs.
The CLI usage requires scan path files corresponding to build information. These scan path files can be generated with the `ScanPathBuilder` class in `raptor.utilities`. The CLI is controlled by a single YAML input file that defines all inputs, parameters, and outputs. The CLI example contains a single scan path to show functionality and observe the fluctuations of the simulated melt pool. The API example is recommended for a more descriptive simulation of undermelting-induced defects.

**How to Run (CLI):**

1. **Prepare Inputs**: Create scan path files, melt pool data files, and a `config.yaml` file (detailed below).
1. **Prepare Inputs**: Create scan path files, melt pool data files, and a `input.yaml` file (detailed below).
2. **Execute Script**: Run the following command from your terminal, providing the path to your configuration file:
```bash
raptor path/to/your/config.yaml
raptor path/to/your/input.yaml
```
3. **Check Outputs**:
* Progress will be printed to the console.
* The 3D porosity map is saved to the `.vti` file specified in the config.
* The pore morphology data is saved to the `.csv` file (if configured).
* The pore morphology data is saved to the `.csv` file (if configured -- the example does not save the morphology information.).

#### CLI Input: The `config.yaml` File
#### CLI Input: The `input.yaml` File

Running RAPTOR from the CLI requires a YAML configuration file to specify all parameters.
Running Raptor from the CLI requires a YAML input file to specify all parameters.

**Example `config.yaml`:**
**Example `input.yaml`:**
```yaml
# List of scan path files (relative to this config file's location)
scan_paths:
Expand Down Expand Up @@ -126,7 +132,6 @@ output:
- "label"
- "area"
- "equivalent_diameter_area"
- "extent"
```

#### Configuration Details:
Expand All @@ -147,13 +152,15 @@ output:
0 0.001 0.0001 0.000 200 0.8
```

* The RVE min and max points *filter the scan paths for those that are near* the box defined by `min_point` and `max_point`; a large number of scan path files (such as from a part-scale build) can be downselected using this parameter setting.

* **Melt Pool Data Files**: These files provide the data for the `melt_pool_data` section of the config.
* If `type: "time_series"`, the file should be a two-column text or CSV file: `[time, value]`.
* If `type: "spectral_components"`, the file should be a three-column text or CSV file: `[amplitude, frequency, phase]`.

### 2. Python Library (API)

For advanced use cases, RAPTOR's core functions can be imported directly into your Python scripts. This allows for programmatic parameter studies, custom workflows, and integration with other tools. An example is provided in examples/api_example/rve.py.
The API allows for programmatic parameter studies, custom workflows, and integration with other tools. The core functionality of Raptor can be called by scripting with the API library. An example is provided in `examples/api_example/rve.py`, which is an RVE simulation of defects in 500µm edge length cube.

The following is a breakdown of the main steps for running a simulation programmatically.

Expand Down Expand Up @@ -181,8 +188,8 @@ from raptor.utilities import ScanPathBuilder
# 2. Create path vectors through the representative volume element (RVE)
power = 370
velocity = 1.7
hatch_spacing = 130e-6
layer_height = 50e-6
hatch_spacing = 140e-6
layer_height = 30e-6
rotation = 67
scan_extension = max(max_point - min_point)
extra_layers = 0
Expand Down Expand Up @@ -252,14 +259,37 @@ porosity = compute_porosity(
```

#### Step 5: Write Results to a VTK File
Finally, use the write_vtk helper function to save the resulting porosity NumPy array to a .vti file for visualization in tools like ParaView.
Use the write_vtk helper function to save the resulting porosity NumPy array to a `.vti` file for visualization in tools like ParaView. Note that this `.vti` will contain 0 for the voxels that are melted, and 1 for unmelted voxels. Paraview's contour feature can be used to isolate the defects within the RVE.

```python
from raptor.api import write_vtk

# 5. Write porosity field to .VTI
write_vtk(grid.origin, grid.resolution, porosity, "rve.vti")
```

#### Step 6: Compute and Write Morphology Descriptors
Optionally use the `compute_morphology` and `write_morphology` functions to compute global descriptors such as volume, equivalent diameter, etc. For a full list of possible descriptors, see https://scikit-image.org/docs/stable/api/skimage.measure.html#skimage.measure.regionprops.

```python
from raptor.api import compute_morphology, write_morphology

# 6. Compute morphology
morphology = compute_morphology(porosity, voxel_resolution, ['area', 'equivalent_diameter_area'])
write_morphology(morphology, "rve_morphology.csv")
```
#### Step 7: Visualize the Output
Optionally use the `visualize` function to open an interactive window via `pyvista`. To perform more advanced visualizations, the output `.vti` file needs to be contoured to isolate the unmelted voxels (value 1) from the melted voxels (value 0). This contouring is automatically performed in `visualize`. The default scaling converts meters to microns for cleaner labeling in the interactive plot, but the scaling argument can be user-assigned.

```python
from raptor.api import visualize

#7. Visualize using PyVista
visualize("./rve.vti")
```
To visualize the example output, uncomment the `visualize("./rve.vti")` line.


## References
The melt pool measurements in the examples are scans performed in Ti6Al4V from the following study:
* Miner, Justin; Narra, Sneha Prabha (2024). Dataset of Melt Pool Variability Measurements for Powder Bed Fusion - Laser Beam of Ti-6Al-4V. Carnegie Mellon University. Dataset. https://doi.org/10.1184/R1/25696293.v1
16 changes: 14 additions & 2 deletions examples/api_example/rve.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
create_grid,
compute_porosity,
write_vtk,
compute_morphology,
write_morphology,
visualize,
)
from raptor.utilities import ScanPathBuilder

Expand All @@ -31,8 +34,8 @@
# 2. Create path vectors through the representative volume element (RVE)
power = 370
velocity = 1.7
hatch_spacing = 130e-6
layer_height = 50e-6
hatch_spacing = 140e-6
Comment thread
colemanjs marked this conversation as resolved.
layer_height = 30e-6
Comment thread
colemanjs marked this conversation as resolved.
rotation = 67
scan_extension = max(max_point - min_point)
extra_layers = 0
Expand Down Expand Up @@ -82,3 +85,12 @@

# 5. Write porosity field to .VTI
write_vtk(grid.origin, grid.resolution, porosity, "rve.vti")

# 6. Compute morphology
morphology = compute_morphology(
porosity, voxel_resolution, ["area", "equivalent_diameter_area"]
)
write_morphology(morphology, "rve_morphology.csv")

# 7. Visualize using PyVista
# visualize("./rve.vti")
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ dependencies = [
"PyYAML",
"vtk",
"scikit-image",
"pytest"
"pytest",
"pandas",
"pyvista"
]

[project.scripts]
Expand Down
43 changes: 36 additions & 7 deletions src/raptor/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
import time
from typing import List, Tuple, Optional, Dict, Any
import numpy as np
import pandas as pd
import vtk
from vtk.util import numpy_support
import pyvista as pv
from skimage import measure
from skimage.morphology import remove_small_objects

Expand Down Expand Up @@ -259,15 +261,42 @@ def write_morphology(properties: dict, morphology_output_path: str) -> None:
"""
Writes morphology output as a .csv.
"""
columns = ",".join([key for key in properties.keys()])

morphology = np.vstack([properties[key] for key in properties.keys()]).transpose()

np.savetxt(
morphology_output_path, morphology, header=columns, delimiter=",", comments=""
)
morphology_df = pd.DataFrame(properties, index=None)
morphology_df.to_csv(morphology_output_path, index=False)

print(
f"Morphology features of {morphology.shape[0]} "
f"Morphology features of {len(morphology_df)} "
f"defects written to: {morphology_output_path}"
)


def visualize(vtk_output_path: str, scaling=1e6) -> None:
"""
Visualizes porosity field using PyVista. Defaults to scaling from meters to microns for better labeling.
"""
rve = pv.read(vtk_output_path)
isosurface = rve.contour(isosurfaces=5)

# Outline of the original domain
outline = rve.outline()

# Set up the plotter
pl = pv.Plotter()
pl.add_mesh(isosurface, color="red", opacity=0.8)
pl.add_mesh(outline, color="black", line_width=1)
label_args = {
"font_size": 12,
"color": "black",
"font_family": "arial",
"fmt": "%.0e",
}
pl.show_grid(
xtitle="X (um)",
ytitle="Y (um)",
ztitle="Z (um)",
grid=False,
location="outer",
**label_args,
)
pl.show()
7 changes: 3 additions & 4 deletions src/raptor/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,11 +201,10 @@ def main() -> int:

# write morphology metrics (optional)
if morphology_fields:
write_morphology
(
compute_morphology(porosity, voxel_resolution, morphology_fields),
morphology_file,
defect_morphologies = compute_morphology(
porosity, voxel_resolution, morphology_fields
)
write_morphology(defect_morphologies, morphology_file)

except FileNotFoundError as e:
print(f"Error: {e}")
Expand Down
30 changes: 26 additions & 4 deletions src/raptor/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,16 @@ def process_vectors(self):
all_vectors.append(vec)
return all_vectors

def write_layers(self, ouput_name):
def write_layers(self, output_name, mode="layers"):
"""
Writes the generated raw scan paths to text files.
"""

Args:
output_name: Base name for the output files.
mode: "layers" to write separate files for each layer, "all" to write a single file with all layers.
"""
if mode == "all":
all_layers = []
for l_key, (l_start, l_end) in self.layers.items():
if l_start.size == 0:
continue
Expand All @@ -181,13 +186,30 @@ def write_layers(self, ouput_name):
for s, e in zip(l_start, l_end)
]

allpaths = np.vstack(se_pairs)
all_paths = np.vstack(se_pairs)
if mode == "all":
all_layers.append(all_paths)
continue
header_str = "Mode X(m) Y(m) Z(m) Power(W) tParam"
filename = f"{output_name}_layer_{l_key}.txt"

np.savetxt(
filename,
allpaths,
all_paths,
fmt="%.6f",
delimiter=" ",
header=header_str,
comments="",
)
print(f"Wrote file {filename}")

if mode == "all" and all_layers:
all_layers = np.vstack(all_layers)
header_str = "Mode X(m) Y(m) Z(m) Power(W) tParam"
filename = f"{output_name}.txt"
np.savetxt(
filename,
all_layers,
fmt="%.6f",
delimiter=" ",
header=header_str,
Expand Down
Loading
Loading