iMeasure 3D API
The iMeasure 3D API supports two request modes, with Interactive Segmentation as the primary integration path.
Interactive Segmentation (primary):
Iterative, user-guided segmentation where clicks are marked as positive (include) or negative (exclude), with optional previous mask input for refinement. This is the recommended mode for most production integrations.
Static Segmentation (legacy compatibility mode): Single-step segmentation based on two extreme points. Still supported and not deprecated, but used less often in modern workflows where iterative correction is required.
Both modes return the same response contract (mask, measurements, volume, etc.); the main difference is request workflow and refinement capability.
If you are building a new integration, start with Interactive.
Integration assumptions (current contract)
- The
POST /segendpoint is fully stateless. Each request must include all required context for that step. - World-space coordinates in this document use the DICOM patient LPS convention.
- Multipart payload compression is not supported. Send raw uncompressed bytes for
image.binandmask.bin.
iMeasure Static (Legacy compatibility mode)
Static mode is fully supported for compatibility and simple two-click flows, but new integrations are encouraged to use Interactive mode.
Segmentation based on 2 initial clicks that roughly indicate longest diameter of the finding on this slice - extreme points.
POST https://api.imeasure.bettermedicine.ai/seg
Request Format
The request must be sent as multipart/form-data in two parts:
Part 1: Metadata (JSON)
- Field name:
metadata - Filename:
metadata.json - Content-Type:
application/json
Schema
type Point3 = [number, number, number];
interface RequestData {
id: string; // UUID. Must be unique and never duplicated.
clicks: Point3[];
world_origin: Point3;
world_shape: Point3;
world_spacing: Point3;
rescale_intercept: number;
rescale_slope: number;
image_orientation_patient: [number, number, number, number, number, number];
crop_boundaries_world: [Point3, Point3];
crop_boundaries_voxel: [Point3, Point3];
crop_boundaries_unclipped_voxel: [Point3, Point3];
crop_cuboid_center_voxel: Point3;
shape: Point3;
world_bounding_image_positions: [Point3, Point3];
image_count: number;
}
Use two extreme points for static segmentation in production workflows. While the schema accepts an array, other point counts are not recommended unless coordinated with model behavior.
Example
{
"id": "d286c20a-b2b1-40e4-ab46-0654b6fc3917",
"clicks": [[-51.16, -138.12, -403.90], [-61.26, -76.11, -403.90]],
"world_origin": [-202.0869140625, -338.0869140625, -783.9],
"world_shape": [512, 512, 154],
"world_spacing": [0.826171875, 0.826171875, 5],
"rescale_intercept": -1024,
"rescale_slope": 1,
"image_orientation_patient": [1, 0, 0, 0, 1, 0],
"crop_boundaries_world": [[-108.73, -159.63, -483.90], [-3.81, -54.71, -328.90]],
"crop_boundaries_voxel": [[113, 216, 60], [240, 343, 91]],
"crop_boundaries_unclipped_voxel": [[113, 216, 60], [240, 343, 91]],
"crop_cuboid_center_voxel": [177, 280, 76],
"shape": [128, 128, 32],
"world_bounding_image_positions": [[-202.09, -338.09, -18.9], [-202.09, -338.09, -783.9]],
"image_count": 154
}
Part 2: Image Data (Binary)
Refer to iMeasure SDK documentation for a library to construct this payload.
- Field name:
image - Filename:
image.bin - Content-Type:
application/octet-stream
The image field contains a raw binary 3D volume cropped from the original medical scan.
Image Volume Details
- Raw binary (not JSON, not Base64, not compressed)
- Flattened in X-fastest (Fortran/column-major) order
- Encoded as Int16, little-endian
- Size must match:
shape[0] * shape[1] * shape[2]
Flattening Loop
for (let z = 0; z < shape[2]; z++) {
for (let y = 0; y < shape[1]; y++) {
for (let x = 0; x < shape[0]; x++) {
// push voxel at (x, y, z) to array
}
}
}
Cropping model
iMeasure operates on a cropped 3D cuboid around user interaction, not the full scan.
Whole scan (world space)
+--------------------------------------+
| selected ROI |
| +------------------+ |
| | cropped cuboid | -> send |
| | (shape X,Y,Z) | |
| +------------------+ |
+--------------------------------------+
Benefits:
- lower payload size and latency
- faster inference
- model focus around the lesion
Trade-offs and things to keep in mind:
- too-tight crops can clip lesion extent
- image, mask, clicks, and crop metadata must refer to the same crop region
- validate byte counts: image elements must equal
shape[0] * shape[1] * shape[2]
Usage Example
const formData = new FormData();
const metadataBlob = new Blob([JSON.stringify(metadata)], {
type: 'application/json'
});
formData.append('metadata', metadataBlob, 'metadata.json');
const imageBlob = new Blob([imageData.buffer], {
type: 'application/octet-stream'
});
formData.append('image', imageBlob, 'image.bin');
const response = await fetch('https://api.imeasure.bettermedicine.ai/seg', {
method: 'POST',
body: formData
});
Success Response (200 OK)
type Point3 = [number, number, number];
interface MeasurementSegment {
coords_world: [Point3, Point3];
length_world: number;
}
interface CrossMeasurement {
short: MeasurementSegment;
long: MeasurementSegment;
}
interface Measurements {
cross: { [sliceIndex: string]: CrossMeasurement };
diameter?: MeasurementSegment;
height?: MeasurementSegment;
}
interface ApiResponse {
id: string;
// Base64 text in JSON. Decode to raw uncompressed binary mask bytes (0/1), reshape to `mask_shape`.
// Flattened in X-fastest (Fortran/column-major) order, same as the input image.
// Many apps then map foreground `1` to a finding-specific `segmentationValue` for storage/rendering.
mask: string;
mask_shape: [number, number, number];
mask_origin_voxel: Point3;
mask_spacing: Point3;
measurements: Measurements;
volume: number;
volume_mm3: number;
}
Example
{
"id": "d286c20a-b2b1-40e4-ab46-0654b6fc3917",
"mask": "AAA...AAA=",
"measurements": {
"cross": {
"2": {
"short": {
"coords_world": [[-82.705, -99.736, -388.9], [-53.789, -88.996, -388.9]],
"length_world": 30.846209
},
"long": {
"coords_world": [[-73.617, -79.908, -388.9], [-54.615, -131.957, -388.9]],
"length_world": 55.408978
}
}
},
"diameter": {
"coords_world": [[-61.225, -107.998, -441.4], [-63.703, -92.301, -361.4]],
"length_world": 81.56315
},
"height": {
"coords_world": [[-63.29, -109.237, -361.4], [-63.29, -109.237, -446.4]],
"length_world": 85
}
},
"volume_mm3": 79784.43,
"volume": 23378,
"mask_shape": [128, 128, 32],
"mask_origin_voxel": [111, 215, 60],
"mask_spacing": [0.82617, 0.82617, 5]
}
iMeasure Interactive (Primary mode)
Interactive segmentation is the recommended mode for production. It supports iterative refinement by combining click history (positive / negative) with optional previous mask input.
Interactive mode also supports MagicPen strokes for contour-based editing.
For each new interactive request, send cumulative finding history plus the latest user input.
POST https://api.imeasure.bettermedicine.ai/seg
Request Format
The request must be sent as multipart/form-data in three parts:
The endpoint is fully stateless in interactive mode as well; each request must include the required current context.
Part 1: Metadata (JSON)
- Field name:
metadata - Filename:
metadata.json - Content-Type:
application/json
Schema
type Point3 = [number, number, number];
interface InteractiveClick {
mode: "positive" | "negative";
points: Point3[];
}
interface InteractiveRequestData {
id: string; // UUID. Must be unique and never duplicated.
finding_id?: string; // UUID of an existing finding when refining.
clicks: InteractiveClick[];
magic_pen_points?: Point3[][]; // Used by the MagicPen edit tool for contour refinement.
world_origin: Point3;
world_shape: Point3;
world_spacing: Point3;
rescale_intercept: number;
rescale_slope: number;
image_orientation_patient: [number, number, number, number, number, number];
crop_boundaries_world: [Point3, Point3];
crop_boundaries_voxel: [Point3, Point3];
crop_boundaries_unclipped_voxel: [Point3, Point3];
crop_cuboid_center_voxel: Point3;
shape: Point3;
world_bounding_image_positions: [Point3, Point3];
image_count: number;
}
Parameter reference
Applies to static and interactive requests unless noted otherwise.
| Field | Meaning and role |
|---|---|
id | Client-generated UUID request identifier used to correlate request/response and downstream finding updates; must be unique and never duplicated. |
finding_id (interactive) | Existing finding UUID to refine; omit for first create when no prior finding exists. |
clicks | User interaction history in world coordinates (DICOM patient LPS); provides model guidance for create/edit. |
clicks[].mode (interactive) | Interaction intent (positive include, negative exclude) used during iterative refinement. |
clicks[].points | One or more world-space points (DICOM patient LPS) for a single interaction step. |
magic_pen_points (interactive) | Contour stroke history from MagicPen in world coordinates (DICOM patient LPS); additional edit guidance. |
world_origin | World-space origin of the source scan volume in DICOM patient LPS; anchors coordinate transforms. |
world_shape | Full source scan dimensions in voxels; defines global image bounds. |
world_spacing | Physical voxel spacing (mm) on each axis; used for scaling and measurements. |
rescale_intercept | DICOM intensity transform intercept (HU conversion input). |
rescale_slope | DICOM intensity transform slope (HU conversion input). |
image_orientation_patient | DICOM orientation vectors mapping image axes to patient/world axes. |
world_bounding_image_positions | World-space anchors for first/last image positions; used to reconstruct scan geometry. |
image_count | Number of source frames/slices in the series. |
crop_boundaries_world | Crop cuboid min/max in world coordinates for the region sent to iMeasure. |
crop_boundaries_voxel | Effective crop min/max in voxel coordinates after clipping to valid image bounds. |
crop_boundaries_unclipped_voxel | Raw crop min/max before clipping; used to derive required padding. |
crop_cuboid_center_voxel | Crop center in voxel coordinates; indicates the ROI center used for inference. |
shape | Cropped payload dimensions (x,y,z); must match binary image element count. |
| Binary part / response field | Meaning and role |
|---|---|
image (image.bin) | Cropped CT volume as raw int16 little-endian bytes in Fortran order; model image input. |
mask request (mask.bin, interactive optional) | Previous-step cropped binary mask (0/1) for iterative refinement; must align with current crop and shape. |
mask response | Base64-encoded raw, uncompressed mask bytes; decode then reshape with mask_shape. |
mask_shape | Output mask dimensions used to reconstruct decoded mask bytes. |
mask_origin_voxel | Output mask origin in voxel coordinates relative to source volume context. |
mask_spacing | Physical spacing of mask voxels in mm. |
measurements | Geometric measurements computed from predicted mask. |
volume | Segmented voxel count. |
volume_mm3 | Physical segmented volume in mm³. |
Interactive history model (important)
clicksshould contain all interactive click groups for this finding (history + latest click group).magic_pen_pointsshould contain all historical MagicPen strokes (and usually the latest stroke) for this finding.maskshould be the previous segmentation mask when editing/refining.- History persistence strategy is integrator-defined: session-only or persisted across sessions.
Create examples
Use the same common metadata fields from the schema above (id, world/crop metadata, shape, etc.).
TwoClick tool (create): exactly two input points
{
"clicks": [
{
"mode": "positive",
"points": [[-51.16, -138.12, -403.90], [-61.26, -76.11, -403.90]]
}
]
}
True Click tool (create): one positive input point
{
"clicks": [
{
"mode": "positive",
"points": [[-51.16, -138.12, -403.90]]
}
]
}
MagicPen tool (create): only MagicPen stroke input
{
"clicks": [],
"magic_pen_points": [
[[-60.0, -100.0, -404.0], [-58.0, -96.0, -404.0], [-56.0, -100.0, -404.0]]
]
}
Edit examples
When editing, send cumulative history and include the new edit input.
True Click tool (edit): create input + new negative point
NEW edit input (negative click) is added to the existing positive click from the create example.
{
"finding_id": "abc123",
"clicks": [
{
"mode": "positive",
"points": [[-51.16, -138.12, -403.90]]
},
{
"mode": "negative",
"points": [[-70.00, -80.00, -400.00]]
}
]
}
MagicPen tool (edit): create stroke + new stroke
NEW edit input (second MagicPen stroke) is added to the existing MagicPen stroke from the create example.
{
"finding_id": "abc123",
"clicks": [],
"magic_pen_points": [
[[-60.0, -100.0, -404.0], [-58.0, -96.0, -404.0], [-56.0, -100.0, -404.0]],
[[-54.0, -101.0, -404.0], [-52.0, -98.0, -404.0], [-50.0, -101.0, -404.0]]
]
}
Part 2: Image Data (Binary)
Use the same binary image requirements as above (image.bin, Int16 little-endian, z-fastest order, element count must match shape).
Part 3: Mask (Optional, Binary)
When creating the initial finding, the mask parameter is not required. For subsequent refinement requests, sending the previous mask is strongly recommended.
Compression is not supported for mask.bin; send raw uncompressed bytes.
Reliability note for integrators: constructing mask from raw CT DICOM intensities alone is not reliable. mask should come from your current segmentation state for the finding, then be cropped to the same crop region and shape as image.
- Field name:
mask - Filename:
mask.bin - Content-Type:
application/octet-stream
interface RequestMask {
mask?: Uint8Array
}
Recommended generic construction flow:
- start from your existing segmentation volume/label state
- crop it using the same crop boundaries used for
image - binarize the current finding (
0background,1foreground) - send raw bytes as
mask.bin(application/octet-stream)
Operational storage pattern (recommended):
- API contract is binary: send
mask.binas0/1, and interpret response mask as0/1. - For viewer/editor workflows, store segmentation as
0+ finding-specificsegmentationValue. - On request: map stored
segmentationValueback to binary (0/1) formask.bin. - On response: map binary foreground (
1) back to the findingsegmentationValuebefore storing/updating segmentation state.
Here's an example of how we construct the mask payload object in OHIF-3:
const currentFindingSegmentationValue = 1;
// Refer to the provided @bettermedicine/imeasure sdk for the functionality
const croppedSegVolume = cropFromVTKVolumeByCropBoundaries(
segVolume,
cropRegion
);
// check if the cropped segmentation volume contains the segmentation value
const uniqueValuesInCroppedVolume = new Set(
croppedSegVolume.voxelValues
);
// Create the maskBlob that will be sent along with the request data.
if (
uniqueValuesInCroppedVolume.has(currentFindingSegmentationValue)
) {
const mask = croppedSegVolume.voxelValues.map((v) => v === currentFindingSegmentationValue ? 1 : 0);
const maskBlob = new Blob([mask.buffer], {
type: 'application/octet-stream',
});
}
Error Responses
Error Response Format
{
"error": "Human-readable error message",
"code": "ERROR_CODE",
"details": { }
}
| Field | Type | Required | Description |
|---|---|---|---|
error | string | Yes | Human-readable error message |
code | string | Yes | Machine-readable error code |
details | object | No | Additional context for debugging |
Error Codes
| HTTP | Code | Error Message | Mitigation |
|---|---|---|---|
| 415 | UNSUPPORTED_MEDIA_TYPE | Request must be multipart/form-data | Set Content-Type to multipart/form-data |
| 400 | MISSING_METADATA | Missing metadata file in form data | Include metadata file in request |
| 400 | MISSING_IMAGE | Missing image file in form data | Include image file in request |
| 400 | METADATA_MALFORMED_JSON | Metadata file contains invalid JSON | Validate JSON syntax before sending |
| 422 | VALIDATION_ERROR | Invalid request data | Check details for field-level errors |
| 422 | IMAGE_DATA_SIZE_MISMATCH | Image data size does not match declared shape | Check details for expected vs received bytes |
| 422 | MASK_SHAPE_MISMATCH | Mask shape does not match image shape | Ensure mask and image have same dimensions |
| 422 | CLICKS_OUT_OF_BOUNDS | Click coordinates outside valid bounds | Verify clicks are within crop boundaries |
| 422 | CROP_SIZE_TOO_SMALL | Crop size is below minimum required dimensions (64x64x16) | Increase crop size to meet minimum requirements |
| 422 | MAGIC_PEN_NOT_AXIAL | All points in a magic pen contour must be on the same axial plane (same Z coordinate) | Ensure all points in each contour share the same Z coordinate |
| 422 | EMPTY_SEGMENTATION_OUTPUT | Segmentation produced no output | Adjust click placement or verify image quality |
| 500 | OUTPUT_SHAPE_MISMATCH | Output shape does not match expected shape | Check details for shapes; contact support |
| 500 | INTERNAL_ERROR | An unexpected error occurred | Retry; contact support if persistent |
Example Responses
Validation error (with field-level details):
{
"error": "Invalid request data",
"code": "VALIDATION_ERROR",
"details": {
"clicks": ["At least one click entry is required"],
"world_spacing": ["Missing data for required field."]
}
}
Image size mismatch (with byte counts):
{
"error": "Image data size does not match declared shape",
"code": "IMAGE_DATA_SIZE_MISMATCH",
"details": {
"expected_bytes": "2097152",
"received_bytes": "1048576"
}
}
Simple error (no details):
{
"error": "Missing metadata file in form data",
"code": "MISSING_METADATA"
}