├── 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 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](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 | 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 | 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 | 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 | 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 |
  1. Control your blood pressure.
  2. 53 |
  3. Keep your cholesterol and triglyceride levels under control.
  4. 54 |
  5. Stay at a healthy weight.
  6. 55 |
  7. Eat a healthy diet.
  8. 56 |
  9. Get regular exercise.
  10. 57 |
  11. Limit alcohol.
  12. 58 |
  13. Don't smoke.
  14. 59 |
  15. Manage stress.
  16. 60 |
  17. Manage diabetes.
  18. 61 |
  19. Make sure that you get enough sleep.
  20. 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 | 26 | 27 | 28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | 37 |
38 |
39 | 40 |
41 |
42 |
43 |
44 |
45 | 46 |
47 |
48 | 52 |
53 |
54 |
55 |
56 |
57 | 58 |
59 |
60 | 67 |
68 |
69 |
70 |
71 |
72 | 73 |
74 |
75 | 76 |
77 |
78 |
79 |
80 |
81 | 82 |
83 |
84 | 85 |
86 |
87 |
88 |
89 |
90 | 91 |
92 |
93 | 97 |
98 |
99 |
100 |
101 |
102 | 103 |
104 |
105 | 110 |
111 |
112 |
113 |
114 |
115 | 116 |
117 |
118 | 119 |
120 |
121 |
122 |
123 |
124 | 125 |
126 |
127 | 131 |
132 |
133 |
134 |
135 |
136 | 137 |
138 |
139 | 140 |
141 |
142 |
143 |
144 |
145 | 146 |
147 |
148 | 153 |
154 |
155 |
156 |
157 |
158 | 159 |
160 |
161 | 162 |
163 |
164 |
165 |
166 |
167 | 168 |
169 |
170 | 171 |
172 |
173 |
174 |
175 |
176 | 177 |
178 |
179 |
180 |
181 |
182 | 183 | 184 | 185 | 186 | 187 | 188 | 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 | --------------------------------------------------------------------------------