Skip to main content

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',
});
}