Skip to content

Commit

Permalink
fix trial abhisheks008#1
Browse files Browse the repository at this point in the history
  • Loading branch information
SHAY2407 committed Aug 12, 2022
1 parent c414f60 commit 5843461
Show file tree
Hide file tree
Showing 2 changed files with 1 addition and 0 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport cv2\nimport random\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nimport tensorflow as tf\nfrom tensorflow.keras.optimizers import Adam\nfrom keras import backend as K\nfrom keras.models import Model\nfrom keras.layers import Input, Conv2D, MaxPooling2D, Reshape, Bidirectional, LSTM, Dense, Lambda, Activation, BatchNormalization, Dropout","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-08-12T13:29:46.860863Z","iopub.execute_input":"2022-08-12T13:29:46.861431Z","iopub.status.idle":"2022-08-12T13:29:53.928507Z","shell.execute_reply.started":"2022-08-12T13:29:46.861341Z","shell.execute_reply":"2022-08-12T13:29:53.927033Z"},"trusted":true},"execution_count":1,"outputs":[]},{"cell_type":"code","source":"train = pd.read_csv('/kaggle/input/handwriting-recognition/written_name_train_v2.csv')\nvalid = pd.read_csv('/kaggle/input/handwriting-recognition/written_name_validation_v2.csv')","metadata":{"execution":{"iopub.status.busy":"2022-08-12T13:31:22.671315Z","iopub.execute_input":"2022-08-12T13:31:22.671726Z","iopub.status.idle":"2022-08-12T13:31:23.232117Z","shell.execute_reply.started":"2022-08-12T13:31:22.671696Z","shell.execute_reply":"2022-08-12T13:31:23.230816Z"},"trusted":true},"execution_count":3,"outputs":[]},{"cell_type":"code","source":"# Viewing the data\nplt.figure(figsize=(15, 10))\n\n# To view first 6 images\nfor i in range(6):\n # plt.subplot(nrows, ncols, index)\n ax = plt.subplot(2, 3, i+1)\n img_dir = '/kaggle/input/handwriting-recognition/train_v2/train/'+train.loc[i, 'FILENAME']\n # cv2.imread(path=img_dir, flag=specifies the way in which image should be read)\n image = cv2.imread(img_dir, cv2.IMREAD_GRAYSCALE)\n # cv2.imread(X=data of the image, colormap instance)\n plt.imshow(image, cmap = 'gray')\n plt.title(train.loc[i, 'IDENTITY'], fontsize=12)\n plt.axis('off')\n# Adjusts the subplot layout parameters\nplt.subplots_adjust(wspace=0.2, hspace=-0.8)","metadata":{"execution":{"iopub.status.busy":"2022-08-12T13:32:09.176604Z","iopub.execute_input":"2022-08-12T13:32:09.177007Z","iopub.status.idle":"2022-08-12T13:32:09.892185Z","shell.execute_reply.started":"2022-08-12T13:32:09.176978Z","shell.execute_reply":"2022-08-12T13:32:09.889925Z"},"trusted":true},"execution_count":4,"outputs":[]},{"cell_type":"code","source":"# Cleaning the data by checking for null values\nprint(\"Number of NaNs in train set : \", train['IDENTITY'].isnull().sum())\nprint(\"Number of NaNs in validation set : \", valid['IDENTITY'].isnull().sum())","metadata":{"execution":{"iopub.status.busy":"2022-08-12T13:32:27.657142Z","iopub.execute_input":"2022-08-12T13:32:27.657636Z","iopub.status.idle":"2022-08-12T13:32:27.691095Z","shell.execute_reply.started":"2022-08-12T13:32:27.657602Z","shell.execute_reply":"2022-08-12T13:32:27.689537Z"},"trusted":true},"execution_count":5,"outputs":[]},{"cell_type":"code","source":"# Dropping the null values from the training and testing data\ntrain.dropna(axis=0, inplace=True)\nvalid.dropna(axis=0, inplace=True)","metadata":{"execution":{"iopub.status.busy":"2022-08-12T13:32:32.901881Z","iopub.execute_input":"2022-08-12T13:32:32.902517Z","iopub.status.idle":"2022-08-12T13:32:33.040293Z","shell.execute_reply.started":"2022-08-12T13:32:32.902472Z","shell.execute_reply":"2022-08-12T13:32:33.038890Z"},"trusted":true},"execution_count":6,"outputs":[]},{"cell_type":"code","source":"# Removing the data with the label 'UNREADABLE'\nunreadable = train[train['IDENTITY'] == 'UNREADABLE']\n# resets the index of the DataFrame, and uses the default one instead\nunreadable.reset_index(inplace = True, drop=True)\n\nplt.figure(figsize=(15, 10))\n\nfor i in range(6):\n ax = plt.subplot(2, 3, i+1)\n img_dir = '/kaggle/input/handwriting-recognition/train_v2/train/'+unreadable.loc[i, 'FILENAME']\n image = cv2.imread(img_dir, cv2.IMREAD_GRAYSCALE)\n plt.imshow(image, cmap = 'gray')\n plt.title(unreadable.loc[i, 'IDENTITY'], fontsize=12)\n plt.axis('off')\n\nplt.subplots_adjust(wspace=0.2, hspace=-0.8)","metadata":{"execution":{"iopub.status.busy":"2022-08-12T13:33:07.899050Z","iopub.execute_input":"2022-08-12T13:33:07.899466Z","iopub.status.idle":"2022-08-12T13:33:08.434038Z","shell.execute_reply.started":"2022-08-12T13:33:07.899425Z","shell.execute_reply":"2022-08-12T13:33:08.432295Z"},"trusted":true},"execution_count":7,"outputs":[]},{"cell_type":"code","source":"train = train[train['IDENTITY'] != 'UNREADABLE']\nvalid = valid[valid['IDENTITY'] != 'UNREADABLE']","metadata":{"execution":{"iopub.status.busy":"2022-08-12T13:33:31.018420Z","iopub.execute_input":"2022-08-12T13:33:31.018817Z","iopub.status.idle":"2022-08-12T13:33:31.079194Z","shell.execute_reply.started":"2022-08-12T13:33:31.018786Z","shell.execute_reply":"2022-08-12T13:33:31.077773Z"},"trusted":true},"execution_count":8,"outputs":[]},{"cell_type":"code","source":"\n# converting some lowercase labels to uppercase to maintain uniformity\ntrain['IDENTITY'] = train['IDENTITY'].str.upper()\nvalid['IDENTITY'] = valid['IDENTITY'].str.upper()\n","metadata":{"execution":{"iopub.status.busy":"2022-08-12T13:33:39.678860Z","iopub.execute_input":"2022-08-12T13:33:39.679289Z","iopub.status.idle":"2022-08-12T13:33:39.847673Z","shell.execute_reply.started":"2022-08-12T13:33:39.679249Z","shell.execute_reply":"2022-08-12T13:33:39.846377Z"},"trusted":true},"execution_count":9,"outputs":[]},{"cell_type":"code","source":"# resetting the index to maintain uniform leveling\ntrain.reset_index(inplace = True, drop=True) \nvalid.reset_index(inplace = True, drop=True)","metadata":{"execution":{"iopub.status.busy":"2022-08-12T13:33:48.286935Z","iopub.execute_input":"2022-08-12T13:33:48.287396Z","iopub.status.idle":"2022-08-12T13:33:48.294660Z","shell.execute_reply.started":"2022-08-12T13:33:48.287365Z","shell.execute_reply":"2022-08-12T13:33:48.292470Z"},"trusted":true},"execution_count":10,"outputs":[]},{"cell_type":"code","source":"def preprocess(img):\n (h, w) = img.shape\n # blank white image\n final_img = np.ones([64, 256])*255 \n \n # cropping the image if the width and height are greater than \n # 256 and 64 respectively\n if w > 256:\n img = img[:, :256]\n \n if h > 64:\n img = img[:64, :]\n \n # rotating the image clockwise to bring the image shape to (x,y)\n final_img[:h, :w] = img\n return cv2.rotate(final_img, cv2.ROTATE_90_CLOCKWISE)","metadata":{"execution":{"iopub.status.busy":"2022-08-12T13:34:01.172690Z","iopub.execute_input":"2022-08-12T13:34:01.173126Z","iopub.status.idle":"2022-08-12T13:34:01.183181Z","shell.execute_reply.started":"2022-08-12T13:34:01.173078Z","shell.execute_reply":"2022-08-12T13:34:01.181253Z"},"trusted":true},"execution_count":11,"outputs":[]},{"cell_type":"code","source":"# training the model 30000 images and the validating it on 3000 images\ntrain_size = 30000\nvalid_size= 3000","metadata":{"execution":{"iopub.status.busy":"2022-08-12T13:34:07.830556Z","iopub.execute_input":"2022-08-12T13:34:07.830982Z","iopub.status.idle":"2022-08-12T13:34:07.836832Z","shell.execute_reply.started":"2022-08-12T13:34:07.830952Z","shell.execute_reply":"2022-08-12T13:34:07.835488Z"},"trusted":true},"execution_count":12,"outputs":[]},{"cell_type":"code","source":"# training\ntrain_x = []\n\nfor i in range(train_size):\n img_dir = '/kaggle/input/handwriting-recognition/train_v2/train/'+train.loc[i, 'FILENAME']\n image = cv2.imread(img_dir, cv2.IMREAD_GRAYSCALE)\n image = preprocess(image)\n image = image/255.\n train_x.append(image)","metadata":{"execution":{"iopub.status.busy":"2022-08-12T13:41:51.845380Z","iopub.execute_input":"2022-08-12T13:41:51.846099Z","iopub.status.idle":"2022-08-12T13:42:38.716267Z","shell.execute_reply.started":"2022-08-12T13:41:51.846060Z","shell.execute_reply":"2022-08-12T13:42:38.714703Z"},"trusted":true},"execution_count":18,"outputs":[]},{"cell_type":"code","source":"# validating\nvalid_x = []\n\nfor i in range(valid_size):\n img_dir = '/kaggle/input/handwriting-recognition/validation_v2/validation/'+valid.loc[i, 'FILENAME']\n image = cv2.imread(img_dir, cv2.IMREAD_GRAYSCALE)\n image = preprocess(image)\n image = image/255.\n valid_x.append(image)","metadata":{"execution":{"iopub.status.busy":"2022-08-12T14:12:01.127329Z","iopub.execute_input":"2022-08-12T14:12:01.127797Z","iopub.status.idle":"2022-08-12T14:12:05.442923Z","shell.execute_reply.started":"2022-08-12T14:12:01.127745Z","shell.execute_reply":"2022-08-12T14:12:05.441529Z"},"trusted":true},"execution_count":20,"outputs":[]},{"cell_type":"code","source":"# .reshape(a: array_like = -1(we want numpy to figure out as the dimensions are unknown))\ntrain_x = np.array(train_x).reshape(-1, 256, 64, 1)\nvalid_x = np.array(valid_x).reshape(-1, 256, 64, 1)","metadata":{"execution":{"iopub.status.busy":"2022-08-12T14:15:04.071714Z","iopub.execute_input":"2022-08-12T14:15:04.072120Z","iopub.status.idle":"2022-08-12T14:15:05.639892Z","shell.execute_reply.started":"2022-08-12T14:15:04.072088Z","shell.execute_reply":"2022-08-12T14:15:05.638520Z"},"trusted":true},"execution_count":21,"outputs":[]},{"cell_type":"code","source":"# labels are converted to numbers representing each character\n# labels are then prepared for Connectionist Temporal Classification Loss (CTC Loss)\nalphabets = u\"ABCDEFGHIJKLMNOPQRSTUVWXYZ-' \"\n# max length of input labels\nmax_str_len = 24 \n# +1 for ctc pseudo blank\nnum_of_characters = len(alphabets) + 1 \n# max length of predicted labels\nnum_of_timestamps = 64 \n\n\ndef label_to_num(label):\n label_num = []\n for ch in label:\n label_num.append(alphabets.find(ch))\n \n return np.array(label_num)\n\ndef num_to_label(num):\n ret = \"\"\n for ch in num:\n if ch == -1: # CTC Blank\n break\n else:\n ret+=alphabets[ch]\n return ret","metadata":{"execution":{"iopub.status.busy":"2022-08-12T14:15:31.449851Z","iopub.execute_input":"2022-08-12T14:15:31.451145Z","iopub.status.idle":"2022-08-12T14:15:31.460936Z","shell.execute_reply.started":"2022-08-12T14:15:31.451113Z","shell.execute_reply":"2022-08-12T14:15:31.459323Z"},"trusted":true},"execution_count":22,"outputs":[]},{"cell_type":"code","source":"name = 'YASH'\nprint(name, '\\n',label_to_num(name))","metadata":{"execution":{"iopub.status.busy":"2022-08-12T14:15:52.049364Z","iopub.execute_input":"2022-08-12T14:15:52.050183Z","iopub.status.idle":"2022-08-12T14:15:52.058398Z","shell.execute_reply.started":"2022-08-12T14:15:52.050152Z","shell.execute_reply":"2022-08-12T14:15:52.056747Z"},"trusted":true},"execution_count":23,"outputs":[]},{"cell_type":"code","source":"# train_y contains the true labels converted to numbers and padded with -1. \n# The length of each label is equal to max_str_len.\ntrain_y = np.ones([train_size, max_str_len]) * -1\n# train_label_len contains the length of each true label (without padding)\ntrain_label_len = np.zeros([train_size, 1])\n# train_input_len contains the length of each predicted label. \n# The length of all the predicted labels is constant i.e number of timestamps - 2.\ntrain_input_len = np.ones([train_size, 1]) * (num_of_timestamps-2)\n# train_output is a dummy output for ctc loss.\ntrain_output = np.zeros([train_size])\n\nfor i in range(train_size):\n train_label_len[i] = len(train.loc[i, 'IDENTITY'])\n train_y[i, 0:len(train.loc[i, 'IDENTITY'])]= label_to_num(train.loc[i, 'IDENTITY']) \n","metadata":{"execution":{"iopub.status.busy":"2022-08-12T14:16:06.528136Z","iopub.execute_input":"2022-08-12T14:16:06.528554Z","iopub.status.idle":"2022-08-12T14:16:08.159873Z","shell.execute_reply.started":"2022-08-12T14:16:06.528524Z","shell.execute_reply":"2022-08-12T14:16:08.158600Z"},"trusted":true},"execution_count":24,"outputs":[]},{"cell_type":"code","source":"valid_y = np.ones([valid_size, max_str_len]) * -1\nvalid_label_len = np.zeros([valid_size, 1])\nvalid_input_len = np.ones([valid_size, 1]) * (num_of_timestamps-2)\nvalid_output = np.zeros([valid_size])\n\nfor i in range(valid_size):\n valid_label_len[i] = len(valid.loc[i, 'IDENTITY'])\n valid_y[i, 0:len(valid.loc[i, 'IDENTITY'])]= label_to_num(valid.loc[i, 'IDENTITY']) ","metadata":{"execution":{"iopub.status.busy":"2022-08-12T14:16:25.240659Z","iopub.execute_input":"2022-08-12T14:16:25.241050Z","iopub.status.idle":"2022-08-12T14:16:25.543503Z","shell.execute_reply.started":"2022-08-12T14:16:25.241021Z","shell.execute_reply":"2022-08-12T14:16:25.541621Z"},"trusted":true},"execution_count":25,"outputs":[]},{"cell_type":"code","source":"print('True label : ',train.loc[100, 'IDENTITY'] , '\\ntrain_y : ',train_y[100],'\\ntrain_label_len : ',train_label_len[100], \n '\\ntrain_input_len : ', train_input_len[100])","metadata":{"execution":{"iopub.status.busy":"2022-08-12T14:16:35.032027Z","iopub.execute_input":"2022-08-12T14:16:35.033341Z","iopub.status.idle":"2022-08-12T14:16:35.042920Z","shell.execute_reply.started":"2022-08-12T14:16:35.033308Z","shell.execute_reply":"2022-08-12T14:16:35.041419Z"},"trusted":true},"execution_count":26,"outputs":[]},{"cell_type":"code","source":"input_data = Input(shape=(256, 64, 1), name='input')\n\ninner = Conv2D(32, (3, 3), padding='same', name='conv1', kernel_initializer='he_normal')(input_data) \ninner = BatchNormalization()(inner)\ninner = Activation('relu')(inner)\ninner = MaxPooling2D(pool_size=(2, 2), name='max1')(inner)\n\ninner = Conv2D(64, (3, 3), padding='same', name='conv2', kernel_initializer='he_normal')(inner)\ninner = BatchNormalization()(inner)\ninner = Activation('relu')(inner)\ninner = MaxPooling2D(pool_size=(2, 2), name='max2')(inner)\ninner = Dropout(0.3)(inner)\n\ninner = Conv2D(128, (3, 3), padding='same', name='conv3', kernel_initializer='he_normal')(inner)\ninner = BatchNormalization()(inner)\ninner = Activation('relu')(inner)\ninner = MaxPooling2D(pool_size=(1, 2), name='max3')(inner)\ninner = Dropout(0.3)(inner)\n\n# CNN to RNN\ninner = Reshape(target_shape=((64, 1024)), name='reshape')(inner)\ninner = Dense(64, activation='relu', kernel_initializer='he_normal', name='dense1')(inner)\n\n## RNN\ninner = Bidirectional(LSTM(256, return_sequences=True), name = 'lstm1')(inner)\ninner = Bidirectional(LSTM(256, return_sequences=True), name = 'lstm2')(inner)\n\n## OUTPUT\ninner = Dense(num_of_characters, kernel_initializer='he_normal',name='dense2')(inner)\ny_pred = Activation('softmax', name='softmax')(inner)\n\nmodel = Model(inputs=input_data, outputs=y_pred)\nmodel.summary()","metadata":{"execution":{"iopub.status.busy":"2022-08-12T14:16:52.431956Z","iopub.execute_input":"2022-08-12T14:16:52.432441Z","iopub.status.idle":"2022-08-12T14:16:57.125958Z","shell.execute_reply.started":"2022-08-12T14:16:52.432380Z","shell.execute_reply":"2022-08-12T14:16:57.124456Z"},"trusted":true},"execution_count":27,"outputs":[]},{"cell_type":"code","source":"# the ctc loss function\ndef ctc_lambda_func(args):\n y_pred, labels, input_length, label_length = args\n # the 2 is critical here since the first couple outputs of the RNN\n # tend to be garbage\n y_pred = y_pred[:, 2:, :]\n return K.ctc_batch_cost(labels, y_pred, input_length, label_length)","metadata":{"execution":{"iopub.status.busy":"2022-08-12T14:17:11.996334Z","iopub.execute_input":"2022-08-12T14:17:11.996806Z","iopub.status.idle":"2022-08-12T14:17:12.004659Z","shell.execute_reply.started":"2022-08-12T14:17:11.996762Z","shell.execute_reply":"2022-08-12T14:17:12.002945Z"},"trusted":true},"execution_count":28,"outputs":[]},{"cell_type":"code","source":"labels = Input(name='gtruth_labels', shape=[max_str_len], dtype='float32')\ninput_length = Input(name='input_length', shape=[1], dtype='int64')\nlabel_length = Input(name='label_length', shape=[1], dtype='int64')\n\nctc_loss = Lambda(ctc_lambda_func, output_shape=(1,), name='ctc')([y_pred, labels, input_length, label_length])\nmodel_final = Model(inputs=[input_data, labels, input_length, label_length], outputs=ctc_loss)","metadata":{"execution":{"iopub.status.busy":"2022-08-12T14:17:21.872630Z","iopub.execute_input":"2022-08-12T14:17:21.873066Z","iopub.status.idle":"2022-08-12T14:17:22.002669Z","shell.execute_reply.started":"2022-08-12T14:17:21.873037Z","shell.execute_reply":"2022-08-12T14:17:22.001344Z"},"trusted":true},"execution_count":29,"outputs":[]},{"cell_type":"code","source":"# the loss calculation occurs elsewhere, so we use a dummy lambda function for the loss\nmodel_final.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer=Adam(learning_rate = 0.0001))\n\nmodel_final.fit(x=[train_x, train_y, train_input_len, train_label_len], y=train_output, \n validation_data=([valid_x, valid_y, valid_input_len, valid_label_len], valid_output),\n epochs=60, batch_size=128)","metadata":{"execution":{"iopub.status.busy":"2022-08-12T14:17:37.141027Z","iopub.execute_input":"2022-08-12T14:17:37.142315Z","iopub.status.idle":"2022-08-12T15:03:19.269779Z","shell.execute_reply.started":"2022-08-12T14:17:37.142281Z","shell.execute_reply":"2022-08-12T15:03:19.268395Z"},"trusted":true},"execution_count":30,"outputs":[]},{"cell_type":"code","source":"preds = model.predict(valid_x)\ndecoded = K.get_value(K.ctc_decode(preds, input_length=np.ones(preds.shape[0])*preds.shape[1], \n greedy=True)[0][0])\n\nprediction = []\nfor i in range(valid_size):\n prediction.append(num_to_label(decoded[i]))","metadata":{"execution":{"iopub.status.busy":"2022-08-12T15:03:39.728736Z","iopub.execute_input":"2022-08-12T15:03:39.729189Z","iopub.status.idle":"2022-08-12T15:03:42.927008Z","shell.execute_reply.started":"2022-08-12T15:03:39.729157Z","shell.execute_reply":"2022-08-12T15:03:42.925513Z"},"trusted":true},"execution_count":31,"outputs":[]},{"cell_type":"code","source":"y_true = valid.loc[0:valid_size, 'IDENTITY']\ncorrect_char = 0\ntotal_char = 0\ncorrect = 0\n\nfor i in range(valid_size):\n pr = prediction[i]\n tr = y_true[i]\n total_char += len(tr)\n \n for j in range(min(len(tr), len(pr))):\n if tr[j] == pr[j]:\n correct_char += 1\n \n if pr == tr :\n correct += 1 \n \nprint('Correct characters predicted : %.2f%%' %(correct_char*100/total_char))\nprint('Correct words predicted : %.2f%%' %(correct*100/valid_size))","metadata":{"execution":{"iopub.status.busy":"2022-08-12T15:03:57.275214Z","iopub.execute_input":"2022-08-12T15:03:57.276553Z","iopub.status.idle":"2022-08-12T15:03:57.309933Z","shell.execute_reply.started":"2022-08-12T15:03:57.276494Z","shell.execute_reply":"2022-08-12T15:03:57.308509Z"},"trusted":true},"execution_count":32,"outputs":[]},{"cell_type":"code","source":"test = pd.read_csv('/kaggle/input/handwriting-recognition/written_name_test_v2.csv')\n\nplt.figure(figsize=(15, 10))\nfor i in range(6):\n ax = plt.subplot(2, 3, i+1)\n img_dir = '/kaggle/input/handwriting-recognition/test_v2/test/'+test.loc[i, 'FILENAME']\n image = cv2.imread(img_dir, cv2.IMREAD_GRAYSCALE)\n plt.imshow(image, cmap='gray')\n \n image = preprocess(image)\n image = image/255.\n pred = model.predict(image.reshape(1, 256, 64, 1))\n decoded = K.get_value(K.ctc_decode(pred, input_length=np.ones(pred.shape[0])*pred.shape[1], \n greedy=True)[0][0])\n plt.title(num_to_label(decoded[0]), fontsize=12)\n plt.axis('off')\n \nplt.subplots_adjust(wspace=0.2, hspace=-0.8)","metadata":{"execution":{"iopub.status.busy":"2022-08-12T15:04:53.781368Z","iopub.execute_input":"2022-08-12T15:04:53.781926Z","iopub.status.idle":"2022-08-12T15:04:55.516671Z","shell.execute_reply.started":"2022-08-12T15:04:53.781841Z","shell.execute_reply":"2022-08-12T15:04:55.515279Z"},"trusted":true},"execution_count":33,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}

0 comments on commit 5843461

Please sign in to comment.