Spaces:
Sleeping
IndiDermaX β Android Integration Guide
Quick Start
Base URL: https://avishek8136-indidermax.hf.space
No auth keys needed β HF Space is public.
Warm-up: call GET /api/health first (cold start takes 20-30s).
Test with curl before writing Android code
curl https://avishek8136-indidermax.hf.space/api/health
β οΈ Cold Start Warning
HuggingFace Spaces sleep after ~15 minutes of inactivity. The first request after sleep takes 20-30 seconds while the container starts.
Fix: Ping /api/health in Application.onCreate() as a warm-up. Show a spinner until it responds.
class IndiDermaXApp : Application() {
override fun onCreate() {
super.onCreate()
CoroutineScope(Dispatchers.IO).launch {
try {
val response = api.healthCheck()
Log.i("IndiDermaX", "Warmed up: mode=${response.mode}")
} catch (e: Exception) {
Log.w("IndiDermaX", "Warm-up failed: ${e.message}")
}
}
}
}
Set Android network timeout to 60 seconds (OkHttp/Retrofit default is 10s β too short).
API Endpoints
1. Health Check
GET /api/health
Response:
{
"status": "healthy",
"mode": "neo4j_live",
"neo4j": true,
"nvidia": true,
"timestamp": "2026-05-11T00:00:00Z"
}
mode values: "neo4j_live" = full production | "cache_fallback" = Neo4j down, using local cache | "kb_only" = degraded
2. Diagnose (JSON β recommended for Android)
Send text symptoms + optional base64 image.
POST /api/diagnose
Content-Type: application/json
Request:
{
"message": "Red scaly ring-shaped patch on my arm, very itchy, spreading for 2 weeks",
"patient_age": 25,
"image_base64": "/9j/4AAQSkZJRg...",
"session_id": "android_session_001"
}
| Field | Type | Required | Description |
|---|---|---|---|
message |
string | No | Symptom description (free text) |
patient_age |
int | No | Age (or auto-extracted from message) |
image_base64 |
string | No | Base64-encoded JPEG (no data URI prefix) |
session_id |
string | No | For tracking β use device ID |
Response:
{
"success": true,
"top_disease": "Tinea Corporis",
"top_score": 18.45,
"candidates": [
{"disease": "Tinea Corporis", "score": 18.45},
{"disease": "Psoriasis", "score": 15.20},
{"disease": "Eczema", "score": 12.80},
{"disease": "Contact Dermatitis", "score": 10.50},
{"disease": "Tinea Cruris", "score": 9.30}
],
"differentials": [
{"disease": "Psoriasis", "score": 15.20},
{"disease": "Eczema", "score": 12.80},
{"disease": "Contact Dermatitis", "score": 10.50}
],
"evidence": [
{"title": "Clinical: annular ring-shaped erythematous-plaque central-clearing", "source": "CLINICAL_KB"},
{"title": "Dermatology reference: tinea corporis", "source": "PubMed"}
],
"answer": "## π₯ Diagnosis: **Tinea Corporis**\n\n...",
"log_text": "[0.0s] 1_input/parser: ...\n...",
"pipeline": {
"stages": 6,
"agents": 5,
"neo4j": true,
"vision": true
}
}
3. Diagnose (Multipart Upload)
Easier for Android camera/gallery images β no base64 encoding needed.
POST /api/diagnose/upload
Content-Type: multipart/form-data
Form Fields:
| Field | Type | Required | Description |
|---|---|---|---|
message |
string | No | Symptom description |
patient_age |
int | No | Patient age |
image |
file | No | JPEG/PNG image file |
Response: Same JSON structure as /api/diagnose.
4. Chat (Multi-turn Conversation)
POST /api/chat
Content-Type: application/json
Request:
{
"message": "It's very itchy and spreading to other areas",
"session_id": "android_session_001",
"image_base64": null,
"patient_age": 25,
"history": []
}
Response:
{
"response": "## π₯ Diagnosis: **Tinea Corporis**\n...",
"session_id": "android_session_001",
"top_disease": "Tinea Corporis",
"top_score": 20.30,
"follow_up_question": "Have you tried any antifungal treatments?"
}
When top_score < 3.0, the API includes a follow_up_question β show this to the user as a prompt.
Kotlin Integration
Retrofit Setup
// build.gradle.kts
// implementation("com.squareup.retrofit2:retrofit:2.11.0")
// implementation("com.squareup.retrofit2:converter-gson:2.11.0")
// implementation("com.squareup.okhttp3:okhttp:4.12.0")
// implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
val okHttp = OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY })
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://avishek8136-indidermax.hf.space/")
.client(okHttp)
.addConverterFactory(GsonConverterFactory.create())
.build()
val api = retrofit.create(IndiDermaXApi::class.java)
Retrofit Interface
interface IndiDermaXApi {
@GET("api/health")
suspend fun healthCheck(): HealthResponse
@POST("api/diagnose")
suspend fun diagnose(@Body request: DiagnoseRequest): DiagnoseResponse
@Multipart
@POST("api/diagnose/upload")
suspend fun diagnoseUpload(
@Part("message") message: RequestBody,
@Part("patient_age") age: RequestBody,
@Part image: MultipartBody.Part
): DiagnoseResponse
@POST("api/chat")
suspend fun chat(@Body request: ChatRequest): ChatResponse
}
Data Classes
data class HealthResponse(
val status: String,
val mode: String, // "neo4j_live" | "cache_fallback" | "kb_only"
val neo4j: Boolean,
val nvidia: Boolean,
val timestamp: String
)
data class DiagnoseRequest(
val message: String = "",
val patient_age: Int? = null,
val image_base64: String? = null,
val session_id: String = "android"
)
data class DiagnoseResponse(
val success: Boolean,
val top_disease: String,
val top_score: Double,
val candidates: List<Candidate>,
val differentials: List<Candidate>,
val evidence: List<EvidenceItem>,
val answer: String, // Markdown β render in a WebView or parse
val log_text: String,
val pipeline: PipelineInfo
)
data class Candidate(
val disease: String,
val score: Double
)
data class EvidenceItem(
val title: String,
val source: String
)
data class PipelineInfo(
val stages: Int,
val agents: Int,
val neo4j: Boolean,
val vision: Boolean
)
data class ChatRequest(
val message: String,
val session_id: String = "chat",
val image_base64: String? = null,
val patient_age: Int? = null,
val history: List<ChatMessage> = emptyList()
)
data class ChatMessage(
val role: String, // "user" | "assistant"
val content: String
)
data class ChatResponse(
val response: String,
val session_id: String,
val top_disease: String,
val top_score: Double,
val follow_up_question: String
)
Image to Base64
fun Bitmap.toBase64Diagnose(): String {
val stream = ByteArrayOutputStream()
this.compress(Bitmap.CompressFormat.JPEG, 85, stream)
return Base64.encodeToString(stream.toByteArray(), Base64.NO_WRAP)
}
fun File.toBase64Diagnose(): String {
return Base64.encodeToString(this.readBytes(), Base64.NO_WRAP)
}
Usage Example (ViewModel)
class DiagnoseViewModel(private val api: IndiDermaXApi) : ViewModel() {
private val _result = MutableLiveData<DiagnoseResponse>()
val result: LiveData<DiagnoseResponse> = _result
private val _loading = MutableLiveData(false)
val loading: LiveData<Boolean> = _loading
fun diagnose(text: String, age: Int?, imageBase64: String?) {
viewModelScope.launch {
_loading.value = true
try {
val response = api.diagnose(
DiagnoseRequest(
message = text,
patient_age = age,
image_base64 = imageBase64,
session_id = UUID.randomUUID().toString()
)
)
_result.value = response
} catch (e: Exception) {
// Handle network error β show retry button
Log.e("Diagnose", "Failed", e)
} finally {
_loading.value = false
}
}
}
}
Error Handling
| HTTP Code | Meaning | Body |
|---|---|---|
200 |
Success | Normal response JSON |
400 |
Bad request (invalid base64, missing fields) | {"success":false,"error":"..."} |
422 |
Validation error | {"detail":[{"loc":[...],"msg":"..."}]} |
500 |
Server error | {"success":false,"error":"...","endpoint":"..."} |
504 |
Timeout (>45s) | {"success":false,"error":"Request timed out..."} |
Error Parsing (Retrofit)
// In your ViewModel or repository:
try {
val response = api.diagnose(request)
if (response.success) {
// Show diagnosis
} else {
// response.success == false β show error message
}
} catch (e: HttpException) {
when (e.code()) {
400, 422 -> showError("Invalid input. Check your message and image.")
500 -> showError("Server error. Please try again.")
504 -> showError("Request timed out. The image may be too large. Try again.")
else -> showError("Network error: ${e.message}")
}
} catch (e: IOException) {
showError("No internet connection. Check your network.")
}
Pipeline Architecture
Android App
β
βββ POST /api/diagnose βββββΊ FastAPI Server (port 7860)
β (text + base64 image) β
β βββ Stage 1: Input Processing
β βββ Stage 2: Feature Extraction (text)
β βββ Stage 2b: NVIDIA NIM Vision (image β descriptors + body location)
β βββ Stage 3: Neo4j Graph Candidate Retrieval
β βββ Stage 4a: Visual Concept Agent
β βββ Stage 4b: Symptom Analyst
β βββ Stage 4c: Temporal Pattern Matcher (DTW)
β βββ Stage 4d: Differential Diagnosis Debater
β βββ Stage 4e: Evidence Synthesizer
β βββ Stage 5: Final Output
β
βββ Response βββββββββββββββββ JSON
{
success, top_disease, top_score,
candidates[5], differentials[3],
evidence[5], answer, log_text, pipeline
}
Testing with curl (before writing Android code)
# 1. Warm up (do this first β cold start takes 20-30s)
curl -s https://avishek8136-indidermax.hf.space/api/health | python3 -m json.tool
# 2. Text-only diagnosis
curl -s -X POST https://avishek8136-indidermax.hf.space/api/diagnose \
-H "Content-Type: application/json" \
-d '{"message":"red scaly ring-shaped patch on arm, very itchy for 2 weeks","patient_age":25}' \
| python3 -m json.tool
# 3. Diagnosis with image (multipart)
curl -s -X POST https://avishek8136-indidermax.hf.space/api/diagnose/upload \
-F "message=ring shaped rash on arm, itchy" \
-F "patient_age=30" \
-F "image=@/path/to/skin_image.jpg" \
| python3 -m json.tool
# 4. Diagnosis with image (base64)
IMG_BASE64=$(base64 -w0 /path/to/skin_image.jpg)
curl -s -X POST https://avishek8136-indidermax.hf.space/api/diagnose \
-H "Content-Type: application/json" \
-d "{\"message\":\"itchy red patch\",\"image_base64\":\"$IMG_BASE64\",\"patient_age\":25}" \
| python3 -m json.tool
# 5. Chat (multi-turn)
curl -s -X POST https://avishek8136-indidermax.hf.space/api/chat \
-H "Content-Type: application/json" \
-d '{"message":"it is spreading and very painful now","session_id":"test_001","patient_age":25}' \
| python3 -m json.tool
Disclaimer
β οΈ AI-assisted decision support tool for educational purposes only. Always consult a qualified dermatologist for in-person examination and diagnosis. This app does not replace professional medical advice.