add http test | fix ctrl+c shutdown
This commit is contained in:
@@ -1,172 +1,101 @@
|
||||
# DICOM Migration REST API
|
||||
|
||||
This REST API provides an endpoint to trigger DICOM study migration for a specific Accession Number.
|
||||
A simple REST API that triggers DICOM study migration for a specific Accession Number.
|
||||
|
||||
## Features
|
||||
|
||||
- Migrate DICOM studies by Accession Number
|
||||
- Token-based authentication
|
||||
- Detailed JSON responses with operation status
|
||||
- Proper logging and error handling
|
||||
- Automatic cleanup of DICOM files after each request
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.9 or higher
|
||||
- FastAPI
|
||||
- Uvicorn
|
||||
- Dependencies from the main DICOM migration project
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install the required dependencies:
|
||||
|
||||
```bash
|
||||
cd api-app
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The API uses the same configuration settings as the main DICOM migration project.
|
||||
|
||||
### Authentication
|
||||
|
||||
The API uses token-based authentication. Set your token using the environment variable:
|
||||
|
||||
```bash
|
||||
export API_TOKEN=your-secure-token
|
||||
```
|
||||
|
||||
If no token is provided, a default token will be used (not recommended for production).
|
||||
|
||||
## Usage
|
||||
|
||||
### Starting the API Server
|
||||
## How to Run
|
||||
|
||||
```bash
|
||||
# Start the server on default port 8000
|
||||
python run.py
|
||||
|
||||
# Specify a custom port
|
||||
# Custom port
|
||||
python run.py --port 9000
|
||||
|
||||
# Specify a custom token
|
||||
# Custom token
|
||||
python run.py --token your-secure-token
|
||||
|
||||
# Enable auto-reload for development
|
||||
# Development mode with auto-reload
|
||||
python run.py --reload
|
||||
```
|
||||
|
||||
### API Endpoints
|
||||
## How to Use
|
||||
|
||||
#### 1. Migrate Study by Accession Number
|
||||
|
||||
```
|
||||
POST /migrate/{accession_number}
|
||||
```
|
||||
|
||||
**Authentication:**
|
||||
- Bearer token in Authorization header
|
||||
|
||||
**Response:**
|
||||
|
||||
Successful migration:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Successfully migrated study for accession number: ABC12345",
|
||||
"details": {
|
||||
"study_uid": "1.2.826.0.1.3680043.9.7307.1.123456",
|
||||
"his_integration_success": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Failed migration:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Failed to process study: Study not found",
|
||||
"details": null
|
||||
}
|
||||
```
|
||||
|
||||
## Example API Calls
|
||||
|
||||
### Using curl
|
||||
### Make a Request
|
||||
|
||||
```bash
|
||||
# Migrate a study by accession number
|
||||
curl -X POST "http://localhost:8000/migrate/ABC12345" \
|
||||
-H "Authorization: Bearer your-token" \
|
||||
# Using curl
|
||||
curl -X POST "http://localhost:8000/migrate/R.20240401.0138" \
|
||||
-H "Authorization: Bearer token-his2-untuk-hit-api-migrasi-clarity" \
|
||||
-H "Content-Type: application/json"
|
||||
```
|
||||
|
||||
### Using Python requests
|
||||
### Response Example
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
url = "http://localhost:8000/migrate/ABC12345"
|
||||
headers = {
|
||||
"Authorization": "Bearer your-token",
|
||||
"Content-Type": "application/json"
|
||||
```json
|
||||
{
|
||||
"success":true,
|
||||
"message":"Berhasil migrasi file DICOM Accession Number: R.20240401.0138",
|
||||
"details":
|
||||
{
|
||||
"accession_number":"R.20240401.0138",
|
||||
"process_start_time":"2025-07-11 15:27:26",
|
||||
"steps_completed":[
|
||||
"study_found","study_retrieved","study_sent","log_created","his_api_success","cleanup_success"
|
||||
],
|
||||
"study_uid":"1.2.826.1.3680043.9.5282.150415.544835.202404010138",
|
||||
"patient_id":"00544835",
|
||||
"study_description":"A103 - Thorax PA",
|
||||
"instances_retrieved":4,
|
||||
"files_sent":2,
|
||||
"total_files":2,
|
||||
"c_store_success":true,
|
||||
"series_count":4,
|
||||
"his_api_status_code":200,
|
||||
"his_integration_success":true,
|
||||
"cleanup_success":true,
|
||||
"process_end_time":"2025-07-11 15:27:46"
|
||||
}
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers)
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
## Logs
|
||||
|
||||
The API logs are stored in the `logs/api.log` file. The log includes information about:
|
||||
- Main API logs: `logs/api.log`
|
||||
- Process details: `logs/api_process.log`
|
||||
|
||||
- API startup and shutdown
|
||||
- Migration requests
|
||||
- Errors during migration process
|
||||
- Integration with HIS results
|
||||
## Simple Workflow
|
||||
|
||||
## Troubleshooting
|
||||
1. API receives request with accession number
|
||||
2. Finds study UID using C-FIND
|
||||
3. Downloads DICOM files using C-GET
|
||||
4. Uploads files to destination PACS using C-STORE
|
||||
5. Updates HIS system via API call
|
||||
6. Cleans up all temporary DICOM files
|
||||
7. Returns success/failure response
|
||||
|
||||
1. **Authentication Error**:
|
||||
- Make sure you're providing the correct token in the Authorization header
|
||||
- Check that the token format is `Bearer your-token`
|
||||
## Troubleshooting Tips
|
||||
|
||||
2. **Study Not Found**:
|
||||
- Verify that the accession number is correct
|
||||
- Check if the PACS server is reachable
|
||||
- Verify that the study exists in the source PACS
|
||||
- Check PACS connectivity in `config/settings.py`
|
||||
- Verify the accession number exists in source PACS
|
||||
- Ensure permissions for log directories
|
||||
- Check both log files for detailed error messages
|
||||
- Verify HIS API connectivity for end-to-end success
|
||||
|
||||
3. **Connection Error**:
|
||||
- Check PACS connectivity
|
||||
- Verify network settings in `config/settings.py`
|
||||
## Quick Test
|
||||
|
||||
## Integrating with External Systems
|
||||
1. Start the server:
|
||||
```bash
|
||||
python run.py
|
||||
```
|
||||
|
||||
You can call this API from other systems to trigger DICOM migrations for specific studies:
|
||||
2. In another terminal, test the root endpoint:
|
||||
```bash
|
||||
curl http://localhost:8000/
|
||||
```
|
||||
|
||||
1. Obtain the API token
|
||||
2. Make a POST request to `/migrate/{accession_number}`
|
||||
3. Handle the JSON response based on the `success` field
|
||||
3. Test migration with a valid accession number:
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/migrate/R.20240401.0138" \
|
||||
-H "Authorization: Bearer token-his2-untuk-hit-api-migrasi-clarity"
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
### DICOM File Cleanup
|
||||
|
||||
The API implements a robust cleanup mechanism to ensure that DICOM files are always removed after processing:
|
||||
|
||||
1. **Exit Handlers**: The application registers cleanup handlers via Python's `atexit` module and signal handlers for graceful cleanup during server shutdown.
|
||||
|
||||
2. **Per-Request Cleanup**: After each migration request completes (whether successful or not), the API explicitly calls the cleanup function to remove all DICOM files associated with the study.
|
||||
|
||||
3. **Directory Registration**: All temporary directories created during the migration process are registered for cleanup, ensuring no files are left behind.
|
||||
|
||||
This dual approach (exit handlers + explicit cleanup) ensures that:
|
||||
- DICOM files are immediately removed after each API request completes
|
||||
- No files are left behind even if the server crashes or is terminated abnormally
|
||||
- Disk space is efficiently managed during high-volume operations
|
||||
|
||||
### Authentication
|
||||
|
||||
The API uses Bearer token authentication. The token is specified through:
|
||||
|
||||
25
api-app/api_cleanup.py
Normal file
25
api-app/api_cleanup.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
Custom cleanup utilities for the API application.
|
||||
This module provides customized cleanup functions that are more compatible with FastAPI.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import atexit
|
||||
from config import settings
|
||||
from utils.logger import setup_logger
|
||||
from utils.cleanup import _cleanup_dirs, cleanup_dicom_files
|
||||
|
||||
# Create API cleanup logger
|
||||
cleanup_logger = setup_logger("api_cleanup", "logs/api_cleanup.log")
|
||||
|
||||
def register_api_cleanup():
|
||||
"""
|
||||
Register cleanup handlers for the API application.
|
||||
This avoids registering signal handlers which can conflict with FastAPI.
|
||||
"""
|
||||
# Only register the atexit handler, not signal handlers
|
||||
atexit.register(cleanup_dicom_files)
|
||||
cleanup_logger.info("Registered API cleanup handler for exit")
|
||||
|
||||
return True
|
||||
@@ -17,7 +17,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from process import process_study_by_accession
|
||||
from utils.logger import setup_logger
|
||||
from utils.dicom_utils import create_directory_if_not_exists
|
||||
from utils.cleanup import register_exit_handlers
|
||||
from api_cleanup import register_api_cleanup
|
||||
|
||||
# Create API logger
|
||||
api_logger = setup_logger("api", "logs/api.log")
|
||||
@@ -71,10 +71,23 @@ def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
"""Initialize resources when the API starts."""
|
||||
register_exit_handlers()
|
||||
# Create necessary directories
|
||||
create_directory_if_not_exists("logs")
|
||||
|
||||
# Use our custom API cleanup registration that avoids signal handlers
|
||||
register_api_cleanup()
|
||||
|
||||
api_logger.info("API started successfully")
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown_event():
|
||||
"""Clean up resources when the API shuts down."""
|
||||
api_logger.info("API shutting down, cleaning up resources...")
|
||||
# Perform cleanup operations here
|
||||
from utils.cleanup import cleanup_dicom_files
|
||||
cleanup_dicom_files()
|
||||
api_logger.info("Cleanup complete, API shutdown successful")
|
||||
|
||||
@app.get("/", response_model=MigrationResponse)
|
||||
async def root():
|
||||
"""
|
||||
|
||||
@@ -4,6 +4,7 @@ Script to run the FastAPI server for DICOM migration service.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import uvicorn
|
||||
import argparse
|
||||
|
||||
@@ -16,6 +17,12 @@ def parse_args():
|
||||
parser.add_argument('--reload', action='store_true', help='Enable auto-reload for development')
|
||||
return parser.parse_args()
|
||||
|
||||
def handle_exit(signum, frame):
|
||||
"""Handle exit signals gracefully."""
|
||||
print(f"Received signal {signum}, shutting down gracefully...")
|
||||
# Let uvicorn handle the exit
|
||||
sys.exit(0)
|
||||
|
||||
def main():
|
||||
"""Main function to run the API server."""
|
||||
args = parse_args()
|
||||
@@ -29,6 +36,10 @@ def main():
|
||||
print("WARNING: API_TOKEN not set. Using default token 'token-his2-untuk-hit-api-migrasi-clarity'.")
|
||||
print("Set environment variable API_TOKEN or use --token argument for better security.")
|
||||
|
||||
# Set up signal handlers for graceful shutdown
|
||||
signal.signal(signal.SIGINT, handle_exit)
|
||||
signal.signal(signal.SIGTERM, handle_exit)
|
||||
|
||||
print(f"Starting DICOM Migration API server on {args.host}:{args.port}")
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
|
||||
12
api-app/test.http
Normal file
12
api-app/test.http
Normal file
@@ -0,0 +1,12 @@
|
||||
@host = http://localhost:8000
|
||||
@token = token-his2-untuk-hit-api-migrasi-clarity
|
||||
|
||||
|
||||
### Healtcheck
|
||||
GET {{host}}/
|
||||
|
||||
|
||||
###
|
||||
POST {{host}}/migrate/R.20240401.0138
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
@@ -2,6 +2,7 @@
|
||||
DICOM file cleanup utility to ensure temporary DICOM files are removed even on script termination.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import atexit
|
||||
import signal
|
||||
@@ -100,4 +101,12 @@ def signal_handler(sig, frame):
|
||||
|
||||
logger.info(f"Received {signal_name}, cleaning up...")
|
||||
cleanup_dicom_files()
|
||||
exit(0)
|
||||
# Don't call exit() directly in a web application context
|
||||
# Let the calling application handle the exit process
|
||||
# This fix makes it compatible with FastAPI and other web frameworks
|
||||
# Raise SystemExit instead of calling exit() directly
|
||||
if hasattr(frame, '_is_web_app') and frame._is_web_app:
|
||||
return
|
||||
else:
|
||||
# For command-line applications, we can exit directly
|
||||
sys.exit(0)
|
||||
Reference in New Issue
Block a user