first_commit

This commit is contained in:
zhangsan 2024-07-29 11:43:52 +08:00
commit 03f32566d1
507 changed files with 117785 additions and 0 deletions

75
.gitignore vendored Normal file
View File

@ -0,0 +1,75 @@
# Python 相关忽略
__pycache__/
*.py[cod]
*$py.class
# 虚拟环境相关忽略(保留环境配置文件)
*.env
*.venv
ENV/
env.bak/
venv.bak/
# 安装包
*.egg
*.egg-info/
dist/
build/
eggs/
parts/
var/
*.log
*.log.*
*.swp
*.swo
*.pid
# PyCharm 相关忽略(保留项目配置文件)
.idea/*
!.idea/*.iml
!.idea/modules.xml
!.idea/workspace.xml
!.idea/tasks.xml
!.idea/inspectionProfiles
# Jupyter Notebook 忽略
.ipynb_checkpoints/
# VSCode 相关忽略
.vscode/
!.vscode/settings.json
!.vscode/launch.json
!.vscode/extensions.json
# 操作系统文件
.DS_Store
Thumbs.db
# 本地配置文件
*.local
*.dat
*.db
*.sqlite3
# 单元测试 / 覆盖率报告
.coverage
.tox/
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# MyPy
.mypy_cache/
# 临时文件和目录
tmp/
temp/
*.tmp
*.temp
#深度学习模型
models/

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.7 (covid_19_detector)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="pytest" />
</component>
</module>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/COVID-19-Detection-Flask-App-based-on-Chest-X-rays-and-CT-Scans-master.iml" filepath="$PROJECT_DIR$/.idea/COVID-19-Detection-Flask-App-based-on-Chest-X-rays-and-CT-Scans-master.iml" />
</modules>
</component>
</project>

26
config.py Normal file
View File

@ -0,0 +1,26 @@
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config: #公共配置
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN')
SQLALCHEMY_TRACK_MODIFICATIONS = False
FLASKY_POSTS_PER_PAGE = 5
FLASKY_PATIENT_PER_PAGE = 5
@staticmethod
def init_app(app):
pass
class DevelopmentConfig(Config):
DEBUG = 1
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:123456@localhost/covid_detector'
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:123456@localhost/covid_detector'
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}

45
flask_app/__init__.py Normal file
View File

@ -0,0 +1,45 @@
from flask import Flask,session
from flask_mail import Mail
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from config import config
from flask_moment import Moment
from flask_bootstrap import Bootstrap
# 创建 Flask 应用对象 app
# 通过配置文件或环境变量加载应用配置;
# 初始化扩展对象,如数据库、邮件等;
# 注册蓝图Blueprint对象将不同模块的视图函数注册到应用中
# 返回 Flask 应用对象 。app
mail = Mail()
db = SQLAlchemy()
moment = Moment()
bootstrap=Bootstrap()
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
mail.init_app(app) #将 Flask 应用程序与 Flask-Mail 扩展库绑定
db.init_app(app)
moment.init_app(app)
login_manager.init_app(app)
bootstrap.init_app(app)
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint)
from .user import user as user_blueprint
app.register_blueprint(user_blueprint)
from .appointment import appointment as appointment_blueprint
app.register_blueprint(appointment_blueprint)
from .faqs import faqs as faqs_blueprint
app.register_blueprint(faqs_blueprint)
# from .detect import detect as detect_blueprint
# app.register_blueprint(detect_blueprint)
return app

View File

@ -0,0 +1,3 @@
from flask import Blueprint
appointment = Blueprint('appointment', __name__)
from . import views

View File

@ -0,0 +1,13 @@
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, FileField, SelectField, IntegerField
from wtforms.validators import DataRequired, Length, Email
from sqlalchemy import or_
class PatientForm(FlaskForm):
name = StringField('就诊人姓名', validators=[DataRequired(), Length(min=2, max=50)])
id_number = StringField('证件号', validators=[DataRequired(), Length(min=18, max=18)])
phone = StringField('手机号', validators=[DataRequired(), Length(min=11, max=11)])
email = StringField('邮箱', validators=[DataRequired(), Email()])
gender = SelectField('性别', choices=[('', ''), ('', '')], validators=[DataRequired()])
age = IntegerField('年龄', validators=[DataRequired()])
location = StringField('家庭地址', validators=[DataRequired()])
submit = SubmitField('提交')

View File

@ -0,0 +1,81 @@
from datetime import datetime, date
from flask import render_template, request, url_for, session, flash
from werkzeug.utils import redirect
from flask_app.decorators import permission_required
from . import appointment
from .. import db
from .forms import PatientForm
from flask_login import current_user
from ..models import User, DocComment, Workday, Appointment, Permission
@appointment.route('/doctor_info/<int:docid>',methods=['GET','POST']) #咨询医生页
def doctor_info(docid):
session['next'] = request.url # 将当前URL保存到session中
doctor = User.query.filter_by(id=docid).first()
comments=DocComment.query.filter_by(doc_id=docid).all()
authors = [User.query.get(comment.author_id) for comment in comments]
comment_pairs = zip(comments, authors)
empty_comments = (len(comments) == 0)
return render_template('appointment/doctor_info.html', doctor=doctor,empty_comments=empty_comments,comment_pairs=comment_pairs)
@appointment.route('/search_res')
def search_res():
stars = [4.50, 3.9, 4.30, 4, 3.5]
counts = [155, 56, 100, 80, 56]
session['next'] = request.url # 将当前URL保存到session中
active_page = "date"
q = request.args.get('q', '')
doctors = User.query.filter_by(role_id=1).filter(User.name.ilike('%' + q + '%')).all()
return render_template('main/date.html', active_page=active_page, doctors=doctors,stars=stars,counts=counts)
@appointment.route('/book_date/<int:chosen_docid>') #预约挂号
def book_date(chosen_docid):
session['next'] = request.url # 将当前URL保存到session中
doctor=User.query.filter_by(id=chosen_docid).first()
today = date.today()
workdays = Workday.query.filter(Workday.doc_id == chosen_docid, Workday.date >= today).order_by(Workday.date).limit(6).all()
return render_template('appointment/book_date.html',doctor=doctor,active_date="0",workdays=workdays,actworkdays=workdays)
@appointment.route('/<int:chosen_docid>/<formatted_date>') #预约挂号/具体日期
def book_appointment(chosen_docid,formatted_date):
session['next'] = request.url # 将当前URL保存到session中
doctor=User.query.filter_by(id=chosen_docid).first()
today = date.today()
workdays = Workday.query.filter(Workday.doc_id == chosen_docid, Workday.date >= today).order_by(Workday.date).limit(6).all()
date_obj = datetime.strptime(formatted_date, '%Y-%m-%d').date()
workday=Workday.query.filter_by(doc_id=chosen_docid,date=date_obj).all()
return render_template('appointment/book_date.html', doctor=doctor,active_date=formatted_date,workdays=workdays,actworkdays=workday)
@appointment.route('/<int:chosen_docid>/<formatted_date>/<time>',methods=['GET','POST']) #申请挂号
@permission_required(Permission.COMMENT)
def submit_date(chosen_docid,formatted_date,time):
session['next'] = request.url # 将当前URL保存到session中
doctor = User.query.filter_by(id=chosen_docid).first()
date_obj = datetime.strptime(formatted_date, '%Y-%m-%d').date()
dateday=Workday.query.filter_by(doc_id=chosen_docid,date=date_obj).first()
form = PatientForm()
appointment = Appointment.query.filter_by(patient_id=current_user.id, doc_id=chosen_docid, date=formatted_date,
time=time).first()
if(appointment):
flash("您已经预约过了")
return render_template('appointment/submit_date.html',form=form,doctor=doctor,dateday=dateday,time=time)
else:
if request.method == 'POST' and form.validate_on_submit():
if(time=="0"):
num=dateday.afternoon_num-dateday.temp_afternoon+1
dateday.temp_afternoon-=1
else:
num = dateday.morning_num - dateday.temp_morning + 1
dateday.temp_morning -= 1
appointment = Appointment(cost=dateday.cost,patient_id=current_user.id, doc_id=chosen_docid, date=date_obj,time=int(time),email=form.email.data,
name=form.name.data, id_number=form.id_number.data, location=form.location.data, gender=form.gender.data,
phone=form.phone.data, age=form.age.data,num=num)
db.session.add(dateday)
db.session.add(appointment)
db.session.commit()
flash("你已成功预约!")
return redirect(url_for('appointment.book_date',chosen_docid=doctor.id))
return render_template('appointment/submit_date.html',form=form,doctor=doctor,dateday=dateday,time=time)

View File

@ -0,0 +1,5 @@
from flask import Blueprint
auth = Blueprint('auth', __name__)
from . import views

34
flask_app/auth/forms.py Normal file
View File

@ -0,0 +1,34 @@
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Length, Email, Regexp, EqualTo
from wtforms import ValidationError
from ..models import User
from sqlalchemy import or_
class LoginForm(FlaskForm):
email_or_username = StringField('邮箱/用户名', validators=[DataRequired(), Length(1, 64)])
password = PasswordField('密码', validators=[DataRequired()])
remember_me = BooleanField('保持登录信息') #用于让用户选择是否保持登录状态
submit = SubmitField('登录')
class RegistrationForm(FlaskForm):
email = StringField('邮箱', validators=[DataRequired(), Length(1, 64),
Email()])
username = StringField('用户名', validators=[
DataRequired(), Length(1, 64),
Regexp('^[A-Za-z0-9_\u4e00-\u9fa5\.]*$', 0,
'Usernames must have only letters, numbers, dots or '
'underscores')])
password = PasswordField('密码', validators=[
DataRequired(), EqualTo('password2', message='Passwords must match.')])
password2 = PasswordField('确认密码', validators=[DataRequired()])
submit = SubmitField('注册')
def validate_email(self, field): #用于验证用户输入的邮箱和用户名是否已经在数据库中被使用了
if User.query.filter_by(email=field.data.lower()).first():
raise ValidationError('邮箱已经注册')
def validate_username(self, field):
if User.query.filter_by(username=field.data).first():
raise ValidationError('用户名已被使用')

66
flask_app/auth/views.py Normal file
View File

@ -0,0 +1,66 @@
from flask import render_template, redirect, request, url_for, flash, session
from flask_login import login_user, logout_user, login_required, \
current_user
from flask_app.decorators import permission_required
from . import auth
from .. import db
from ..models import User, Permission
from .forms import LoginForm, RegistrationForm
from sqlalchemy import or_
@auth.before_app_request
def before_request():
if current_user.is_authenticated:
current_user.ping()
@auth.app_errorhandler(403)
def forbidden_error(error):
return render_template('403.html')
@auth.route('/user/')
@permission_required(Permission.COMMENT)
def pleaselogin():
flash("请先登录再访问该页面!")
return redirect(url_for('auth.login'))
@auth.route('/post/')
@permission_required(Permission.COMMENT)
def pleaselogin2():
flash("请先登录再访问该页面!")
return redirect(url_for('auth.login'))
@auth.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter(or_(User.email == form.email_or_username.data.lower(), User.username == form.email_or_username.data)).first()
if user is not None and user.verify_password(form.password.data): #验证成功
login_user(user, form.remember_me.data)
session['color'] = user.avatar_color
next = session.pop('next', None)
if next is None:
next = url_for('main.root')
return redirect(next)
flash('账号或密码错误!')
return render_template('auth/login.html', form=form)
@auth.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('main.index'))
@auth.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
user = User(email=form.email.data.lower(),
username=form.username.data,
password=form.password.data)
user.avatar_color = user.get_random_color()
db.session.add(user)
db.session.commit()
flash('现在可以登录了!')
return redirect(url_for('auth.login'))
return render_template('auth/register.html', form=form)

25
flask_app/decorators.py Normal file
View File

@ -0,0 +1,25 @@
from functools import wraps
from flask import abort, render_template
from flask_login import current_user
from .models import Permission
def permission_required(permission):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not current_user.is_authenticated:
abort(403)
if not current_user.can(permission):
abort(403)
if 'username' in kwargs and kwargs['username'] != current_user.username:
return render_template('error.html')
if 'docid' in kwargs and kwargs['docid'] != str(current_user.id):
return render_template('error.html')
if 'uid' in kwargs and kwargs['uid'] != str(current_user.id):
return render_template('error.html')
return f(*args, **kwargs)
return decorated_function
return decorator
def doctor_required(f):
return permission_required(Permission.DETECT)(f)

View File

@ -0,0 +1,4 @@
from flask import Blueprint
detect = Blueprint('detect', __name__)
from . import views

View File

@ -0,0 +1,208 @@
import math
import tensorflow as tf
from data_utils import exterior_exclusion
def random_rotation(image, max_degrees, bbox=None, prob=0.5):
"""Applies random rotation to image and bbox"""
def _rotation(image, bbox):
# Get random angle
degrees = tf.random.uniform([], minval=-max_degrees, maxval=max_degrees, dtype=tf.float32)
radians = degrees * math.pi / 180.
if bbox is not None:
# Get offset from image center
image_shape = tf.cast(tf.shape(image), tf.float32)
image_height, image_width = image_shape[0], image_shape[1]
bbox = tf.cast(bbox, tf.float32)
center_x = image_width / 2.
center_y = image_height / 2.
bbox_center_x = (bbox[0] + bbox[2]) / 2.
bbox_center_y = (bbox[1] + bbox[3]) / 2.
trans_x = center_x - bbox_center_x
trans_y = center_y - bbox_center_y
# Apply rotation
image = _translate_image(image, trans_x, trans_y)
bbox = _translate_bbox(bbox, image_height, image_width, trans_x, trans_y)
image = tf.contrib.image.rotate(image, radians, interpolation='BILINEAR')
bbox = _rotate_bbox(bbox, image_height, image_width, radians)
image = _translate_image(image, -trans_x, -trans_y)
bbox = _translate_bbox(bbox, image_height, image_width, -trans_x, -trans_y)
bbox = tf.cast(bbox, tf.int32)
return image, bbox
return tf.contrib.image.rotate(image, radians, interpolation='BILINEAR')
retval = image if bbox is None else (image, bbox)
return tf.cond(_should_apply(prob), lambda: _rotation(image, bbox), lambda: retval)
def random_bbox_jitter(bbox, image_height, image_width, max_fraction, prob=0.5):
"""Randomly jitters bbox coordinates by +/- jitter_fraction of the width/height"""
def _bbox_jitter(bbox):
bbox = tf.cast(bbox, tf.float32)
width_jitter = max_fraction*(bbox[2] - bbox[0])
height_jitter = max_fraction*(bbox[3] - bbox[1])
xmin = bbox[0] + tf.random.uniform([], minval=-width_jitter, maxval=width_jitter, dtype=tf.float32)
ymin = bbox[1] + tf.random.uniform([], minval=-height_jitter, maxval=height_jitter, dtype=tf.float32)
xmax = bbox[2] + tf.random.uniform([], minval=-width_jitter, maxval=width_jitter, dtype=tf.float32)
ymax = bbox[3] + tf.random.uniform([], minval=-height_jitter, maxval=height_jitter, dtype=tf.float32)
xmin, ymin, xmax, ymax = _clip_bbox(xmin, ymin, xmax, ymax, image_height, image_width)
bbox = tf.cast(tf.stack([xmin, ymin, xmax, ymax]), tf.int32)
return bbox
return tf.cond(_should_apply(prob), lambda: _bbox_jitter(bbox), lambda: bbox)
def random_shift_and_scale(image, max_shift, max_scale_change, prob=0.5):
"""Applies random shift and scale to pixel values"""
def _shift_and_scale(image):
shift = tf.cast(tf.random.uniform([], minval=-max_shift, maxval=max_shift, dtype=tf.int32), tf.float32)
scale = tf.random.uniform([], minval=(1. - max_scale_change),
maxval=(1. + max_scale_change), dtype=tf.float32)
image = scale*(tf.cast(image, tf.float32) + shift)
image = tf.cast(tf.clip_by_value(image, 0., 255.), tf.uint8)
return image
return tf.cond(_should_apply(prob), lambda: _shift_and_scale(image), lambda: image)
def random_shear(image, max_lambda, bbox=None, prob=0.5):
"""Applies random shear in either the x or y direction"""
shear_lambda = tf.random.uniform([], minval=-max_lambda, maxval=max_lambda, dtype=tf.float32)
image_shape = tf.cast(tf.shape(image), tf.float32)
image_height, image_width = image_shape[0], image_shape[1]
def _shear_x(image, bbox):
image = _shear_x_image(image, shear_lambda)
if bbox is not None:
bbox = _shear_bbox(bbox, image_height, image_width, shear_lambda, horizontal=True)
bbox = tf.cast(bbox, tf.int32)
return image, bbox
return image
def _shear_y(image, bbox):
image = _shear_y_image(image, shear_lambda)
if bbox is not None:
bbox = _shear_bbox(bbox, image_height, image_width, shear_lambda, horizontal=False)
bbox = tf.cast(bbox, tf.int32)
return image, bbox
return image
def _shear(image, bbox):
return tf.cond(_should_apply(0.5), lambda: _shear_x(image, bbox), lambda: _shear_y(image, bbox))
retval = image if bbox is None else (image, bbox)
return tf.cond(_should_apply(prob), lambda: _shear(image, bbox), lambda: retval)
def random_exterior_exclusion(image, prob=0.5):
"""Randomly removes visual features exterior to the patient's body"""
def _exterior_exclusion(image):
shape = image.get_shape()
image = tf.py_func(exterior_exclusion, [image], tf.uint8)
image.set_shape(shape)
return image
return tf.cond(_should_apply(prob), lambda: _exterior_exclusion(image), lambda: image)
def _translate_image(image, delta_x, delta_y):
"""Translate an image"""
return tf.contrib.image.translate(image, [delta_x, delta_y], interpolation='BILINEAR')
def _translate_bbox(bbox, image_height, image_width, delta_x, delta_y):
"""Translate an bbox, ensuring coordinates lie in the image"""
bbox = bbox + tf.stack([delta_x, delta_y, delta_x, delta_y])
xmin, ymin, xmax, ymax = _clip_bbox(bbox[0], bbox[1], bbox[2], bbox[3], image_height, image_width)
bbox = tf.stack([xmin, ymin, xmax, ymax])
return bbox
def _rotate_bbox(bbox, image_height, image_width, radians):
"""Rotates the bbox by the given angle"""
# Shift bbox to origin
xmin, ymin, xmax, ymax = bbox[0], bbox[1], bbox[2], bbox[3]
center_x = (xmin + xmax) / 2.
center_y = (ymin + ymax) / 2.
xmin = xmin - center_x
xmax = xmax - center_x
ymin = ymin - center_y
ymax = ymax - center_y
# Rotate bbox coordinates
radians = -radians # negate direction since y-axis is flipped
coords = tf.stack([[xmin, ymin], [xmax, ymin], [xmin, ymax], [xmax, ymax]])
coords = tf.transpose(tf.cast(coords, tf.float32))
rotation_matrix = tf.stack(
[[tf.cos(radians), -tf.sin(radians)],
[tf.sin(radians), tf.cos(radians)]])
new_coords = tf.matmul(rotation_matrix, coords)
# Find new bbox coordinates and clip to image size
xmin = tf.reduce_min(new_coords[0, :]) + center_x
ymin = tf.reduce_min(new_coords[1, :]) + center_y
xmax = tf.reduce_max(new_coords[0, :]) + center_x
ymax = tf.reduce_max(new_coords[1, :]) + center_y
xmin, ymin, xmax, ymax = _clip_bbox(xmin, ymin, xmax, ymax, image_height, image_width)
bbox = tf.stack([xmin, ymin, xmax, ymax])
return bbox
def _shear_x_image(image, shear_lambda):
"""Shear image in x-direction"""
tform = tf.stack([1., shear_lambda, 0., 0., 1., 0., 0., 0.])
image = tf.contrib.image.transform(
image, tform, interpolation='BILINEAR')
return image
def _shear_y_image(image, shear_lambda):
"""Shear image in y-direction"""
tform = tf.stack([1., 0., 0., shear_lambda, 1., 0., 0., 0.])
image = tf.contrib.image.transform(
image, tform, interpolation='BILINEAR')
return image
def _shear_bbox(bbox, image_height, image_width, shear_lambda, horizontal=True):
"""Shear bbox in x- or y-direction"""
# Shear bbox coordinates
xmin, ymin, xmax, ymax = bbox[0], bbox[1], bbox[2], bbox[3]
coords = tf.stack([[xmin, ymin], [xmax, ymin], [xmin, ymax], [xmax, ymax]])
coords = tf.transpose(tf.cast(coords, tf.float32))
if horizontal:
shear_matrix = tf.stack(
[[1., -shear_lambda],
[0., 1.]])
else:
shear_matrix = tf.stack(
[[1., 0.],
[-shear_lambda, 1.]])
new_coords = tf.matmul(shear_matrix, coords)
# Find new bbox coordinates and clip to image size
xmin = tf.reduce_min(new_coords[0, :])
ymin = tf.reduce_min(new_coords[1, :])
xmax = tf.reduce_max(new_coords[0, :])
ymax = tf.reduce_max(new_coords[1, :])
xmin, ymin, xmax, ymax = _clip_bbox(xmin, ymin, xmax, ymax, image_height, image_width)
bbox = tf.stack([xmin, ymin, xmax, ymax])
return bbox
def _clip_bbox(xmin, ymin, xmax, ymax, image_height, image_width):
"""Clip bbox to valid image coordinates"""
xmin = tf.clip_by_value(xmin, 0, image_width)
ymin = tf.clip_by_value(ymin, 0, image_height)
xmax = tf.clip_by_value(xmax, 0, image_width)
ymax = tf.clip_by_value(ymax, 0, image_height)
return xmin, ymin, xmax, ymax
def _should_apply(prob):
"""Helper function to create bool tensor with probability"""
return tf.cast(tf.floor(tf.random_uniform([], dtype=tf.float32) + prob), tf.bool)

View File

@ -0,0 +1,23 @@
import os
import numpy as np
import tensorflow as tf
class COVIDxCTDataset:
"""COVIDx CT dataset class, which handles construction of train/validation datasets"""
def __init__(self, data_dir, image_height=512, image_width=512):
# General parameters
self.data_dir = data_dir
self.image_height = image_height
self.image_width = image_width
def _make_dataset(self, split_file, batch_size, is_training, balanced=True):
"""Creates COVIDX-CT dataset for train or val split"""
files, classes, bboxes = self._get_files(split_file)
count = len(files)
dataset = tf.data.Dataset.from_tensor_slices((files, classes, bboxes))
dataset = dataset.batch(batch_size)
return dataset, count, batch_size

View File

@ -0,0 +1,94 @@
import os
import cv2
import numpy as np
import tensorflow as tf
from flask_app.detect.ct_dataset import COVIDxCTDataset
IMAGE_INPUT_TENSOR = 'Placeholder:0'
CLASS_PRED_TENSOR = 'ArgMax:0'
CLASS_PROB_TENSOR = 'softmax_tensor:0'
TRAINING_PH_TENSOR = 'is_training:0'
CLASS_NAMES = ('Normal', 'Pneumonia', 'COVID-19')
def create_session():
config = tf.ConfigProto()
config.gpu_options.allow_growth = True #显存按需分配,避免预先分配固定大小的显存造成浪费
sess = tf.Session(config=config)
return sess
class COVIDNetCTRunner:
"""Primary training/testing/inference class"""
def __init__(self, meta_file, ckpt=None, data_dir=None, input_height=512, input_width=512):
self.meta_file = meta_file
self.ckpt = ckpt
self.input_height = input_height
self.input_width = input_width
self.data_dir=data_dir
if data_dir is None:
self.dataset = None
else:
self.dataset = COVIDxCTDataset(
data_dir,
image_height=input_height,
image_width=input_width,
)
def load_graph(self):
"""Creates new graph and session"""
graph = tf.Graph()
with graph.as_default():
# Create session and load model
sess = create_session()
# Load meta file
print('Loading meta graph from ' + self.meta_file)
saver = tf.train.import_meta_graph(self.meta_file, clear_devices=True)
return graph, sess, saver
def load_ckpt(self, sess, saver):
"""Helper for loading weights"""
# Load weights
if self.ckpt is not None:
print('Loading weights from ' + self.ckpt)
saver.restore(sess, self.ckpt)
def infer(self, image_file, autocrop=False):
image = cv2.imread(image_file, cv2.IMREAD_GRAYSCALE)
image = cv2.resize(image, (self.input_width, self.input_height), cv2.INTER_CUBIC)
image = image.astype(np.float32)/255.0
image = np.expand_dims(np.stack((image, image, image), axis=-1), axis=0)
feed_dict = {IMAGE_INPUT_TENSOR: image, TRAINING_PH_TENSOR: False}
graph, sess, saver = self.load_graph()
with graph.as_default():
self.load_ckpt(sess, saver)
try:
sess.graph.get_tensor_by_name(TRAINING_PH_TENSOR)
feed_dict[TRAINING_PH_TENSOR] = False
except KeyError:
pass
class_, probs = sess.run([CLASS_PRED_TENSOR, CLASS_PROB_TENSOR], feed_dict=feed_dict)
pred_type=CLASS_NAMES[class_[0]]
pred_normal = round(probs[0][0],3)
pred_pneu = round(probs[0][1],3)
pred_covid = round(probs[0][2],3)
return pred_type,pred_normal,pred_pneu,pred_covid
def detectct(imagepath):
model_dir="models/COVID-Net_CT-2_L"
meta_name="model.meta"
ckpt_name="model"
input_height=512
input_width=512
meta_file = os.path.join(model_dir, meta_name)
ckpt = os.path.join(model_dir, ckpt_name)
runner = COVIDNetCTRunner(
meta_file=meta_file,
ckpt=ckpt,
input_height=input_height,
input_width=input_width
)
return runner.infer(imagepath)

View File

@ -0,0 +1,32 @@
import tensorflow as tf
import os
import numpy as np
from .processImg import process_image_file
def detectsev_xray(imagepath):
weightspath = "models/COVIDNet-CXR-S"
metaname = "model.meta"
ckptname = "model"
n_classes = "2"
in_tensorname = "input_1:0"
out_tensorname = "norm_dense_2/Softmax:0"
input_size = 480
top_percent = 0.08
mapping = {'轻微': 0, '严重': 1}
inv_mapping = {0: '轻微', 1: '严重'}
mapping_keys = list(mapping.keys())
sess = tf.Session()
tf.get_default_graph()
saver = tf.train.import_meta_graph(os.path.join(weightspath, metaname))
saver.restore(sess, os.path.join(weightspath, ckptname))
graph = tf.get_default_graph()
image_tensor = graph.get_tensor_by_name(in_tensorname)
pred_tensor = graph.get_tensor_by_name(out_tensorname)
x = process_image_file(imagepath, input_size, top_percent=top_percent)
x = x.astype('float32') / 255.0
feed_dict = {image_tensor: np.expand_dims(x, axis=0)}
pred = sess.run(pred_tensor, feed_dict=feed_dict)
pred_type = inv_mapping[pred.argmax(axis=1)[0]]
pred_mild = round(pred[0][mapping['轻微']], 3)
pred_severe = round(pred[0][mapping['严重']], 3)
return pred_type,pred_mild,pred_severe

View File

@ -0,0 +1,32 @@
import tensorflow as tf
import os
import numpy as np
from .processImg import process_image_file
def detectxray(imagepath):
weightspath = "models/COVIDNet-CXR4-A"
metaname = "model.meta"
ckptname = "model-18540"
n_classes = "3"
in_tensorname = "input_1:0"
out_tensorname = "norm_dense_1/Softmax:0"
input_size = 480
top_percent = 0.08
mapping = {'normal': 0, 'pneumonia': 1, 'COVID-19': 2}
inv_mapping = {0: 'normal', 1: 'pneumonia', 2: 'COVID-19'}
mapping_keys = list(mapping.keys())
sess = tf.Session()
tf.get_default_graph()
saver = tf.train.import_meta_graph(os.path.join(weightspath, metaname))
saver.restore(sess, os.path.join(weightspath, ckptname))
graph = tf.get_default_graph()
image_tensor = graph.get_tensor_by_name(in_tensorname)
pred_tensor = graph.get_tensor_by_name(out_tensorname)
x = process_image_file(imagepath, input_size, top_percent=top_percent)
x = x.astype('float32') / 255.0
feed_dict = {image_tensor: np.expand_dims(x, axis=0)}
pred = sess.run(pred_tensor, feed_dict=feed_dict)
prediction = inv_mapping[pred.argmax(axis=1)[0]]
pred_normal = round(pred[0][mapping['normal']], 3)
pred_pneu = round(pred[0][mapping['pneumonia']], 3)
pred_covid = round(pred[0][mapping['COVID-19']], 3)
return prediction,pred_normal,pred_pneu,pred_covid

View File

@ -0,0 +1,16 @@
import cv2
def crop_top(img, percent=0.15): #对图像的上部进行裁剪,去除不必要信息
offset = int(img.shape[0] * percent)
return img[offset:]
def central_crop(img): #裁剪为长宽相等且居中
size = min(img.shape[0], img.shape[1])
offset_h = int((img.shape[0] - size) / 2)
offset_w = int((img.shape[1] - size) / 2)
return img[offset_h:offset_h + size, offset_w:offset_w + size] #切片表示
def process_image_file(filepath, size, top_percent=0.08, crop=True):
img = cv2.imread(filepath) #返回三维列表 长 宽 通道
img = crop_top(img, percent=top_percent)
if crop:
img = central_crop(img)
img = cv2.resize(img, (size, size)) #默认双线性插值 调整长宽大小为size
return img

107
flask_app/detect/views.py Normal file
View File

@ -0,0 +1,107 @@
from flask import Flask, render_template, request, session, redirect, url_for, flash, jsonify
from flask_login import current_user
from flask_app.decorators import permission_required
from flask_app.models import Permission
from .predict_xray import detectxray
from .predict_ct import detectct
from .predict_sevxray import detectsev_xray
import os
from . import detect
@detect.route('/upload.html')
@permission_required(Permission.COMMENT)
def upload():
session['next'] = request.url # 将当前URL保存到session中
active_page = "index"
return render_template('detect/upload.html',active_page=active_page)
@detect.route('/upload_chest.html')
@permission_required(Permission.COMMENT)
def upload_chest():
session['next'] = request.url # 将当前URL保存到session中
return render_template('detect/upload_chest.html')
@detect.route('/upload_ct.html')
@permission_required(Permission.COMMENT)
def upload_ct():
session['next'] = request.url # 将当前URL保存到session中
return render_template('detect/upload_ct.html')
@detect.route('/uploaded_chest/<uid>', methods = ['POST', 'GET'])
def uploaded_chest(uid):
approot=detect.root_path
dir_path = os.path.dirname(approot) #flask_app
if request.method == 'POST':
# check if the post request has the file part
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
if file.filename == '':
flash('No selected file')
return redirect(request.url)
if file:
temp_dir = os.path.join(dir_path, 'static/images/upload_img', str(uid))
if(not os.path.exists(temp_dir)):
os.makedirs(temp_dir)
file.save(os.path.join(temp_dir,'upload_chest.jpg')) #选择的图片保存到指定文件夹
return render_template('detect/predicting.html',
redirect_url=url_for('detect.result_chest'))
@detect.route('/result_chest')
@permission_required(Permission.COMMENT)
def result_chest():
session['next'] = request.url # 将当前URL保存到session中
approot = detect.root_path
dir_path = os.path.dirname(approot)
imagepath = os.path.join(dir_path, 'static/images/upload_img',str(current_user.id),'upload_chest.jpg')
pred_type, pred_normal, pred_pneu, pred_covid = detectxray(imagepath)
return render_template('detect/results_chest.html', pred_type=pred_type, pred_covid=pred_covid, pred_pneu=pred_pneu,
pred_normal=pred_normal)
@detect.route('/uploaded_ct/<uid>', methods = ['POST', 'GET'])
def uploaded_ct(uid):
approot=detect.root_path
dir_path = os.path.dirname(approot)
if request.method == 'POST':
# check if the post request has the file part
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
if file.filename == '':
flash('No selected file')
return redirect(request.url)
if file:
temp_dir = os.path.join(dir_path, 'static/images/upload_img', str(uid))
if (not os.path.exists(temp_dir)):
os.makedirs(temp_dir)
file.save(os.path.join(temp_dir, 'upload_ct.jpg')) # 选择的图片保存到指定文件夹
return render_template('detect/predicting.html',
redirect_url=url_for('detect.result_ct'))
@detect.route('/result_ct')
@permission_required(Permission.COMMENT)
def result_ct():
session['next'] = request.url # 将当前URL保存到session中
approot = detect.root_path
dir_path = os.path.dirname(approot)
imagepath = os.path.join(dir_path, 'static/images/upload_img',str(current_user.id),'upload_ct.jpg')
pred_type,pred_normal,pred_pneu,pred_covid=detectct(imagepath)
return render_template('detect/results_ct.html',pred_type=pred_type,pred_covid=pred_covid,pred_pneu=pred_pneu,pred_normal=pred_normal)
@detect.route('/sev_xray')
def sev_xray():
return render_template('detect/predicting.html',
redirect_url=url_for('detect.result_sevxray'))
@detect.route('/result_sevxray')
@permission_required(Permission.COMMENT)
def result_sevxray():
session['next'] = request.url # 将当前URL保存到session中
approot = detect.root_path
dir_path = os.path.dirname(approot)
imagepath = os.path.join(dir_path, 'static/images/upload_img',str(current_user.id),'upload_chest.jpg')
pred_type,pred_mild,pred_sev=detectsev_xray(imagepath)
return render_template('detect/results_sevxray.html',pred_type=pred_type,pred_mild=pred_mild,pred_sev=pred_sev)

View File

@ -0,0 +1,3 @@
from flask import Blueprint
faqs = Blueprint('faqs', __name__)
from . import views

15
flask_app/faqs/forms.py Normal file
View File

@ -0,0 +1,15 @@
from flask_wtf import FlaskForm
from flask_wtf.file import FileAllowed, FileRequired
from wtforms import StringField, SubmitField, FileField
from wtforms.validators import DataRequired, Length
from sqlalchemy import or_
from flask_pagedown.fields import PageDownField
class CommentForm(FlaskForm):
body=StringField('',validators=[DataRequired()],render_kw={"placeholder": "我来说两句"})
submit=SubmitField('提交')
class PostForm(FlaskForm):
title = StringField('标题', validators=[DataRequired(), Length(min=5, max=50)],render_kw={"placeholder": "一句话概括你的问题"})
content = PageDownField("正文", validators=[DataRequired()],render_kw={"placeholder": "详细描述你的问题"})
images = FileField('插入图片', validators=[FileAllowed(['jpg', 'jpeg', 'png', 'gif'], '只能上传图片!'),
FileRequired('未选择任何文件!')], render_kw={'multiple': True})

114
flask_app/faqs/views.py Normal file
View File

@ -0,0 +1,114 @@
import os
from flask import render_template, request, url_for, flash, jsonify, session
from werkzeug.utils import redirect
from flask_app.decorators import permission_required
from . import faqs
from .. import db
from .forms import PostForm, CommentForm
from flask_login import login_required,current_user
from ..models import User, Post, Comment, Like, Collect, Permission
root_path=faqs.root_path
root_dir=os.path.dirname(root_path)
UPLOAD_FOLDER = os.path.join(root_dir,"static/images")
@faqs.route('/article/<int:id>',methods=['GET','POST'])
@permission_required(Permission.COMMENT)
def article(id):
session['next'] = request.url # 将当前URL保存到session中
post=Post.query.get_or_404(id)
form=CommentForm()
has_liked = Like.query.filter_by(user_id=current_user.id, post_id=id).first() is not None
has_collected = Collect.query.filter_by(user_id=current_user.id, post_id=id).first() is not None
if form.validate_on_submit():
comment=Comment(body=form.body.data,post=post,author=current_user._get_current_object())
post.comment_num+=1
db.session.add(comment)
db.session.add(post)
db.session.commit()
flash('评论发布成功!','success')
form.body.data = ''
return redirect(url_for('faqs.article',id=post.id))
else:
for field, errors in form.errors.items():
for error in errors:
flash(f'Error in {getattr(form, field).label.text}: {error}')
comments = Comment.query.filter_by(post_id=id).all()
authors = [User.query.get(comment.author_id) for comment in comments] #authors指发布评论的人
comment_pairs = zip(comments, authors)
empty_comments = (len(comments) == 0)
return render_template('faqs/article.html',post=post,form=form,comment_pairs=comment_pairs,
empty_comments=empty_comments,has_liked=has_liked,has_collected=has_collected)
@faqs.route('/post/<username>',methods=['GET','POST'])
@permission_required(Permission.COMMENT)
def post(username):
session['next'] = request.url # 将当前URL保存到session中
form = PostForm()
UPLOAD_FOLDER = os.path.join(root_dir, "static/images")
if form.validate_on_submit():
post = Post(title=form.title.data, content=form.content.data,
author=current_user._get_current_object())
img_count = 0
filefolder = os.path.join(UPLOAD_FOLDER, "post", "none")
if not os.path.exists(filefolder):
os.makedirs(filefolder)
for i, file in enumerate(request.files.getlist('images')):
filepath = os.path.join(filefolder, str(i+1)+".jpg")
file.save(filepath)
img_count += 1
post.img_count = img_count
db.session.commit()
new_dir_name=os.path.join(os.path.dirname(filefolder),str(post.id))
os.rename(filefolder,new_dir_name)
flash('您已成功发布一篇文章')
return redirect(url_for('main.faqs'))
return render_template('faqs/post.html', form=form)
@faqs.route('/like/<int:post_id>', methods=['POST', 'DELETE'])
@login_required
def like(post_id):
post = Post.query.get_or_404(post_id)
user = current_user
if request.method == 'POST':
# 处理点赞请求
post.like_num += 1
like = Like(post_id=post_id, user_id=user.id)
db.session.add(like)
db.session.add(post)
db.session.commit()
elif request.method == 'DELETE':
# 处理取消点赞请求
post.like_num -= 1
like = Like.query.filter_by(post_id=post_id, user_id=user.id).first()
db.session.delete(like)
db.session.add(post)
db.session.commit()
# 返回点赞状态
return jsonify()
@faqs.route('/collect/<int:post_id>', methods=['POST', 'DELETE'])
@login_required
def collect(post_id):
post = Post.query.get_or_404(post_id)
user = current_user
if request.method == 'POST':
# 处理点赞请求
post.collect_num += 1
collect = Collect(post_id=post_id, user_id=user.id)
db.session.add(collect)
db.session.add(post)
db.session.commit()
elif request.method == 'DELETE':
# 处理取消点赞请求
post.collect_num -= 1
collect = Like.query.filter_by(post_id=post_id, user_id=user.id).first()
db.session.delete(collect)
db.session.add(post)
db.session.commit()
# 返回点赞状态
return jsonify()

View File

@ -0,0 +1,4 @@
from flask import Blueprint
main = Blueprint('main', __name__)
from . import views

2
flask_app/main/forms.py Normal file
View File

@ -0,0 +1,2 @@

81
flask_app/main/views.py Normal file
View File

@ -0,0 +1,81 @@
import string
from flask import render_template, request, url_for, flash, current_app, session
from flask_app.decorators import permission_required
from . import main
from .. import db
from flask_login import login_required,current_user
from ..models import User, Post, Like, Collect, Feedback, Permission
letters = string.ascii_lowercase
@main.route('/')
def root():
session['next'] = request.url # 将当前URL保存到session中
active_page ="index"
return render_template('main/index.html',active_page=active_page)
@main.route('/index.html')
def index():
session['next'] = request.url # 将当前URL保存到session中
active_page = "index"
return render_template('main/index.html',active_page=active_page)
@main.route('/date.html')
def date():
stars=[4.50,4.40,4.30,4,3.5]
counts=[155,206,100,80,56]
session['next'] = request.url # 将当前URL保存到session中
active_page="date"
doctors = User.query.filter_by(role_id=1).all()
return render_template('main/date.html',active_page=active_page,doctors=doctors,stars=stars,counts=counts)
@main.route('/medical.html')
def medical():
session['next'] = request.url # 将当前URL保存到session中
active_page = "medical"
should_do = ["洗手至少20秒", "出门记得戴口罩", "使用酒精消毒", "擤鼻涕时遮挡口鼻"]
should_no=["惊慌","去人多的地方","与人物理接触","轻易听信谣言"]
howtodo=["正确认识新冠病毒","在家抗原自测怎么做?","不同情况该怎么用药?",
"如何居家隔离?","如何判断要不要去医院?","如何调整心态?","老人感染如何护理?","儿童感染如何护理?"]
feedbacks=Feedback.query.all()
return render_template('main/medical.html',should_do=should_do,should_no=should_no,howtodo=howtodo,feedbacks=feedbacks,active_page=active_page,User=User)
@main.route('/user/<username>')
@permission_required(Permission.COMMENT)
def user(username):
session['next'] = request.url # 将当前URL保存到session中
active_page ="user"
active_page1="basic_info"
user = User.query.filter_by(username=username).first_or_404()
user_initial = user.username[0]
return render_template('main/user.html', user=user,user_initial=user_initial,active_page=active_page,active_page1=active_page1)
@main.route('/doc/<docname>')
@permission_required(Permission.DETECT)
def doc_basic(docname):
session['next'] = request.url # 将当前URL保存到session中
active_page ="user"
active_page1="basic_info"
user = User.query.filter_by(username=docname).first_or_404()
return render_template('main/doc_basic.html', user=user,active_page=active_page,active_page1=active_page1,user_initial=user.username[0])
@main.route('/faqs.html',methods=['GET','POST'])
def faqs():
# posts = Post.query.all()
session['next'] = request.url # 将当前URL保存到session中
active_page ="faqs"
head_1=["新冠症状有哪些?","我们为什么应该居家隔离?","新冠病毒可以入侵你的肺部?","我们如何检测新冠?"]
content_1=["一般症状为发热、乏力、干咳、味觉及嗅觉改变,部分患者起病症状轻微,甚至可恶明显发热","新冠病毒是一种高度传染性病毒,通过空气飞沫、接触传播等途径进行传播。居家隔离可避免医疗资源过度消耗和医疗系统崩溃,减少人员感染风险。",
"新冠病毒是一种呼吸道病毒,主要通过空气飞沫传播,它会通过呼吸道向下移动,进入肺部并感染肺泡和支气管等部位。","1.RT-PCR检测即核酸检测这是目前最常用的检测方法 2.CT扫描和X光"]
head_2=["盐蒸橙子/橘子能治疗感染吗?","转阴后为什么还一直咳嗽?","“阳了”后洗澡会加重病情?","“阳康”后,还要打疫苗吗?"]
content_2=["盐蒸橙子/橘子可以补充维C但不是药不能发挥治疗效果","体内垃圾会变成痰液,通过咳嗽排出去,这是打扫“战场”、修复气道的康复过程。",
"当处于急性高热严重时期,这时身体较虚弱,不建议洗澡;但一般而言,洗澡不会导致新冠症状加重。","在阳康后3个月抗体和免疫记忆会消退至较低水平需要用疫苗重新唤醒。"]
page = request.args.get('page', 1, type=int)
per_page = current_app.config['FLASKY_POSTS_PER_PAGE']
pagination = Post.query.order_by(Post.timestamp.desc()).paginate(
page, per_page, #每页最多显示记录数
error_out=False)
posts = pagination.items
return render_template('main/faqs.html',posts=posts,pagination=pagination,Like=Like,Collect=Collect,
head_1=head_1,head_2=head_2,content_1=content_1,content_2=content_2,active_page=active_page)

232
flask_app/models.py Normal file
View File

@ -0,0 +1,232 @@
from datetime import datetime
import random
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin,AnonymousUserMixin
from . import db, login_manager
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from flask import current_app
class Permission:
FOLLOW = 1
COMMENT = 2
WRITE = 4
DETECT=8
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
default = db.Column(db.Boolean, default=False, index=True)
permissions = db.Column(db.Integer)
users = db.relationship('User', backref='role', lazy='dynamic')
def __init__(self, **kwargs):
super(Role, self).__init__(**kwargs)
if self.permissions is None:
self.permissions = 0
@staticmethod
def insert_roles():
roles = {
'User': [Permission.FOLLOW, Permission.COMMENT, Permission.WRITE],
'Doctor': [Permission.FOLLOW, Permission.COMMENT,
Permission.WRITE, Permission.DETECT,
],
}
default_role = 'User'
for r in roles:
role = Role.query.filter_by(name=r).first()
if role is None:
role = Role(name=r)
role.reset_permissions()
for perm in roles[r]:
role.add_permission(perm)
role.default = (role.name == default_role)
db.session.add(role)
db.session.commit()
def add_permission(self, perm):
if not self.has_permission(perm):
self.permissions += perm
def remove_permission(self, perm):
if self.has_permission(perm):
self.permissions -= perm
def reset_permissions(self):
self.permissions = 0
def has_permission(self, perm):
return self.permissions & perm == perm
def __repr__(self):
return '<Role %r>' % self.name
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(64), unique=True, index=True)
username = db.Column(db.String(64), unique=True, index=True)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
password_hash = db.Column(db.String(128))
name = db.Column(db.String(64))
location = db.Column(db.String(256))
about_me = db.Column(db.Text())
last_seen = db.Column(db.DateTime(), default=datetime.utcnow)
avatar_color = db.Column(db.String(10))
has_avatar = db.Column(db.Boolean, default=False)
sex = db.Column(db.String(5))
phone=db.Column(db.String(15))
age=db.Column(db.Integer)
department=db.Column(db.String(128))
id_number= db.Column(db.String(64))
posts=db.relationship('Post',backref='author',lazy='dynamic') #user表与comment为1对多 即可通过一篇文章获得一个作者(用户)
comments = db.relationship('Comment', backref='author', lazy='dynamic')
def __init__(self, **kwargs):
super(User, self).__init__(**kwargs)
if self.role is None:
default_role = Role.query.filter_by(default=True).first()
self.role = default_role
def can(self, perm):
return self.role is not None and self.role.has_permission(perm)
def is_administrator(self):
return self.can(Permission.ADMIN)
@property
def is_authenticated(self):
return True if self.id else False
@property
def password(self):
raise AttributeError('password is not a readable attribute')
@password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
def verify_password(self, password):
return check_password_hash(self.password_hash, password)
def get_random_color(self):
colors = ['red', 'blue', 'orange','green']
return random.choice(colors)
def ping(self):
self.last_seen = datetime.utcnow()
db.session.add(self)
db.session.commit()
def __repr__(self):
return '<User %r>' % self.username
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.Text, nullable=False)
content = db.Column(db.Text, nullable=False)
img_count = db.Column(db.Integer, default=0)
collect_num = db.Column(db.Integer, default=0)
comment_num = db.Column(db.Integer, default=0)
like_num = db.Column(db.Integer, default=0)
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
author_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) #一个人可以发多个文章,但每篇文章对应一个用户
comments=db.relationship('Comment',backref='post',lazy='dynamic')
class AnonymousUser(AnonymousUserMixin):
def can(self, permissions):
return False
def is_administrator(self):
return False
login_manager.anonymous_user = AnonymousUser
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class Comment(db.Model):
__tablename__ = 'comments'
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.Text)
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
post_id = db.Column(db.Integer, db.ForeignKey('posts.id'))
class DocComment(db.Model):
__tablename__ = 'doccomments'
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.Text)
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
doc_id = db.Column(db.Integer, db.ForeignKey('users.id'))
star_num=db.Column(db.Integer,default=5)
class Like(db.Model):
__tablename__ = 'likes'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
post_id = db.Column(db.Integer, db.ForeignKey('posts.id'))
class Collect(db.Model):
__tablename__ = 'collects'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
post_id = db.Column(db.Integer, db.ForeignKey('posts.id'))
class Feedback(db.Model):
__tablename__='feedbacks'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
feedback=db.Column(db.Text, nullable=False)
class Workday(db.Model):
__tablename__ = 'workday'
id = db.Column(db.Integer, primary_key=True)
doc_id=db.Column(db.Integer, db.ForeignKey('users.id'))
date=db.Column(db.Date, index=True) #工作日期
morning_num=db.Column(db.Integer, default=0)
temp_morning=db.Column(db.Integer, default=0)
afternoon_num=db.Column(db.Integer, default=0)
temp_afternoon = db.Column(db.Integer, default=0)
cost=db.Column(db.Integer, default=30)
class Appointment(db.Model):
__tablename__='appointment'
id = db.Column(db.Integer, primary_key=True)
patient_id = db.Column(db.Integer, db.ForeignKey('users.id'))
doc_id = db.Column(db.Integer, db.ForeignKey('users.id'))
date=db.Column(db.Date, index=True) #预约日期
time=db.Column(db.Integer, default=-1)
email = db.Column(db.String(256), index=True)
id_number = db.Column(db.String(256))
name = db.Column(db.String(256))
gender = db.Column(db.String(256))
location = db.Column(db.String(256))
phone = db.Column(db.String(256))
age = db.Column(db.Integer)
cost=db.Column(db.Integer)
num = db.Column(db.Integer)
class Call_number(db.Model):
__tablename__ = 'call_numbers'
id=db.Column(db.Integer, primary_key=True)
appointment_id = db.Column(db.Integer, db.ForeignKey('appointment.id'), unique=True)
notified = db.Column(db.Boolean, default=False)
call_time=db.Column(db.DateTime, default=datetime.utcnow)
class Report(db.Model):
__tablename__='reports'
id = db.Column(db.Integer, primary_key=True)
appointment_id = db.Column(db.Integer, db.ForeignKey('appointment.id'), unique=True)
diagnosis_result=db.Column(db.String(256))
diagnosis_date=db.Column(db.String(64))
diagnosis_advice=db.Column(db.Text, nullable=False)
diagnosis_sign=db.Column(db.String(64))
class Private_message(db.Model):
__tablename__ = 'private_messages'
id = db.Column(db.Integer, primary_key=True)
sender_id = db.Column(db.Integer, db.ForeignKey('users.id'))
recipient_id= db.Column(db.Integer, db.ForeignKey('users.id'))
body=db.Column(db.Text)
time = db.Column(db.DateTime, default=datetime.now)

3610
flask_app/static/css/animate.min.css vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,157 @@
body {
color: #9ea8c6;
background-color: #051236;
}
.blog-one__content h3,
.funfact-one p,
.block-title h3 {
color: #fff;
}
.about-one__icon-box p {
color: #fefefe;
}
.about-one__content > p {
color: #9ea8c6;
}
.blog-one__content,
.about-one .inner-container {
border-color: rgba(255, 255, 255, 0.1);
}
.testimonials-one,
.blog-one__home,
.about-five,
.service-one {
background-color: #0a1c4f;
}
.testimonials-one::before,
.about-five::before,
.blog-one__home::before,
.service-one::before {
background-image: url(../images/dark/shapes/virus-pattern-1-1.png);
opacity: .15;
background-blend-mode: overlay;
background-color: #0a1c4f;
}
.service-one__inner {
background-color: #051236;
}
.service-one__single:hover .service-one__inner {
background-color: #040f2e;
}
.faq-one .accrodion.active h4,
.service-one__single h3 {
color: #fff;
}
.service-one__single p {
color: #9ea8c6;
}
.progress-one__box,
.service-one__image {
background-color: #040f2e;
}
.blog-one__content,
.service-one__single:hover .service-one__image {
background-color: #051236;
}
.prevention-one__box-top::before {
background-image: url(../images/dark/shapes/prevent-header-shape-1-1.png);
border-color: rgba(255, 255, 255, 0.1);
}
.prevention-one__box-bottom {
background-color: #040f2e;
border-color: rgba(255, 255, 255, 0.1);
}
.brand-one__carousel,
.faq-one .accrodion,
.map-one .inner-container,
.prevention-one__single + .prevention-one__single {
border-color: rgba(255, 255, 255, 0.1);
}
.prevention-one__icon-inner {
background-color: #0a1c4f;
}
.about-two__icon-text h3,
.funfact-two__single i,
.funfact-two__single h3,
.team-one__single h3,
.prevention-one__content h3 {
color: #fff;
}
.testimonials-one__text,
.progress-one__box p,
.prevention-one__content p {
color: #9ea8c6;
}
.faq-one .accrodion {
background-color: #051236;
}
.faq-one .accrodion.active .accrodion-title::before {
color: var(--thm-primary);
}
.team-one__sep,
[class*="col"]:not(:last-of-type) .funfact-two__single::after, [class*="col"]:not(:last-of-type) .funfact-two__single::before {
background-color: rgba(255, 255, 255, 0.1);
}
.slider-one__wrapper::before {
background-image: url(../images/dark/shapes/banner-2-bg-shape.png);
}
.slider-one__video-btn {
border-color: #051236;
}
.symptomps-one__image {
background-color: #0a1c4f;
}
.symptomps-one__single h3 {
background-color: #051236;
border-color: rgba(255, 255, 255, 0.1);
color: #fff;
}
.symptomps-one__single {
background-color: transparent;
}
.funfact-two__single p,
.team-one__single p,
.about-five .about-one__list li,
.about-five__content > p {
color: #9ea8c6;
}
.testimonials-one__title {
color: #0c75d8;
}
.prevention-one__icon::before {
border-color: #040f2e;
}
.testimonials-one__qoute {
border-color: #0a1c4f;
}
/*# sourceMappingURL=dark.css.map */

View File

@ -0,0 +1,9 @@
{
"version": 3,
"mappings": "AAAA,AAAA,IAAI,CAAC;EACD,KAAK,EAAE,OAAO;EACd,gBAAgB,EAAE,OAAO;CAC5B;;AACD,AAAA,kBAAkB,CAAC,EAAE;AACrB,YAAY,CAAC,CAAC;AACd,YAAY,CAAC,EAAE,CAAC;EACZ,KAAK,EAAE,IAAI;CACd;;AACD,AAAA,oBAAoB,CAAC,CAAC,CAAA;EAClB,KAAK,EAAE,OAAO;CACjB;;AAED,AAAA,mBAAmB,GAAG,CAAC,CAAC;EACpB,KAAK,EAAE,OAAO;CACjB;;AACD,AAAA,kBAAkB;AAClB,UAAU,CAAC,gBAAgB,CAAC;EACxB,YAAY,EAAe,wBAAI;CAClC;;AACD,AAAA,iBAAiB;AACjB,eAAe;AACf,WAAW;AACX,YAAY,CAAC;EACT,gBAAgB,EAAE,OAAO;CAC5B;;AACD,AAAA,iBAAiB,AAAA,QAAQ;AACzB,WAAW,AAAA,QAAQ;AACnB,eAAe,AAAA,QAAQ;AACvB,YAAY,AAAA,QAAQ,CAAC;EACjB,gBAAgB,EAAE,gDAAgD;EAClE,OAAO,EAAE,GAAG;EACZ,qBAAqB,EAAE,OAAO;EAC9B,gBAAgB,EAAE,OAAO;CAC5B;;AAED,AAAA,mBAAmB,CAAC;EAChB,gBAAgB,EAAE,OAAO;CAC5B;;AAED,AAAA,oBAAoB,AAAA,MAAM,CAAC,mBAAmB,CAAC;EAC3C,gBAAgB,EAAE,OAAO;CAC5B;;AACD,AAAA,QAAQ,CAAC,UAAU,AAAA,OAAO,CAAC,EAAE;AAC7B,oBAAoB,CAAC,EAAE,CAAC;EACpB,KAAK,EAAE,IAAI;CACd;;AAED,AAAA,oBAAoB,CAAC,CAAC,CAAC;EACnB,KAAK,EAAE,OAAO;CACjB;;AACD,AAAA,kBAAkB;AAClB,mBAAmB,CAAC;EAChB,gBAAgB,EAAE,OAAO;CAC5B;;AACD,AAAA,kBAAkB;AAClB,oBAAoB,AAAA,MAAM,CAAC,mBAAmB,CAAC;EAC3C,gBAAgB,EAAE,OAAO;CAC5B;;AAED,AAAA,wBAAwB,AAAA,QAAQ,CAAC;EAC7B,gBAAgB,EAAE,uDAAuD;EACzE,YAAY,EAAe,wBAAI;CAClC;;AACD,AAAA,2BAA2B,CAAC;EACxB,gBAAgB,EAAE,OAAO;EACzB,YAAY,EAAe,wBAAI;CAClC;;AACD,AAAA,oBAAoB;AACpB,QAAQ,CAAC,UAAU;AACnB,QAAQ,CAAC,gBAAgB;AACzB,uBAAuB,GAAG,uBAAuB,CAAC;EAC9C,YAAY,EAAe,wBAAI;CAClC;;AACD,AAAA,2BAA2B,CAAC;EACxB,gBAAgB,EAAE,OAAO;CAC5B;;AACD,AAAA,qBAAqB,CAAC,EAAE;AACxB,oBAAoB,CAAC,CAAC;AACtB,oBAAoB,CAAC,EAAE;AACvB,iBAAiB,CAAC,EAAE;AACpB,wBAAwB,CAAC,EAAE,CAAC;EACxB,KAAK,EAAE,IAAI;CACd;;AACD,AAAA,uBAAuB;AACvB,kBAAkB,CAAC,CAAC;AACpB,wBAAwB,CAAC,CAAC,CAAC;EACvB,KAAK,EAAE,OAAO;CAEjB;;AAED,AAAA,QAAQ,CAAC,UAAU,CAAC;EAChB,gBAAgB,EAAE,OAAO;CAC5B;;AAED,AAAA,QAAQ,CAAC,UAAU,AAAA,OAAO,CAAC,gBAAgB,AAAA,QAAQ,CAAC;EAChD,KAAK,EAAE,kBAAkB;CAC5B;;AACD,AAAA,cAAc;CACd,AAAA,KAAC,EAAO,KAAK,AAAZ,CAAa,IAAK,CAAA,aAAa,EAAE,oBAAoB,AAAA,OAAO,GAAE,AAAA,KAAC,EAAO,KAAK,AAAZ,CAAa,IAAK,CAAA,aAAa,EAAE,oBAAoB,AAAA,QAAQ,CAAC;EAC1H,gBAAgB,EAAO,wBAAI;CAC9B;;AAED,AAAA,oBAAoB,AAAA,QAAQ,CAAC;EACzB,gBAAgB,EAAE,gDAAgD;CACrE;;AAED,AAAA,sBAAsB,CAAC;EACnB,YAAY,EAAE,OAAO;CACxB;;AAED,AAAA,qBAAqB,CAAC;EAClB,gBAAgB,EAAE,OAAO;CAC5B;;AAED,AAAA,sBAAsB,CAAC,EAAE,CAAC;EACtB,gBAAgB,EAAE,OAAO;EACzB,YAAY,EAAe,wBAAI;EAC/B,KAAK,EAAE,IAAI;CACd;;AAED,AAAA,sBAAsB,CAAC;EACnB,gBAAgB,EAAE,WAAW;CAChC;;AACD,AAAA,oBAAoB,CAAC,CAAC;AACtB,iBAAiB,CAAC,CAAC;AACnB,WAAW,CAAC,gBAAgB,CAAC,EAAE;AAC/B,oBAAoB,GAAG,CAAC,CAAC;EACrB,KAAK,EAAE,OAAO;CACjB;;AAED,AAAA,wBAAwB,CAAC;EACrB,KAAK,EAAE,OAAO;CACjB;;AAED,AAAA,qBAAqB,AAAA,QAAQ,CAAC;EAC1B,YAAY,EAAE,OAAO;CACxB;;AAED,AAAA,wBAAwB,CAAC;EACrB,YAAY,EAAE,OAAO;CACxB",
"sources": [
"../scss/dark.scss"
],
"names": [],
"file": "dark.css"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,6 @@
/**
* Owl Carousel v2.3.4
* Copyright 2013-2018 David Deutsch
* Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE
*/
.owl-carousel,.owl-carousel .owl-item{-webkit-tap-highlight-color:transparent;position:relative}.owl-carousel{display:none;width:100%;z-index:1}.owl-carousel .owl-stage{position:relative;-ms-touch-action:pan-Y;touch-action:manipulation;-moz-backface-visibility:hidden}.owl-carousel .owl-stage:after{content:".";display:block;clear:both;visibility:hidden;line-height:0;height:0}.owl-carousel .owl-stage-outer{position:relative;overflow:hidden;-webkit-transform:translate3d(0,0,0)}.owl-carousel .owl-item,.owl-carousel .owl-wrapper{-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0)}.owl-carousel .owl-item{min-height:1px;float:left;-webkit-backface-visibility:hidden;-webkit-touch-callout:none}.owl-carousel .owl-item img{display:block;width:100%}.owl-carousel .owl-dots.disabled,.owl-carousel .owl-nav.disabled{display:none}.no-js .owl-carousel,.owl-carousel.owl-loaded{display:block}.owl-carousel .owl-dot,.owl-carousel .owl-nav .owl-next,.owl-carousel .owl-nav .owl-prev{cursor:pointer;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.owl-carousel .owl-nav button.owl-next,.owl-carousel .owl-nav button.owl-prev,.owl-carousel button.owl-dot{background:0 0;color:inherit;border:none;padding:0!important;font:inherit}.owl-carousel.owl-loading{opacity:0;display:block}.owl-carousel.owl-hidden{opacity:0}.owl-carousel.owl-refresh .owl-item{visibility:hidden}.owl-carousel.owl-drag .owl-item{-ms-touch-action:pan-y;touch-action:pan-y;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.owl-carousel.owl-grab{cursor:move;cursor:grab}.owl-carousel.owl-rtl{direction:rtl}.owl-carousel.owl-rtl .owl-item{float:right}.owl-carousel .animated{animation-duration:1s;animation-fill-mode:both}.owl-carousel .owl-animated-in{z-index:0}.owl-carousel .owl-animated-out{z-index:1}.owl-carousel .fadeOut{animation-name:fadeOut}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}.owl-height{transition:height .5s ease-in-out}.owl-carousel .owl-item .owl-lazy{opacity:0;transition:opacity .4s ease}.owl-carousel .owl-item .owl-lazy:not([src]),.owl-carousel .owl-item .owl-lazy[src^=""]{max-height:0}.owl-carousel .owl-item img.owl-lazy{transform-style:preserve-3d}.owl-carousel .owl-video-wrapper{position:relative;height:100%;background:#000}.owl-carousel .owl-video-play-icon{position:absolute;height:80px;width:80px;left:50%;top:50%;margin-left:-40px;margin-top:-40px;background:url(owl.video.play.png) no-repeat;cursor:pointer;z-index:1;-webkit-backface-visibility:hidden;transition:transform .1s ease}.owl-carousel .owl-video-play-icon:hover{-ms-transform:scale(1.3,1.3);transform:scale(1.3,1.3)}.owl-carousel .owl-video-playing .owl-video-play-icon,.owl-carousel .owl-video-playing .owl-video-tn{display:none}.owl-carousel .owl-video-tn{opacity:0;height:100%;background-position:center center;background-repeat:no-repeat;background-size:contain;transition:opacity .4s ease}.owl-carousel .owl-video-frame{position:relative;z-index:1;height:100%;width:100%}

View File

@ -0,0 +1,6 @@
/**
* Owl Carousel v2.3.4
* Copyright 2013-2018 David Deutsch
* Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE
*/
.owl-theme .owl-dots,.owl-theme .owl-nav{text-align:center;-webkit-tap-highlight-color:transparent}.owl-theme .owl-nav{margin-top:10px}.owl-theme .owl-nav [class*=owl-]{color:#FFF;font-size:14px;margin:5px;padding:4px 7px;background:#D6D6D6;display:inline-block;cursor:pointer;border-radius:3px}.owl-theme .owl-nav [class*=owl-]:hover{background:#869791;color:#FFF;text-decoration:none}.owl-theme .owl-nav .disabled{opacity:.5;cursor:default}.owl-theme .owl-nav.disabled+.owl-dots{margin-top:10px}.owl-theme .owl-dots .owl-dot{display:inline-block;zoom:1}.owl-theme .owl-dots .owl-dot span{width:10px;height:10px;margin:5px 7px;background:#D6D6D6;display:block;-webkit-backface-visibility:visible;transition:opacity .2s ease;border-radius:30px}.owl-theme .owl-dots .owl-dot.active span,.owl-theme .owl-dots .owl-dot:hover span{background:#869791}

View File

@ -0,0 +1,77 @@
@font-face {
font-family: 'vimns-icons';
src: url('../fonts/vimns-icons.eot?wz00rk');
src: url('../fonts/vimns-icons.eot?wz00rk#iefix') format('embedded-opentype'),
url('../fonts/vimns-icons.ttf?wz00rk') format('truetype'),
url('../fonts/vimns-icons.woff?wz00rk') format('woff'),
url('../fonts/vimns-icons.svg?wz00rk#vimns-icons') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
}
[class^="vimns-icon-"], [class*=" vimns-icon-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'vimns-icons' !important;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.vimns-icon-phone:before {
content: "\e900";
}
.vimns-icon-alert:before {
content: "\e901";
}
.vimns-icon-virus:before {
content: "\e902";
}
.vimns-icon-mask:before {
content: "\e903";
}
.vimns-icon-tick:before {
content: "\e904";
}
.vimns-icon-back:before {
content: "\e905";
}
.vimns-icon-front:before {
content: "\e909";
}
.vimns-icon-screw:before {
content: "\e906";
}
.vimns-icon-work:before {
content: "\e907";
}
.vimns-icon-mail:before {
content: "\e908";
}
.vimns-icon-infected:before {
content: "\e90a";
}
.vimns-icon-washing-hands:before {
content: "\e90b";
}
.vimns-icon-shopping-online:before {
content: "\e90c";
}
.vimns-icon-network:before {
content: "\e90d";
}
.vimns-icon-worldwide:before {
content: "\e90e";
}
.vimns-icon-family:before {
content: "\e90f";
}
.vimns-icon-grave:before {
content: "\e910";
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 678 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Some files were not shown because too many files have changed in this diff Show More