gRPCMedical Devices4DMedical

Talking to medical devices over gRPC

April 20, 2024 • 12 min read

What made this hard

  • 1GB+ DICOM files – a single scan is large, and we needed to move it without buffering the whole thing
  • Patient data – HIPAA obligations meant encryption and audit trails weren't optional
  • Low latency – the device and the backend needed to talk in close to real time
  • Mixed platforms – Windows on the device, Linux in the cloud

Where we landed

  • 80% less bandwidth – compared with the REST approach we started from
  • 3,290+ scans a day – moving through the pipeline
  • No dropped scans – chunk checksums and retries cover the network hiccups
  • 50+ clinics – running the same client

The XVD hardware sits in a clinic and produces lung scans. The analysis runs in our cloud backend. So the scan has to get from the box to the cloud, and a single scan is over a gigabyte. We started with REST, because that's what you reach for. A 1GB scan serialised as JSON over HTTP/1.1 is slow, and you can't stream it without bolting something on. We moved to gRPC. Bandwidth dropped about 80%, which I didn't expect to be that large. The rest is the parts that took longer than the protocol switch did.

gRPC vs REST for this job

REST
• 1GB+ JSON payloads
• HTTP overhead
• No streaming
• Polling required
gRPC
• Binary Protocol Buffers
• HTTP/2 multiplexing
• Bidirectional streaming
• Real-time push

The two that mattered for us were the binary wire format and real streaming. We're pushing big files on a schedule from boxes on patchy networks, so those two cover most of what we needed. The rest of the list is nice to have.

The Protocol Buffer schema

You write the schema first, both ends generate off it, and the C# device client and the Go backend stop disagreeing about what a field means. That alone was worth the move. Here's a trimmed-down version of what we used for the imaging data:

// medical_imaging.proto
syntax = "proto3";

package medical.imaging;

// DICOM scan metadata
message ScanMetadata {
  string scan_id = 1;
  string patient_id = 2;
  string device_id = 3;
  int64 timestamp = 4;
  ScanType scan_type = 5;
  PatientInfo patient_info = 6;
  DeviceSettings device_settings = 7;
}

// Patient information (HIPAA compliant)
message PatientInfo {
  string encrypted_patient_id = 1;  // Encrypted for privacy
  int32 age = 2;
  Gender gender = 3;
  repeated string medical_conditions = 4;
}

// XVD device-specific settings
message DeviceSettings {
  float breathing_protocol_duration = 1;  // seconds
  int32 image_resolution_x = 2;
  int32 image_resolution_y = 3;
  float voxel_size = 4;  // mm
  string calibration_data = 5;
}

// Streaming DICOM data chunks
message DicomChunk {
  string scan_id = 1;
  int32 chunk_sequence = 2;
  bytes data = 3;  // Binary DICOM data
  string checksum = 4;  // For integrity verification
  bool is_final_chunk = 5;
}

enum ScanType {
  SCAN_TYPE_UNSPECIFIED = 0;
  FOUR_D_CT = 1;
  INSPIRATION_HOLD = 2;
  EXPIRATION_HOLD = 3;
}

enum Gender {
  GENDER_UNSPECIFIED = 0;
  MALE = 1;
  FEMALE = 2;
  OTHER = 3;
}

A few things that mattered

  • Stream, don't buffer – chunking the scan over a stream keeps memory flat on the device. The box is also running the acquisition, so we couldn't have it holding a gigabyte in RAM to send a gigabyte.
  • Do security first – cert-based auth, encryption in transit, audit logging. We did these on day one because retrofitting them onto a working pipeline is miserable, and I've done that before.
  • Make it observable – health checks and per-stream metrics. When a scan stalls in a clinic in another state, you want to know why without anyone driving out there.
  • Expect bad networks – hospital networks drop. Retries, a circuit breaker, a resume-from-chunk fallback. A connection cutting out mid-scan is normal, not an edge case.

gRPC didn't solve anything on its own. The chunking, the retries, the auth, the metrics — that's where the actual work was, and most of it you'd write whatever protocol you picked. What gRPC gave us was a sane place to put it. For moving big scans off a box on a network you don't control, I'd pick it again.

Share: