Commit 277c5639 by Jaime Collado

Initial commit

parents
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Rope
.ropeproject
# Django stuff:
*.log
*.pot
# Sphinx documentation
docs/_build/
# Database
*.db
# Environments
venv/
# Secrets
config.yaml
File mode changed
No preview for this file type
account:
algorithm: HS256
secret_key: <your_secret_key>
access_token_expire_minutes: 10080
from sqlalchemy.orm import Session
import security, models, schemas
def get_user(db, username: str):
return db.query(models.User).filter(models.User.username == username).first()
def authenticate_user(db: Session, username: str, password: str):
user = get_user(db, username)
if not user:
return False
if not security.verify_password(password, user.hashed_password):
return False
return user
def create_user(db: Session, user: schemas.UserCreate):
hashed_password = security.get_password_hash(user.password)
# Remove unhashed password and insert the hashed one
user_dict = user.dict()
del user_dict["password"]
# Create the user
db_user = models.User(**user_dict, hashed_password=hashed_password)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
[
{
"id": 0,
"text": "exploraciones : mamografia digital bilateral ecografia mamaria radiografia de torax comparado con : ecografia del 05/01/2021 y del 02/07/2020 mamografia del 02/07/2020 hallazgos : radiografia de torax silueta cardiomediastinica de forma , situacion y tamano normal . hilios pulmonares con estructura conservada . columna aerea de la traquea central . parenquima pulmonar sin evidencia de infiltrados , consolidados , nodulos o masas . vascularidad pulmonar normal . senos costodiafragmatico y costofrenicos libres . estructuras blandas evaluadas normales . imagenes de alta densidad proyectadas en hemitorax derecho que pueden corresponder a clips quirurgicos mamografia : se realiza mamografia digital en proyecciones cc y oml de ambas mamas , identificandose patron mixto con predominio del tejido fibroglandular heterogeneamente denso disperso con tendencia a formacion de imagenes de aspecto pseudonodular y areas parcheadas de tejido adiposo interpuesto , hallazgo que pudiera ocultar lesiones de pequeno tamano . calcificaciones groseras con liponecrosis proyectada en icsup de md con imagenes lineales de mayor densidad que pueden corresponder a material quirurgico en relacion ocn antecedente de la pciente . no se identifican imagenes nodulares de aspecto espiculado , estrellado asi como tampoco distorsiones del parenquima mamario . no se observan microcalcificaciones agrupadas . adecuada relacion complejo piel areola pezon . region axilar libre de adenopatias . ecografia mamaria se realiza valoracion con transductor lineal de ambas mamas en sentido horario y complementariamente en regiones axilares , identificandose un tejido de muy alto poder ecogenico de ecoestructura heterogenea secundario a combinacion de areas hiperecogenicas , hipoecogenicas y de atenuacion en las interfases del tejido glandular y adiposo . no se identifican imagenes nodulares de aspecto quistico o solido sospechoso de patologia mamaria area de distorsion de la ecoestructura del parenquima mamario en icsup y hacia cse/icext de md con marcada sombra posterior en relacion a cambios postquirurgicos . no hay signos de ectasia ductal retroareolar . region axilar libre de adenopatias . conclusion : densidad mamaria c , clasificacion acr cambios postquirurgicos en md con clips y calcificaciones groseras , estable"
},
{
"id": 1,
"text": "exploraciones : mamografia digital bilateral ecografia mamaria radiografia de torax comparado con : ecografia del 05/01/2021 y del 02/07/2020 mamografia del 02/07/2020 hallazgos : radiografia de torax silueta cardiomediastinica de forma , situacion y tamano normal . hilios pulmonares con estructura conservada . columna aerea de la traquea central . parenquima pulmonar sin evidencia de infiltrados , consolidados , nodulos o masas . vascularidad pulmonar normal . senos costodiafragmatico y costofrenicos libres . estructuras blandas evaluadas normales . imagenes de alta densidad proyectadas en hemitorax derecho que pueden corresponder a clips quirurgicos mamografia : se realiza mamografia digital en proyecciones cc y oml de ambas mamas , identificandose patron mixto con predominio del tejido fibroglandular heterogeneamente denso disperso con tendencia a formacion de imagenes de aspecto pseudonodular y areas parcheadas de tejido adiposo interpuesto , hallazgo que pudiera ocultar lesiones de pequeno tamano . calcificaciones groseras con liponecrosis proyectada en icsup de md con imagenes lineales de mayor densidad que pueden corresponder a material quirurgico en relacion ocn antecedente de la pciente . no se identifican imagenes nodulares de aspecto espiculado , estrellado asi como tampoco distorsiones del parenquima mamario . no se observan microcalcificaciones agrupadas . adecuada relacion complejo piel areola pezon . region axilar libre de adenopatias . ecografia mamaria se realiza valoracion con transductor lineal de ambas mamas en sentido horario y complementariamente en regiones axilares , identificandose un tejido de muy alto poder ecogenico de ecoestructura heterogenea secundario a combinacion de areas hiperecogenicas , hipoecogenicas y de atenuacion en las interfases del tejido glandular y adiposo . no se identifican imagenes nodulares de aspecto quistico o solido sospechoso de patologia mamaria area de distorsion de la ecoestructura del parenquima mamario en icsup y hacia cse/icext de md con marcada sombra posterior en relacion a cambios postquirurgicos . no hay signos de ectasia ductal retroareolar . region axilar libre de adenopatias . conclusion : densidad mamaria c , clasificacion acr cambios postquirurgicos en md con clips y calcificaciones groseras , estable"
}
]
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
# connect_args={"check_same_thread": False is only for SQLite
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
from fastapi.security import OAuth2PasswordBearer
from fastapi import Depends, HTTPException, status
from jose import JWTError, jwt
from sqlalchemy.orm import Session
import security, schemas, crud, database
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def get_db():
db = database.SessionLocal()
try:
yield db
finally:
db.close()
async def get_current_user(
db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, security.SECRET_KEY, algorithms=[security.ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_data = schemas.TokenData(username=username)
except JWTError:
raise credentials_exception
user = crud.get_user(db, username=token_data.username)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(current_user: schemas.User = Depends(get_current_user)):
if not current_user.is_active:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
\ No newline at end of file
import joblib
from datetime import timedelta
from typing import List
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.orm import Session
import schemas, security, dependencies, crud, database
database.Base.metadata.create_all(bind=database.engine)
# APP
app = FastAPI()
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Load the vectorizer
with open("/home/jcollado/projects/api_pirads/vectorizers/birads_tfidf.pkl", "rb") as pickled_file:
vectorizer = joblib.load(pickled_file)
# Load the model
with open("/home/jcollado/projects/api_pirads/classifiers/birads_model.joblib", "rb") as pickled_file:
clf = joblib.load(pickled_file)
def _predict(text, model, vectorizer):
# TODO: ¿Preprocesar texto?
X_test = vectorizer.transform([text])
probs = model.predict_proba(X_test)
probs = [prob[0][1] for prob in probs]
return probs
# Methods
@app.post("/register", response_model=schemas.User)
def register(user: schemas.UserCreate, db: Session = Depends(dependencies.get_db)):
db_user = crud.get_user(db, username=user.username)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.post("/token", response_model=schemas.Token)
async def login_for_access_token(
form_data: OAuth2PasswordRequestForm = Depends(),
db: Session = Depends(dependencies.get_db)
):
user = crud.authenticate_user(db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=security.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = security.create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@app.post("/predict", response_model=List[schemas.OutputData])
async def predict(
input_data: List[schemas.InputData],
current_user: str = Depends(dependencies.get_current_active_user)
):
# Input data: [{"id": 1, "texto": "a"}, {"id": 2, "texto": "b"}]
predictions = []
for report in input_data:
prediction = _predict(text=report.text, model=clf, vectorizer=vectorizer)
print(prediction)
predictions.append(
{
"id": report.id,
"prediction": prediction
}
)
# Return predictions
return predictions
\ No newline at end of file
from sqlalchemy import Boolean, Column, Integer, String
from database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
\ No newline at end of file
anyio==3.6.2
bcrypt==4.0.1
certifi==2022.12.7
cffi==1.15.1
click==8.1.3
cryptography==39.0.1
dnspython==2.3.0
ecdsa==0.18.0
email-validator==1.3.1
fastapi==0.89.1
greenlet==2.0.2
h11==0.14.0
httpcore==0.16.3
httptools==0.5.0
httpx==0.23.3
idna==3.4
itsdangerous==2.1.2
Jinja2==3.1.2
joblib==1.2.0
MarkupSafe==2.1.2
numpy==1.24.2
orjson==3.8.5
passlib==1.7.4
pyasn1==0.4.8
pycparser==2.21
pydantic==1.10.4
python-dotenv==0.21.1
python-jose==3.3.0
python-multipart==0.0.5
PyYAML==6.0
rfc3986==1.5.0
rsa==4.9
scikit-learn==1.2.1
scipy==1.10.0
six==1.16.0
sniffio==1.3.0
SQLAlchemy==2.0.2
starlette==0.22.0
threadpoolctl==3.1.0
typing_extensions==4.4.0
ujson==5.7.0
uvicorn==0.20.0
uvloop==0.17.0
watchfiles==0.18.1
websockets==10.4
xgboost==0.90
from typing import List, Union
from pydantic import BaseModel
# ---------- TOKEN SCHEMAS ----------
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: Union[str, None] = None
# ---------- USER SCHEMAS ----------
class UserBase(BaseModel):
username: str
class UserCreate(UserBase):
password: str
class User(UserBase):
is_active: Union[bool, None] = None
class Config:
orm_mode = True
class UserInDB(User):
hashed_password: str
# ---------- DATA SCHEMAS ----------
class InputData(BaseModel):
id: int
text: str
class OutputData(BaseModel):
id: int
prediction: List[float]
\ No newline at end of file
import yaml
import os
from typing import Union
from datetime import datetime, timedelta
from jose import jwt
from passlib.context import CryptContext
cwd = os.path.abspath(os.getcwd())
config_path = os.path.join(cwd, "config.yaml")
with open(config_path, "r") as ymlfile:
cfg = yaml.load(ymlfile, Loader=yaml.FullLoader)
# ------- ACCOUNT SECURITY -------
# to get a new secret key run:
# openssl rand -hex 32
SECRET_KEY = cfg["account"]["secret_key"]
ALGORITHM = cfg["account"]["algorithm"]
ACCESS_TOKEN_EXPIRE_MINUTES = cfg["account"]["access_token_expire_minutes"] # 7 days
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
\ No newline at end of file
No preview for this file type
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment