Quickstart¶
This page takes one image from your disk to a pore size distribution. You need an API key for your team, your Team ID, and a segmented 3D image saved as .npy, with False (or 0) marking solid voxels and True (or 1, or 255) marking pore. See Images and uploads for other formats.
Set up¶
export PORESTORE_HOST="https://porestore.com"
export PORESTORE_TEAM_ID="7k3mf8q2" # Team settings → Team ID
export PORESTORE_API_KEY="AbCd1234.…" # shown once, when the key is created
pip install requests
The whole loop¶
"""Upload a 3D image to PoreStore, run a pore size distribution on it, and print the result."""
import os
import time
import uuid
import requests
HOST = os.environ["PORESTORE_HOST"]
TEAM = os.environ["PORESTORE_TEAM_ID"]
API = f"{HOST}/api/v1/teams/{TEAM}"
session = requests.Session()
session.headers["Authorization"] = f"Api-Key {os.environ['PORESTORE_API_KEY']}"
def poll(url, statuses, interval=5):
"""GET url until its status is one of statuses, then return the body."""
while True:
body = session.get(url).json()
if body["status"] in statuses:
return body
time.sleep(interval)
# 1. Ask for an upload target, then send the file straight to object storage.
filename = "berea.npy"
meta = {"filename": filename, "name": "Berea sandstone", "voxel_size": 5.345} # voxel_size in µm
target = session.post(f"{API}/uploads/", json=meta).json()
with open(filename, "rb") as f:
upload = requests.post(target["url"], data=target["fields"], files={"file": (filename, f)})
upload.raise_for_status() # object storage answers 204 No Content
# 2. Register the upload and wait for ingestion.
image = session.post(f"{API}/uploads/{target['id']}/complete/", json=meta).json()
image = poll(f"{API}/images/{image['id']}/", {"ready", "failed"})
if image["status"] == "failed":
raise SystemExit(f"Ingestion failed: {image['processing_error']}")
print("Image ready:", image["dimensions"], "porosity", image["metrics"].get("porosity"))
# 3. Submit the job, with an idempotency key so a retry can't submit it twice.
request = {"image_id": image["id"], "analysis_type": "poresize", "parameters": {"steps": 25, "queue_name": "porespy"}}
job = session.post(f"{API}/analysis-jobs/", json=request, headers={"Idempotency-Key": str(uuid.uuid4())}).json()
# 4. Wait for the job, then read its result.
job = poll(f"{API}/analysis-jobs/{job['id']}/", {"completed", "failed", "cancelled"})
if job["status"] != "completed":
raise SystemExit(f"Job {job['status']}: {job['error_message']}")
result = session.get(f"{API}/analysis-jobs/{job['id']}/result/").json()
solution = result["metrics"]["solution"]
for radius, pdf in zip(solution["R"], solution["pdf"]):
print(f"{radius:8.2f} {pdf:.4f}")
What each step does:
- Upload.
POST /uploads/returns a presigned form for object storage. The file goes there directly and never passes through PoreStore's web server, so its size is limited by storage (4 GiB), not by a request timeout. Send every returned field unchanged, then the file. See Images and uploads. - Ingest.
POST /uploads/{id}/complete/creates the image and starts ingestion. Complete the upload within 6 days of sending the file. Poll untilstatusleavesprocessing. - Submit. Send every parameter the analysis marks as required, including
queue_name, even when the schema shows a default: the default is what the app pre-fills, and the API doesn't fill it in for you. TheIdempotency-Keymakes a retried request return the same job instead of a second one. See Analysis jobs. - Read the result. The pore size distribution comes back as radii
Rwith theirpdfandcdf. With a voxel size set,Ris in µm. See Results for every analysis's keys.
Run something heavier¶
Permeability, diffusivity and conductivity run on GPU solvers and take a backend and a compute tier. Find the current choices with GET /analysis-types/, then submit the same way:
request = {
"image_id": image["id"],
"analysis_type": "permeability",
"parameters": {
"direction": "x",
"queue_name": "walberla",
"compute_tier": "auto",
"max_iterations": 20000,
"velocity_tolerance": 0.001,
"permeability_tolerance": 0.001,
},
}
"auto" lets PoreStore pick the hardware. See Compute tiers for the tiers you can name instead.
Before you choose a backend, read Permeability in the Scientific reference. The backends don't all set the problem up the same way, and the boundary conditions on the faces parallel to the flow are different.
Same thing with curl¶
curl --fail-with-body -sS -X POST "$PORESTORE_HOST/api/v1/teams/$PORESTORE_TEAM_ID/uploads/" \
-H "Authorization: Api-Key $PORESTORE_API_KEY" -H 'Content-Type: application/json' \
--data '{"filename":"berea.npy","name":"Berea sandstone","voxel_size":5.345}'
# POST the file to the returned "url": every "fields" entry as -F key=value, then -F file=@berea.npy
curl --fail-with-body -sS -X POST "$PORESTORE_HOST/api/v1/teams/$PORESTORE_TEAM_ID/analysis-jobs/" \
-H "Authorization: Api-Key $PORESTORE_API_KEY" -H 'Content-Type: application/json' \
-H "Idempotency-Key: $(uuidgen)" \
--data "{\"image_id\":\"$IMAGE_ID\",\"analysis_type\":\"poresize\",\"parameters\":{\"steps\":25,\"queue_name\":\"porespy\"}}"