iMeasure 3D API
The iMeasure 3D API provides two segmentation modes to support different annotation workflows:
Static Segmentation: Designed for segmentation tasks where a fixed set of clicks is provided. All clicks are treated equally, making this mode ideal for simple, single-step object segmentation.
Interactive Segmentation: Enables iterative, user-guided segmentation. Clicks are categorized as “positive” (add) or “negative” (remove), allowing users to refine results step-by-step. This mode supports advanced annotation scenarios, such as interactive correction and progressive mask improvement.
Both modes return the same response format, returning a segmentation mask and measurements. The main difference is in how you structure the request and the flexibility of the workflow.
Use static mode for creating quick segmentation masks.
Use interactive mode for annotation tools, review workflows, or any scenario where the user may want to iteratively improve the segmentation.
iMeasure Static
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;
clicks: [Point3, 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;
}
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 Z-fastest (Fortran-style) 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
}
}
}
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('/v1/imeasure-static-3d', {
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;
mask: string;
mask_shape: [number, number, number];
mask_origin_voxel: Point3;
mask_spacing: Point3;
measurements: Measurements;
volume: number;
volume_mm3: number;
}
Example
{
"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
Interactive segmentation, allowing iterative refinement via adding clicks. Clicks are separated into positive and negative; previous "mask" can be used to refine segmentation. "Positive" click means that you want this area to be included in the final segmentation - you use that if yor object is under-segmented. "Negative" click does the opposite - they hint to the model that some area should be exluded from the final segmentation.
POST https://api.imeasure.bettermedicine.ai/seg
Request Format
The request must be sent as multipart/form-data in three parts:
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;
finding_id?: string;
mode?: "positive" | "negative";
latest_click?: Point3;
clicks: InteractiveClick[];
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;
}
Example payload
{
"id": "d286c20a-b2b1-40e4-ab46-0654b6fc3917",
"finding_id": "abc123",
"mode": "positive",
"latest_click": [-61.26, -76.11, -403.90],
"clicks": [
{
"mode": "positive",
"points": [[-51.16, -138.12, -403.90], [-61.26, -76.11, -403.90]]
},
{
"mode": "negative",
"points": [[-70.00, -80.00, -400.00]]
}
],
"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)
Reference the Static iMeasure documentation for the expected payload.
Part 3: Mask (Optional, Binary)
When creating the "initial" finding, the "mask" parameter is not required. However for subsequent requests or for dynamic resizing of the mask via positive/negative clicks, the "mask" parameter is required.
- Field name:
mask - Filename:
mask.bin - Content-Type:
application/octet-stream
interface RequestMask {
mask?: Uint8Array
}
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',
});
}