File size: 6,827 Bytes
ddbe9e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
"""
MedTech Image Processing Backend API
Provides endpoints for processing medical images with arterial and venous phase filters
"""

from fastapi import FastAPI, File, UploadFile, Form, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import Response
import cv2
import numpy as np
from io import BytesIO
from PIL import Image
import logging

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Initialize FastAPI app
app = FastAPI(
    title="MedTech Image Processing API",
    description="Backend API for processing medical images",
    version="1.0.0"
)

# Configure CORS to allow requests from GitHub Pages and localhost
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "https://*.github.io",
        "http://localhost:*",
        "http://127.0.0.1:*",
        "*"  # Allow all origins for development - restrict in production
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


def increase_contrast(img: np.ndarray) -> np.ndarray:
    """
    Apply CLAHE (Contrast Limited Adaptive Histogram Equalization) to enhance contrast
    Used for arterial phase processing

    Args:
        img: Input image as numpy array (BGR format)

    Returns:
        Enhanced image as numpy array
    """
    try:
        # Convert to LAB color space for better contrast enhancement
        lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
        l, a, b = cv2.split(lab)

        # Apply CLAHE to L channel (lightness)
        clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
        l_enhanced = clahe.apply(l)

        # Merge channels back and convert to BGR
        enhanced_lab = cv2.merge([l_enhanced, a, b])
        enhanced = cv2.cvtColor(enhanced_lab, cv2.COLOR_LAB2BGR)

        logger.info("Contrast enhancement applied successfully")
        return enhanced
    except Exception as e:
        logger.error(f"Error in contrast enhancement: {str(e)}")
        raise


def apply_gaussian_blur(img: np.ndarray) -> np.ndarray:
    """
    Apply Gaussian blur for smoothing
    Used for venous phase processing

    Args:
        img: Input image as numpy array (BGR format)

    Returns:
        Blurred image as numpy array
    """
    try:
        # Apply Gaussian blur with 15x15 kernel
        blurred = cv2.GaussianBlur(img, (15, 15), 0)
        logger.info("Gaussian blur applied successfully")
        return blurred
    except Exception as e:
        logger.error(f"Error in Gaussian blur: {str(e)}")
        raise


def read_image_file(file_bytes: bytes) -> np.ndarray:
    """
    Read image file bytes and convert to OpenCV format

    Args:
        file_bytes: Raw image file bytes

    Returns:
        Image as numpy array in BGR format
    """
    try:
        # Convert bytes to PIL Image
        image = Image.open(BytesIO(file_bytes))

        # Convert to RGB if needed
        if image.mode != 'RGB':
            image = image.convert('RGB')

        # Convert PIL Image to numpy array
        img_array = np.array(image)

        # Convert RGB to BGR for OpenCV
        img_bgr = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)

        logger.info(f"Image loaded successfully: shape={img_bgr.shape}")
        return img_bgr
    except Exception as e:
        logger.error(f"Error reading image file: {str(e)}")
        raise HTTPException(status_code=400, detail=f"Invalid image file: {str(e)}")


def encode_image(img: np.ndarray) -> bytes:
    """
    Encode OpenCV image to PNG bytes

    Args:
        img: Image as numpy array (BGR format)

    Returns:
        PNG encoded bytes
    """
    try:
        # Encode image to PNG format
        success, buffer = cv2.imencode('.png', img)
        if not success:
            raise Exception("Failed to encode image")

        return buffer.tobytes()
    except Exception as e:
        logger.error(f"Error encoding image: {str(e)}")
        raise


@app.get("/")
async def root():
    """Health check endpoint"""
    return {
        "status": "online",
        "service": "MedTech Image Processing API",
        "version": "1.0.0",
        "endpoints": {
            "/process": "POST - Process medical images with arterial or venous phase filters"
        }
    }


@app.post("/process")
async def process_image(
    image: UploadFile = File(..., description="Medical image file (JPG/PNG)"),
    phase: str = Form(..., description="Processing phase: 'arterial' or 'venous'")
):
    """
    Process medical image with selected phase filter

    Args:
        image: Uploaded image file (JPG/PNG)
        phase: Processing phase - 'arterial' (contrast enhancement) or 'venous' (blur)

    Returns:
        Processed image as PNG
    """
    logger.info(f"Processing request received - Phase: {phase}")

    # Validate phase parameter
    if phase not in ['arterial', 'venous']:
        raise HTTPException(
            status_code=400,
            detail="Invalid phase. Must be 'arterial' or 'venous'"
        )

    # Validate file type
    if not image.content_type.startswith('image/'):
        raise HTTPException(
            status_code=400,
            detail="File must be an image (JPG/PNG)"
        )

    try:
        # Read uploaded file
        file_bytes = await image.read()
        logger.info(f"File received: {image.filename}, size: {len(file_bytes)} bytes")

        # Convert to OpenCV format
        img = read_image_file(file_bytes)

        # Apply processing based on phase
        if phase == 'arterial':
            processed_img = increase_contrast(img)
            logger.info("Arterial phase processing completed")
        else:  # venous
            processed_img = apply_gaussian_blur(img)
            logger.info("Venous phase processing completed")

        # Encode processed image
        output_bytes = encode_image(processed_img)

        # Return processed image
        return Response(
            content=output_bytes,
            media_type="image/png",
            headers={
                "Content-Disposition": f"inline; filename=processed_{image.filename}",
                "X-Processing-Phase": phase
            }
        )

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Unexpected error processing image: {str(e)}")
        raise HTTPException(
            status_code=500,
            detail=f"Error processing image: {str(e)}"
        )


@app.get("/health")
async def health_check():
    """Detailed health check endpoint"""
    return {
        "status": "healthy",
        "opencv_version": cv2.__version__,
        "numpy_version": np.__version__
    }


if __name__ == "__main__":
    import uvicorn
    # Run server on port 7860 (Hugging Face Spaces default)
    uvicorn.run(app, host="0.0.0.0", port=7860)