├── Procfile
├── flaskr.db
├── models
├── rfc.sav
└── scaler.pkl
├── static
└── cropped.png
├── README.md
├── schema.sql
├── requirements.txt
├── preprocess.py
├── test.py
├── templates
├── nodisease.html
├── heartdisease.html
├── about.html
├── information.html
└── home.html
├── main_file.py
├── model.py
└── dataset
└── heart.csv
/Procfile:
--------------------------------------------------------------------------------
1 | web: gunicorn app:main_file
2 |
--------------------------------------------------------------------------------
/flaskr.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ashishsalunkhe/HeartDiseaseDiagnosis/master/flaskr.db
--------------------------------------------------------------------------------
/models/rfc.sav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ashishsalunkhe/HeartDiseaseDiagnosis/master/models/rfc.sav
--------------------------------------------------------------------------------
/models/scaler.pkl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ashishsalunkhe/HeartDiseaseDiagnosis/master/models/scaler.pkl
--------------------------------------------------------------------------------
/static/cropped.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ashishsalunkhe/HeartDiseaseDiagnosis/master/static/cropped.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HeartDiseaseDiagnosis
2 | LPIII Mini Project
3 | ---
4 | [](https://heroku.com/deploy)
5 |
6 |
--------------------------------------------------------------------------------
/schema.sql:
--------------------------------------------------------------------------------
1 | drop table if exists entries;
2 | create table entries (
3 | id integer primary key autoincrement,
4 | title text not null,
5 | text text not null
6 | );
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | click==6.7
2 | Flask==1.0.2
3 | itsdangerous==0.24
4 | MarkupSafe==1.0
5 | numpy==1.22.0
6 | pandas==0.23.4
7 | python-dateutil==2.7.3
8 | pytz==2018.5
9 | scikit-learn==0.19.2
10 | six==1.11.0
11 | sklearn==0.0
12 | Werkzeug==0.14.1
13 |
--------------------------------------------------------------------------------
/preprocess.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 |
3 | import numpy as np
4 | import os, time , gc
5 |
6 | path = os.path.dirname(__file__)
7 | path1 = os.path.join(path, 'dataset/heart.csv')
8 |
9 | df = pd.read_csv(path1)
10 |
11 | df = df.replace('?', np.nan)
12 |
13 | col = ['age', 'sex', 'cp', 'trestbps', 'chol', 'fbs', 'restecg',
14 | 'thalach', 'exang', 'oldpeak', 'slope', 'ca', 'thal', 'target']
15 | df.columns = col
16 |
17 | #Checking if there is missing values
18 | print("Size={}\nNumber of missing values".format(df.shape))
19 | print(df.isna().sum())
20 |
21 | #Replacing missing values with the median
22 | df = df.fillna(df.median())
23 | #Removing duplicates
24 | df = df.drop_duplicates()
25 |
26 | #Save preprocessed data
27 | df.to_csv(os.path.join(path, 'dataset/heart.csv'), index=False)
28 |
--------------------------------------------------------------------------------
/test.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from sklearn.externals import joblib
3 | import os
4 | import numpy as np
5 | import pickle
6 | import main_file
7 | class BasicTestCase(unittest.TestCase):
8 |
9 | def test_index(self):
10 | tester = app.test_client(self)
11 | response = tester.get('/', content_type='html/text')
12 | self.assertEqual(response.status_code, 404)
13 |
14 | def test_database(self):
15 | tester = os.path.exists("flaskr.db")
16 | self.assertTrue(tester)
17 |
18 |
19 | import tempfile
20 |
21 |
22 |
23 | class BasicTestCase(unittest.TestCase):
24 |
25 | def test_index(self):
26 | """Initial test: Ensure flask was set up correctly."""
27 | tester = main_file.app.test_client(self)
28 | response = tester.get('/', content_type='html/text')
29 | self.assertEqual(response.status_code, 200)
30 |
31 | def test_database(self):
32 | """Initial test: Ensure that the database exists."""
33 | tester = os.path.exists("flaskr.db")
34 | self.assertEqual(tester, True)
35 |
36 |
37 |
38 |
39 | class TestModelResponse(unittest.TestCase):
40 | def test_true(self):
41 | x = np.array([67,1,2,152,212,0,0,150,0,0.8,1,0,3]).reshape(1, -1)
42 |
43 | scaler_path = os.path.join(os.path.dirname(__file__), 'models/scaler.pkl')
44 | scaler = None
45 | with open(scaler_path, 'rb') as f:
46 | scaler = pickle.load(f)
47 |
48 | x = scaler.transform(x)
49 |
50 | model_path = os.path.join(os.path.dirname(__file__), 'models/rfc.sav')
51 | clf = joblib.load(model_path)
52 |
53 | y = clf.predict(x)
54 | print(y)
55 | self.assertEqual(y, 1)
56 |
57 | def test_false(self):
58 | x = np.array([63, 1, 3, 145, 233, 1, 0, 150, 0, 2.3, 0, 0, 1]).reshape(1, -1)
59 |
60 | scaler_path = os.path.join(os.path.dirname(__file__), 'models/scaler.pkl')
61 | scaler = None
62 | with open(scaler_path, 'rb') as f:
63 | scaler = pickle.load(f)
64 |
65 | x = scaler.transform(x)
66 |
67 | model_path = os.path.join(os.path.dirname(__file__), 'models/rfc.sav')
68 | clf = joblib.load(model_path)
69 |
70 | y = clf.predict(x)
71 | print(y)
72 | self.assertEqual(y, 1)
73 |
74 |
75 | if __name__ == '__main__':
76 | unittest.main()
77 |
--------------------------------------------------------------------------------
/templates/nodisease.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Diagnosis
11 |
12 |
13 | Heart Disease Prediction
14 |
15 |
16 |
17 |
18 |
19 |
20 | About
21 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
You don't have a heart disease !
33 |
The ML algorithm has diagnosed you with no heart disease based on your inputs. However you still have to go to a doctor just to be safe.
34 |
35 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/templates/heartdisease.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Diagnosis
11 |
12 |
13 | Heart Disease Prediction
14 |
15 |
16 |
17 |
28 |
29 |
30 |
31 |
32 |
We are sorry to inform you that you probably have a heart disease.
33 |
You might have to book an appointment with a doctor today!
34 |
Take extra good care!
35 |
36 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/templates/about.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | About
11 |
12 |
13 |
14 | Heart Disease Prediction
15 |
16 |
17 |
18 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
Heart Disease Diagnosis
37 |
38 |
Correctly diagnosing diseases takes years of medical training. Even then, diagnostics is often an arduous, time-consuming process. In many fields, the demand for experts far exceeds the available supply. This puts doctors under strain and often delays life-saving patient diagnostics.
39 |
40 | Machine Learning (ML) have recently made huge advances in automatically diagnosing diseases, making diagnostics cheaper and more accessible.
41 |
42 |
This app predicts wether you have or not a heart disease using Machine Learning (ML).
43 |
You still have to go to a doctor just to be safe :) !
44 |
45 |
46 |
55 |
56 |
57 |
58 |
59 |
60 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/templates/information.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | About
11 |
12 |
13 |
14 | Heart Disease Prediction
15 |
16 |
17 |
18 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
Heart Disease Diagnosis
37 |
38 |
Correctly diagnosing diseases takes years of medical training. Even then, diagnostics is often an arduous, time-consuming process. In many fields, the demand for experts far exceeds the available supply. This puts doctors under strain and often delays life-saving patient diagnostics.
39 |
40 | Machine Learning (ML) have recently made huge advances in automatically diagnosing diseases, making diagnostics cheaper and more accessible.
41 |
42 |
This app predicts wether you have or not a heart disease using Machine Learning (ML).
43 |
You still have to go to a doctor just to be safe :) !
44 |
45 |
46 |
47 |
48 |
What can I do to lower my risk of heart disease?
49 |
50 |
51 |
52 | Control your blood pressure.
53 | Keep your cholesterol and triglyceride levels under control.
54 | Stay at a healthy weight.
55 | Eat a healthy diet.
56 | Get regular exercise.
57 | Limit alcohol.
58 | Don't smoke.
59 | Manage stress.
60 | Manage diabetes.
61 | Make sure that you get enough sleep.
62 |
63 |
64 |
73 |
74 |
75 |
76 |
77 |
78 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/main_file.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, render_template, url_for, session, request, flash, jsonify, g, redirect
2 | from sklearn.externals import joblib
3 | import os
4 | import numpy as np
5 | import pickle
6 |
7 | import sqlite3
8 |
9 | # configuration
10 | DATABASE = 'flaskr.db'
11 | DEBUG = True
12 | SECRET_KEY = 'liberrex'
13 | USERNAME = 'admin'
14 | PASSWORD = 'admin'
15 | # create and initialize app
16 |
17 |
18 | app = Flask(__name__, static_folder='static')
19 |
20 | app.config.from_object(__name__)
21 |
22 |
23 | # connect to database
24 | def connect_db():
25 | """Connects to the database."""
26 | rv = sqlite3.connect(app.config['DATABASE'])
27 | rv.row_factory = sqlite3.Row
28 | return rv
29 |
30 |
31 | # create the database
32 | def init_db():
33 | with app.app_context():
34 | db = get_db()
35 | with app.open_resource('schema.sql', mode='r') as f:
36 | db.cursor().executescript(f.read())
37 | db.commit()
38 |
39 |
40 | # open database connection
41 | def get_db():
42 | if not hasattr(g, 'sqlite_db'):
43 | g.sqlite_db = connect_db()
44 | return g.sqlite_db
45 |
46 |
47 | @app.route('/information')
48 | def show_entries():
49 | """Searches the database for entries, then displays them."""
50 | db = get_db()
51 | cur = db.execute('select * from entries order by id desc')
52 | entries = cur.fetchall()
53 | return render_template('information.html', entries=entries)
54 |
55 |
56 | # close database connection
57 |
58 | @app.teardown_appcontext
59 | def close_db(error):
60 | if hasattr(g, 'sqlite_db'):
61 | g.sqlite_db.close()
62 |
63 |
64 | @app.route('/information', methods=['GET', 'POST'])
65 | def login():
66 | """User login/authentication/session management."""
67 | error = None
68 | if request.method == 'POST':
69 | if request.form['username'] != app.config['USERNAME']:
70 | error = 'Invalid username'
71 | elif request.form['password'] != app.config['PASSWORD']:
72 | error = 'Invalid password'
73 | else:
74 | session['logged_in'] = True
75 | flash('You were logged in')
76 | return redirect(url_for('home'))
77 | return render_template('information.html', error=error)
78 |
79 |
80 | @app.route('/logout')
81 | def logout():
82 | """User logout/authentication/session management."""
83 | session.pop('logged_in', None)
84 | flash('You were logged out')
85 | return redirect(url_for('login'))
86 |
87 |
88 | @app.route('/add', methods=['POST'])
89 | def add_entry():
90 | """Add new post to database."""
91 | if not session.get('logged_in'):
92 | abort(401)
93 | db = get_db()
94 | db.execute(
95 | 'insert into entries (title, text) values (?, ?)',
96 | [request.form['title'], request.form['text']]
97 | )
98 | db.commit()
99 | flash('New entry was successfully posted')
100 | return redirect(url_for('login'))
101 |
102 |
103 | @app.route("/")
104 | def index():
105 | return render_template('home.html')
106 |
107 |
108 | @app.route('/result', methods=['POST', 'GET'])
109 | def result():
110 | age = int(request.form['age'])
111 | sex = int(request.form['sex'])
112 | trestbps = float(request.form['trestbps'])
113 | chol = float(request.form['chol'])
114 | restecg = float(request.form['restecg'])
115 | thalach = float(request.form['thalach'])
116 | exang = int(request.form['exang'])
117 | cp = int(request.form['cp'])
118 | fbs = float(request.form['fbs'])
119 | oldpeak = float(request.form['oldpeak'])
120 | slope = float(request.form['slope'])
121 | ca = int(request.form['ca'])
122 | thal = int(request.form['thal'])
123 |
124 | x = np.array([age, sex, cp, trestbps, chol, fbs, restecg,
125 | thalach, exang, oldpeak, slope, ca, thal]).reshape(1, -1)
126 |
127 | scaler_path = os.path.join(os.path.dirname(__file__), 'models/scaler.pkl')
128 | scaler = None
129 | with open(scaler_path, 'rb') as f:
130 | scaler = pickle.load(f)
131 |
132 | x = scaler.transform(x)
133 |
134 | model_path = os.path.join(os.path.dirname(__file__), 'models/rfc.sav')
135 | clf = joblib.load(model_path)
136 |
137 | y = clf.predict(x)
138 | print(y)
139 |
140 | # No heart disease
141 | if y == 0:
142 | return render_template('nodisease.html')
143 |
144 | # y=1 heart disease
145 | else:
146 | return render_template('heartdisease.html')
147 |
148 |
149 | @app.route('/home')
150 | def home():
151 | return render_template('home.html')
152 |
153 |
154 | if __name__ == "__main__":
155 | init_db()
156 |
157 | app.run(debug=True)
158 |
--------------------------------------------------------------------------------
/model.py:
--------------------------------------------------------------------------------
1 | from sklearn.svm import SVC, LinearSVC
2 | from sklearn.preprocessing import MinMaxScaler
3 | from sklearn.ensemble import RandomForestClassifier
4 | from sklearn.naive_bayes import GaussianNB
5 | from sklearn.externals import joblib
6 | import numpy as np
7 | import pandas as pd
8 | import pickle
9 | import os, gc, time
10 | from sklearn.metrics import accuracy_score
11 | from sklearn.model_selection import train_test_split
12 |
13 | # Feature creation libraries
14 | from sklearn.random_projection import SparseRandomProjection as sr # Projection features
15 | from sklearn.cluster import KMeans # Cluster features
16 | from sklearn.preprocessing import PolynomialFeatures # Interaction features
17 |
18 | root = os.path.dirname(__file__)
19 | path_df = os.path.join(root, 'dataset/heart.csv')
20 | data = pd.read_csv(path_df)
21 |
22 | scaler = MinMaxScaler()
23 | train, test = train_test_split(data, test_size=0.25)
24 |
25 | X_train = train.drop('target', axis=1)
26 | x_train = train.drop('target', axis=1)
27 | Y_train = train['target']
28 | y_train = train['target']
29 | X_test = test.drop('target', axis=1)
30 | x_test = test.drop('target', axis=1)
31 | Y_test = test['target']
32 | y_test = test['target']
33 |
34 | """Feature engineering """
35 | tmp_train = X_train
36 | tmp_test = X_test
37 | ##using statistical methods
38 | feat = ["var", "median", "mean", "std", "max", "min"]
39 | for i in feat:
40 | X_train[i] = tmp_train.aggregate(i, axis=1)
41 | X_test[i] = tmp_test.aggregate(i, axis=1)
42 | # Delete not needed variables and release memory
43 | del (tmp_train)
44 | del (tmp_test)
45 | gc.collect()
46 | # So what do we have finally
47 | X_train.shape
48 | X_train.head(1)
49 | X_test.shape
50 | X_test.head(2)
51 | target = Y_train
52 | colNames = X_train.columns.values
53 |
54 | ##using random projections
55 | tmp = pd.concat([X_train, X_test],
56 | axis=0,
57 | ignore_index=True
58 | )
59 | NUM_OF_COM = 6
60 |
61 | rp_instance = sr(n_components=NUM_OF_COM)
62 | print(rp_instance)
63 | rp = rp_instance.fit_transform(tmp.iloc[:, :13])
64 | rp_col_names = ["r" + str(i) for i in range(6)]
65 |
66 | ##using Polynomials
67 | degree = 2
68 | poly = PolynomialFeatures(degree,
69 | interaction_only=True,
70 | include_bias=False)
71 | df = poly.fit_transform(tmp.iloc[:, : 8])
72 | poly_names = ["poly" + str(i) for i in range(36)]
73 |
74 | # concatenate all features
75 | tmp = np.hstack([tmp, rp, df])
76 | tmp.shape
77 | X = tmp
78 | del tmp
79 | gc.collect()
80 | y = pd.concat([Y_train, Y_test],
81 | axis=0,
82 | ignore_index=True
83 | )
84 |
85 | # Data scaling
86 | # We don't scale targets: Y_test, Y_train as SVC returns the class labels not probability values
87 | x_train = scaler.fit_transform(x_train)
88 | x_test = scaler.fit_transform(x_test)
89 |
90 | """ Building the model"""
91 |
92 | from sklearn.model_selection import RandomizedSearchCV
93 |
94 | # HP tuning using grid search
95 | rf_param_grid = {
96 | 'max_depth': [4, 6, 8, 10],
97 | 'n_estimators': range(1, 30),
98 | 'max_features': ['sqrt', 'auto', 'log2'],
99 | 'min_samples_split': [2, 3, 10, 20],
100 | 'min_samples_leaf': [1, 3, 10, 18],
101 | 'bootstrap': [True, False],
102 |
103 | }
104 |
105 | rfc = RandomForestClassifier()
106 | models = RandomizedSearchCV(param_distributions=rf_param_grid,
107 | estimator=rfc, scoring="accuracy",
108 | verbose=0, n_iter=100, cv=5)
109 | # Fitting the models
110 | models.fit(x_train, y_train)
111 |
112 | par = models.best_params_
113 | best_model = RandomForestClassifier(n_estimators=par["n_estimators"],
114 | min_samples_split=par['min_samples_split'],
115 | min_samples_leaf=par['min_samples_leaf'],
116 | max_features=par['max_features'],
117 | max_depth=par['max_depth'],
118 | bootstrap=par['bootstrap'])
119 |
120 | # Training the best classifier
121 |
122 | best_model.fit(x_train, y_train)
123 |
124 | # making predictions
125 |
126 | y_pred = best_model.predict(x_test)
127 |
128 | # Evaluating model accuracy
129 | from sklearn import metrics
130 |
131 | print("Accuracy:", metrics.accuracy_score(y_test, y_pred))
132 |
133 | # Saving the trained model for inference
134 | model_path = os.path.join(root, 'models/rfc.sav')
135 | joblib.dump(best_model, model_path)
136 |
137 | # Saving the scaler object
138 | scaler_path = os.path.join(root, 'models/scaler.pkl')
139 | with open(scaler_path, 'wb') as scaler_file:
140 | pickle.dump(scaler, scaler_file)
141 |
--------------------------------------------------------------------------------
/templates/home.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Heart Disease Prediction
11 |
12 |
13 | Heart Disease Prediction
14 |
15 |
16 |
17 |
25 |
26 |
27 |
28 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
197 |
198 |
--------------------------------------------------------------------------------
/dataset/heart.csv:
--------------------------------------------------------------------------------
1 | age,sex,cp,trestbps,chol,fbs,restecg,thalach,exang,oldpeak,slope,ca,thal,target
2 | 63,1,3,145,233,1,0,150,0,2.3,0,0,1,1
3 | 37,1,2,130,250,0,1,187,0,3.5,0,0,2,1
4 | 41,0,1,130,204,0,0,172,0,1.4,2,0,2,1
5 | 56,1,1,120,236,0,1,178,0,0.8,2,0,2,1
6 | 57,0,0,120,354,0,1,163,1,0.6,2,0,2,1
7 | 57,1,0,140,192,0,1,148,0,0.4,1,0,1,1
8 | 56,0,1,140,294,0,0,153,0,1.3,1,0,2,1
9 | 44,1,1,120,263,0,1,173,0,0,2,0,3,1
10 | 52,1,2,172,199,1,1,162,0,0.5,2,0,3,1
11 | 57,1,2,150,168,0,1,174,0,1.6,2,0,2,1
12 | 54,1,0,140,239,0,1,160,0,1.2,2,0,2,1
13 | 48,0,2,130,275,0,1,139,0,0.2,2,0,2,1
14 | 49,1,1,130,266,0,1,171,0,0.6,2,0,2,1
15 | 64,1,3,110,211,0,0,144,1,1.8,1,0,2,1
16 | 58,0,3,150,283,1,0,162,0,1,2,0,2,1
17 | 50,0,2,120,219,0,1,158,0,1.6,1,0,2,1
18 | 58,0,2,120,340,0,1,172,0,0,2,0,2,1
19 | 66,0,3,150,226,0,1,114,0,2.6,0,0,2,1
20 | 43,1,0,150,247,0,1,171,0,1.5,2,0,2,1
21 | 69,0,3,140,239,0,1,151,0,1.8,2,2,2,1
22 | 59,1,0,135,234,0,1,161,0,0.5,1,0,3,1
23 | 44,1,2,130,233,0,1,179,1,0.4,2,0,2,1
24 | 42,1,0,140,226,0,1,178,0,0,2,0,2,1
25 | 61,1,2,150,243,1,1,137,1,1,1,0,2,1
26 | 40,1,3,140,199,0,1,178,1,1.4,2,0,3,1
27 | 71,0,1,160,302,0,1,162,0,0.4,2,2,2,1
28 | 59,1,2,150,212,1,1,157,0,1.6,2,0,2,1
29 | 51,1,2,110,175,0,1,123,0,0.6,2,0,2,1
30 | 65,0,2,140,417,1,0,157,0,0.8,2,1,2,1
31 | 53,1,2,130,197,1,0,152,0,1.2,0,0,2,1
32 | 41,0,1,105,198,0,1,168,0,0,2,1,2,1
33 | 65,1,0,120,177,0,1,140,0,0.4,2,0,3,1
34 | 44,1,1,130,219,0,0,188,0,0,2,0,2,1
35 | 54,1,2,125,273,0,0,152,0,0.5,0,1,2,1
36 | 51,1,3,125,213,0,0,125,1,1.4,2,1,2,1
37 | 46,0,2,142,177,0,0,160,1,1.4,0,0,2,1
38 | 54,0,2,135,304,1,1,170,0,0,2,0,2,1
39 | 54,1,2,150,232,0,0,165,0,1.6,2,0,3,1
40 | 65,0,2,155,269,0,1,148,0,0.8,2,0,2,1
41 | 65,0,2,160,360,0,0,151,0,0.8,2,0,2,1
42 | 51,0,2,140,308,0,0,142,0,1.5,2,1,2,1
43 | 48,1,1,130,245,0,0,180,0,0.2,1,0,2,1
44 | 45,1,0,104,208,0,0,148,1,3,1,0,2,1
45 | 53,0,0,130,264,0,0,143,0,0.4,1,0,2,1
46 | 39,1,2,140,321,0,0,182,0,0,2,0,2,1
47 | 52,1,1,120,325,0,1,172,0,0.2,2,0,2,1
48 | 44,1,2,140,235,0,0,180,0,0,2,0,2,1
49 | 47,1,2,138,257,0,0,156,0,0,2,0,2,1
50 | 53,0,2,128,216,0,0,115,0,0,2,0,0,1
51 | 53,0,0,138,234,0,0,160,0,0,2,0,2,1
52 | 51,0,2,130,256,0,0,149,0,0.5,2,0,2,1
53 | 66,1,0,120,302,0,0,151,0,0.4,1,0,2,1
54 | 62,1,2,130,231,0,1,146,0,1.8,1,3,3,1
55 | 44,0,2,108,141,0,1,175,0,0.6,1,0,2,1
56 | 63,0,2,135,252,0,0,172,0,0,2,0,2,1
57 | 52,1,1,134,201,0,1,158,0,0.8,2,1,2,1
58 | 48,1,0,122,222,0,0,186,0,0,2,0,2,1
59 | 45,1,0,115,260,0,0,185,0,0,2,0,2,1
60 | 34,1,3,118,182,0,0,174,0,0,2,0,2,1
61 | 57,0,0,128,303,0,0,159,0,0,2,1,2,1
62 | 71,0,2,110,265,1,0,130,0,0,2,1,2,1
63 | 54,1,1,108,309,0,1,156,0,0,2,0,3,1
64 | 52,1,3,118,186,0,0,190,0,0,1,0,1,1
65 | 41,1,1,135,203,0,1,132,0,0,1,0,1,1
66 | 58,1,2,140,211,1,0,165,0,0,2,0,2,1
67 | 35,0,0,138,183,0,1,182,0,1.4,2,0,2,1
68 | 51,1,2,100,222,0,1,143,1,1.2,1,0,2,1
69 | 45,0,1,130,234,0,0,175,0,0.6,1,0,2,1
70 | 44,1,1,120,220,0,1,170,0,0,2,0,2,1
71 | 62,0,0,124,209,0,1,163,0,0,2,0,2,1
72 | 54,1,2,120,258,0,0,147,0,0.4,1,0,3,1
73 | 51,1,2,94,227,0,1,154,1,0,2,1,3,1
74 | 29,1,1,130,204,0,0,202,0,0,2,0,2,1
75 | 51,1,0,140,261,0,0,186,1,0,2,0,2,1
76 | 43,0,2,122,213,0,1,165,0,0.2,1,0,2,1
77 | 55,0,1,135,250,0,0,161,0,1.4,1,0,2,1
78 | 51,1,2,125,245,1,0,166,0,2.4,1,0,2,1
79 | 59,1,1,140,221,0,1,164,1,0,2,0,2,1
80 | 52,1,1,128,205,1,1,184,0,0,2,0,2,1
81 | 58,1,2,105,240,0,0,154,1,0.6,1,0,3,1
82 | 41,1,2,112,250,0,1,179,0,0,2,0,2,1
83 | 45,1,1,128,308,0,0,170,0,0,2,0,2,1
84 | 60,0,2,102,318,0,1,160,0,0,2,1,2,1
85 | 52,1,3,152,298,1,1,178,0,1.2,1,0,3,1
86 | 42,0,0,102,265,0,0,122,0,0.6,1,0,2,1
87 | 67,0,2,115,564,0,0,160,0,1.6,1,0,3,1
88 | 68,1,2,118,277,0,1,151,0,1,2,1,3,1
89 | 46,1,1,101,197,1,1,156,0,0,2,0,3,1
90 | 54,0,2,110,214,0,1,158,0,1.6,1,0,2,1
91 | 58,0,0,100,248,0,0,122,0,1,1,0,2,1
92 | 48,1,2,124,255,1,1,175,0,0,2,2,2,1
93 | 57,1,0,132,207,0,1,168,1,0,2,0,3,1
94 | 52,1,2,138,223,0,1,169,0,0,2,4,2,1
95 | 54,0,1,132,288,1,0,159,1,0,2,1,2,1
96 | 45,0,1,112,160,0,1,138,0,0,1,0,2,1
97 | 53,1,0,142,226,0,0,111,1,0,2,0,3,1
98 | 62,0,0,140,394,0,0,157,0,1.2,1,0,2,1
99 | 52,1,0,108,233,1,1,147,0,0.1,2,3,3,1
100 | 43,1,2,130,315,0,1,162,0,1.9,2,1,2,1
101 | 53,1,2,130,246,1,0,173,0,0,2,3,2,1
102 | 42,1,3,148,244,0,0,178,0,0.8,2,2,2,1
103 | 59,1,3,178,270,0,0,145,0,4.2,0,0,3,1
104 | 63,0,1,140,195,0,1,179,0,0,2,2,2,1
105 | 42,1,2,120,240,1,1,194,0,0.8,0,0,3,1
106 | 50,1,2,129,196,0,1,163,0,0,2,0,2,1
107 | 68,0,2,120,211,0,0,115,0,1.5,1,0,2,1
108 | 69,1,3,160,234,1,0,131,0,0.1,1,1,2,1
109 | 45,0,0,138,236,0,0,152,1,0.2,1,0,2,1
110 | 50,0,1,120,244,0,1,162,0,1.1,2,0,2,1
111 | 50,0,0,110,254,0,0,159,0,0,2,0,2,1
112 | 64,0,0,180,325,0,1,154,1,0,2,0,2,1
113 | 57,1,2,150,126,1,1,173,0,0.2,2,1,3,1
114 | 64,0,2,140,313,0,1,133,0,0.2,2,0,3,1
115 | 43,1,0,110,211,0,1,161,0,0,2,0,3,1
116 | 55,1,1,130,262,0,1,155,0,0,2,0,2,1
117 | 37,0,2,120,215,0,1,170,0,0,2,0,2,1
118 | 41,1,2,130,214,0,0,168,0,2,1,0,2,1
119 | 56,1,3,120,193,0,0,162,0,1.9,1,0,3,1
120 | 46,0,1,105,204,0,1,172,0,0,2,0,2,1
121 | 46,0,0,138,243,0,0,152,1,0,1,0,2,1
122 | 64,0,0,130,303,0,1,122,0,2,1,2,2,1
123 | 59,1,0,138,271,0,0,182,0,0,2,0,2,1
124 | 41,0,2,112,268,0,0,172,1,0,2,0,2,1
125 | 54,0,2,108,267,0,0,167,0,0,2,0,2,1
126 | 39,0,2,94,199,0,1,179,0,0,2,0,2,1
127 | 34,0,1,118,210,0,1,192,0,0.7,2,0,2,1
128 | 47,1,0,112,204,0,1,143,0,0.1,2,0,2,1
129 | 67,0,2,152,277,0,1,172,0,0,2,1,2,1
130 | 52,0,2,136,196,0,0,169,0,0.1,1,0,2,1
131 | 74,0,1,120,269,0,0,121,1,0.2,2,1,2,1
132 | 54,0,2,160,201,0,1,163,0,0,2,1,2,1
133 | 49,0,1,134,271,0,1,162,0,0,1,0,2,1
134 | 42,1,1,120,295,0,1,162,0,0,2,0,2,1
135 | 41,1,1,110,235,0,1,153,0,0,2,0,2,1
136 | 41,0,1,126,306,0,1,163,0,0,2,0,2,1
137 | 49,0,0,130,269,0,1,163,0,0,2,0,2,1
138 | 60,0,2,120,178,1,1,96,0,0,2,0,2,1
139 | 62,1,1,128,208,1,0,140,0,0,2,0,2,1
140 | 57,1,0,110,201,0,1,126,1,1.5,1,0,1,1
141 | 64,1,0,128,263,0,1,105,1,0.2,1,1,3,1
142 | 51,0,2,120,295,0,0,157,0,0.6,2,0,2,1
143 | 43,1,0,115,303,0,1,181,0,1.2,1,0,2,1
144 | 42,0,2,120,209,0,1,173,0,0,1,0,2,1
145 | 67,0,0,106,223,0,1,142,0,0.3,2,2,2,1
146 | 76,0,2,140,197,0,2,116,0,1.1,1,0,2,1
147 | 70,1,1,156,245,0,0,143,0,0,2,0,2,1
148 | 44,0,2,118,242,0,1,149,0,0.3,1,1,2,1
149 | 60,0,3,150,240,0,1,171,0,0.9,2,0,2,1
150 | 44,1,2,120,226,0,1,169,0,0,2,0,2,1
151 | 42,1,2,130,180,0,1,150,0,0,2,0,2,1
152 | 66,1,0,160,228,0,0,138,0,2.3,2,0,1,1
153 | 71,0,0,112,149,0,1,125,0,1.6,1,0,2,1
154 | 64,1,3,170,227,0,0,155,0,0.6,1,0,3,1
155 | 66,0,2,146,278,0,0,152,0,0,1,1,2,1
156 | 39,0,2,138,220,0,1,152,0,0,1,0,2,1
157 | 58,0,0,130,197,0,1,131,0,0.6,1,0,2,1
158 | 47,1,2,130,253,0,1,179,0,0,2,0,2,1
159 | 35,1,1,122,192,0,1,174,0,0,2,0,2,1
160 | 58,1,1,125,220,0,1,144,0,0.4,1,4,3,1
161 | 56,1,1,130,221,0,0,163,0,0,2,0,3,1
162 | 56,1,1,120,240,0,1,169,0,0,0,0,2,1
163 | 55,0,1,132,342,0,1,166,0,1.2,2,0,2,1
164 | 41,1,1,120,157,0,1,182,0,0,2,0,2,1
165 | 38,1,2,138,175,0,1,173,0,0,2,4,2,1
166 | 38,1,2,138,175,0,1,173,0,0,2,4,2,1
167 | 67,1,0,160,286,0,0,108,1,1.5,1,3,2,0
168 | 67,1,0,120,229,0,0,129,1,2.6,1,2,3,0
169 | 62,0,0,140,268,0,0,160,0,3.6,0,2,2,0
170 | 63,1,0,130,254,0,0,147,0,1.4,1,1,3,0
171 | 53,1,0,140,203,1,0,155,1,3.1,0,0,3,0
172 | 56,1,2,130,256,1,0,142,1,0.6,1,1,1,0
173 | 48,1,1,110,229,0,1,168,0,1,0,0,3,0
174 | 58,1,1,120,284,0,0,160,0,1.8,1,0,2,0
175 | 58,1,2,132,224,0,0,173,0,3.2,2,2,3,0
176 | 60,1,0,130,206,0,0,132,1,2.4,1,2,3,0
177 | 40,1,0,110,167,0,0,114,1,2,1,0,3,0
178 | 60,1,0,117,230,1,1,160,1,1.4,2,2,3,0
179 | 64,1,2,140,335,0,1,158,0,0,2,0,2,0
180 | 43,1,0,120,177,0,0,120,1,2.5,1,0,3,0
181 | 57,1,0,150,276,0,0,112,1,0.6,1,1,1,0
182 | 55,1,0,132,353,0,1,132,1,1.2,1,1,3,0
183 | 65,0,0,150,225,0,0,114,0,1,1,3,3,0
184 | 61,0,0,130,330,0,0,169,0,0,2,0,2,0
185 | 58,1,2,112,230,0,0,165,0,2.5,1,1,3,0
186 | 50,1,0,150,243,0,0,128,0,2.6,1,0,3,0
187 | 44,1,0,112,290,0,0,153,0,0,2,1,2,0
188 | 60,1,0,130,253,0,1,144,1,1.4,2,1,3,0
189 | 54,1,0,124,266,0,0,109,1,2.2,1,1,3,0
190 | 50,1,2,140,233,0,1,163,0,0.6,1,1,3,0
191 | 41,1,0,110,172,0,0,158,0,0,2,0,3,0
192 | 51,0,0,130,305,0,1,142,1,1.2,1,0,3,0
193 | 58,1,0,128,216,0,0,131,1,2.2,1,3,3,0
194 | 54,1,0,120,188,0,1,113,0,1.4,1,1,3,0
195 | 60,1,0,145,282,0,0,142,1,2.8,1,2,3,0
196 | 60,1,2,140,185,0,0,155,0,3,1,0,2,0
197 | 59,1,0,170,326,0,0,140,1,3.4,0,0,3,0
198 | 46,1,2,150,231,0,1,147,0,3.6,1,0,2,0
199 | 67,1,0,125,254,1,1,163,0,0.2,1,2,3,0
200 | 62,1,0,120,267,0,1,99,1,1.8,1,2,3,0
201 | 65,1,0,110,248,0,0,158,0,0.6,2,2,1,0
202 | 44,1,0,110,197,0,0,177,0,0,2,1,2,0
203 | 60,1,0,125,258,0,0,141,1,2.8,1,1,3,0
204 | 58,1,0,150,270,0,0,111,1,0.8,2,0,3,0
205 | 68,1,2,180,274,1,0,150,1,1.6,1,0,3,0
206 | 62,0,0,160,164,0,0,145,0,6.2,0,3,3,0
207 | 52,1,0,128,255,0,1,161,1,0,2,1,3,0
208 | 59,1,0,110,239,0,0,142,1,1.2,1,1,3,0
209 | 60,0,0,150,258,0,0,157,0,2.6,1,2,3,0
210 | 49,1,2,120,188,0,1,139,0,2,1,3,3,0
211 | 59,1,0,140,177,0,1,162,1,0,2,1,3,0
212 | 57,1,2,128,229,0,0,150,0,0.4,1,1,3,0
213 | 61,1,0,120,260,0,1,140,1,3.6,1,1,3,0
214 | 39,1,0,118,219,0,1,140,0,1.2,1,0,3,0
215 | 61,0,0,145,307,0,0,146,1,1,1,0,3,0
216 | 56,1,0,125,249,1,0,144,1,1.2,1,1,2,0
217 | 43,0,0,132,341,1,0,136,1,3,1,0,3,0
218 | 62,0,2,130,263,0,1,97,0,1.2,1,1,3,0
219 | 63,1,0,130,330,1,0,132,1,1.8,2,3,3,0
220 | 65,1,0,135,254,0,0,127,0,2.8,1,1,3,0
221 | 48,1,0,130,256,1,0,150,1,0,2,2,3,0
222 | 63,0,0,150,407,0,0,154,0,4,1,3,3,0
223 | 55,1,0,140,217,0,1,111,1,5.6,0,0,3,0
224 | 65,1,3,138,282,1,0,174,0,1.4,1,1,2,0
225 | 56,0,0,200,288,1,0,133,1,4,0,2,3,0
226 | 54,1,0,110,239,0,1,126,1,2.8,1,1,3,0
227 | 70,1,0,145,174,0,1,125,1,2.6,0,0,3,0
228 | 62,1,1,120,281,0,0,103,0,1.4,1,1,3,0
229 | 35,1,0,120,198,0,1,130,1,1.6,1,0,3,0
230 | 59,1,3,170,288,0,0,159,0,0.2,1,0,3,0
231 | 64,1,2,125,309,0,1,131,1,1.8,1,0,3,0
232 | 47,1,2,108,243,0,1,152,0,0,2,0,2,0
233 | 57,1,0,165,289,1,0,124,0,1,1,3,3,0
234 | 55,1,0,160,289,0,0,145,1,0.8,1,1,3,0
235 | 64,1,0,120,246,0,0,96,1,2.2,0,1,2,0
236 | 70,1,0,130,322,0,0,109,0,2.4,1,3,2,0
237 | 51,1,0,140,299,0,1,173,1,1.6,2,0,3,0
238 | 58,1,0,125,300,0,0,171,0,0,2,2,3,0
239 | 60,1,0,140,293,0,0,170,0,1.2,1,2,3,0
240 | 77,1,0,125,304,0,0,162,1,0,2,3,2,0
241 | 35,1,0,126,282,0,0,156,1,0,2,0,3,0
242 | 70,1,2,160,269,0,1,112,1,2.9,1,1,3,0
243 | 59,0,0,174,249,0,1,143,1,0,1,0,2,0
244 | 64,1,0,145,212,0,0,132,0,2,1,2,1,0
245 | 57,1,0,152,274,0,1,88,1,1.2,1,1,3,0
246 | 56,1,0,132,184,0,0,105,1,2.1,1,1,1,0
247 | 48,1,0,124,274,0,0,166,0,0.5,1,0,3,0
248 | 56,0,0,134,409,0,0,150,1,1.9,1,2,3,0
249 | 66,1,1,160,246,0,1,120,1,0,1,3,1,0
250 | 54,1,1,192,283,0,0,195,0,0,2,1,3,0
251 | 69,1,2,140,254,0,0,146,0,2,1,3,3,0
252 | 51,1,0,140,298,0,1,122,1,4.2,1,3,3,0
253 | 43,1,0,132,247,1,0,143,1,0.1,1,4,3,0
254 | 62,0,0,138,294,1,1,106,0,1.9,1,3,2,0
255 | 67,1,0,100,299,0,0,125,1,0.9,1,2,2,0
256 | 59,1,3,160,273,0,0,125,0,0,2,0,2,0
257 | 45,1,0,142,309,0,0,147,1,0,1,3,3,0
258 | 58,1,0,128,259,0,0,130,1,3,1,2,3,0
259 | 50,1,0,144,200,0,0,126,1,0.9,1,0,3,0
260 | 62,0,0,150,244,0,1,154,1,1.4,1,0,2,0
261 | 38,1,3,120,231,0,1,182,1,3.8,1,0,3,0
262 | 66,0,0,178,228,1,1,165,1,1,1,2,3,0
263 | 52,1,0,112,230,0,1,160,0,0,2,1,2,0
264 | 53,1,0,123,282,0,1,95,1,2,1,2,3,0
265 | 63,0,0,108,269,0,1,169,1,1.8,1,2,2,0
266 | 54,1,0,110,206,0,0,108,1,0,1,1,2,0
267 | 66,1,0,112,212,0,0,132,1,0.1,2,1,2,0
268 | 55,0,0,180,327,0,2,117,1,3.4,1,0,2,0
269 | 49,1,2,118,149,0,0,126,0,0.8,2,3,2,0
270 | 54,1,0,122,286,0,0,116,1,3.2,1,2,2,0
271 | 56,1,0,130,283,1,0,103,1,1.6,0,0,3,0
272 | 46,1,0,120,249,0,0,144,0,0.8,2,0,3,0
273 | 61,1,3,134,234,0,1,145,0,2.6,1,2,2,0
274 | 67,1,0,120,237,0,1,71,0,1,1,0,2,0
275 | 58,1,0,100,234,0,1,156,0,0.1,2,1,3,0
276 | 47,1,0,110,275,0,0,118,1,1,1,1,2,0
277 | 52,1,0,125,212,0,1,168,0,1,2,2,3,0
278 | 58,1,0,146,218,0,1,105,0,2,1,1,3,0
279 | 57,1,1,124,261,0,1,141,0,0.3,2,0,3,0
280 | 58,0,1,136,319,1,0,152,0,0,2,2,2,0
281 | 61,1,0,138,166,0,0,125,1,3.6,1,1,2,0
282 | 42,1,0,136,315,0,1,125,1,1.8,1,0,1,0
283 | 52,1,0,128,204,1,1,156,1,1,1,0,0,0
284 | 59,1,2,126,218,1,1,134,0,2.2,1,1,1,0
285 | 40,1,0,152,223,0,1,181,0,0,2,0,3,0
286 | 61,1,0,140,207,0,0,138,1,1.9,2,1,3,0
287 | 46,1,0,140,311,0,1,120,1,1.8,1,2,3,0
288 | 59,1,3,134,204,0,1,162,0,0.8,2,2,2,0
289 | 57,1,1,154,232,0,0,164,0,0,2,1,2,0
290 | 57,1,0,110,335,0,1,143,1,3,1,1,3,0
291 | 55,0,0,128,205,0,2,130,1,2,1,1,3,0
292 | 61,1,0,148,203,0,1,161,0,0,2,1,3,0
293 | 58,1,0,114,318,0,2,140,0,4.4,0,3,1,0
294 | 58,0,0,170,225,1,0,146,1,2.8,1,2,1,0
295 | 67,1,2,152,212,0,0,150,0,0.8,1,0,3,0
296 | 44,1,0,120,169,0,1,144,1,2.8,0,0,1,0
297 | 63,1,0,140,187,0,0,144,1,4,2,2,3,0
298 | 63,0,0,124,197,0,1,136,1,0,1,0,2,0
299 | 59,1,0,164,176,1,0,90,0,1,1,2,1,0
300 | 57,0,0,140,241,0,1,123,1,0.2,1,0,3,0
301 | 45,1,3,110,264,0,1,132,0,1.2,1,0,3,0
302 | 68,1,0,144,193,1,1,141,0,3.4,1,2,3,0
303 | 57,1,0,130,131,0,1,115,1,1.2,1,1,3,0
304 | 57,0,1,130,236,0,0,174,0,0,1,1,2,0
305 |
--------------------------------------------------------------------------------