{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport cv2\nimport gc\nfrom sklearn.utils import shuffle\n# Input data files are available in the \"../input/\" directory.\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\nORIGINAL_HEIGHT = 137\nORIGINAL_WIDTH = 236\nPROCESSED_HEIGHT = 128\nPROCESSED_WIDTH = 128\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# Any results you write to the current directory are saved as output.","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Define the size of a training file"},{"metadata":{},"cell_type":"markdown","source":"There are 200840=2^3*5*5021 Images. Because of the extremely large prime factor it is not possible to split them evenly and still get a convenient batch size (5021 makes my Kernel crash, 4 is ridiculously slow). Because of this, I define a size for each of the training files and the remainder is put into the last file, which is used for validation."},{"metadata":{"trusted":true},"cell_type":"code","source":"rows_per_file = 20084+1","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(f\"Size of training files: {rows_per_file} elements per file\")\nremainder = 200840 % rows_per_file\nprint(f\"Size of last file: {remainder} elements\")\nnum_files = 200840 // rows_per_file\nprint(f\"Number of training files: {num_files}\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Image Processing"},{"metadata":{},"cell_type":"markdown","source":"Inspired by Iafoss [popular Kernel](https://www.kaggle.com/iafoss/image-preprocessing-128x128). I use modified version, though."},{"metadata":{"trusted":true},"cell_type":"code","source":"def load_to_numpy(file):\n    parquet = pd.read_parquet(file)\n    return 255 - parquet.iloc[:,1:].values.astype(np.uint8).reshape(-1, ORIGINAL_HEIGHT, ORIGINAL_WIDTH)\n\ndef normalize_image(img):\n    return (img*(255.0/img.max())).astype(np.uint8)\n\ndef get_min_indices(img, min_writing_value=80):\n    min_value = img > min_writing_value\n    h_min, h_max = np.where(np.any(min_value, axis=0))[0][[0, -1]]\n    v_min, v_max = np.where(np.any(min_value, axis=1))[0][[0, -1]]\n    return (h_min, h_max, v_min, v_max)\n\ndef get_min_indices_with_border(img, min_writing_value=80, border=20):\n    h_min, h_max, v_min, v_max = get_min_indices(img[border:-border, border:-border], min_writing_value=min_writing_value)\n    return (h_min + border, h_max + border, v_min + border, v_max + border) #indices ignored border, is added again\n\ndef cut_and_denoise_image(img, border=20, min_writing_value=80, max_noise=28):\n    #cut minimum needed to encease image\n    h_min, h_max, v_min, v_max = get_min_indices_with_border(img, border=border, min_writing_value=min_writing_value)\n    #add tolerance around minium, making it dependend on border prevents missing part of the character\n    h_min= (h_min-border) if h_min>border else 0\n    v_min= (v_min-border) if v_min>border else 0\n    h_max= (h_max+border) if ORIGINAL_WIDTH-h_max>border else ORIGINAL_WIDTH\n    v_max= (v_max+border) if ORIGINAL_HEIGHT-v_max>border else ORIGINAL_HEIGHT\n    #cut image\n    img = img[v_min:v_max, h_min:h_max]\n    #denoise\n    img[img < max_noise] = 0\n    #add padding to image\n    longer_side_length = max(np.ma.size(img, axis=0), np.ma.size(img, axis=1))\n    padding = [((longer_side_length - np.ma.size(img, axis=0)) // 2,),\n               ((longer_side_length - np.ma.size(img, axis=1)) // 2,)]\n    img = np.pad(img, padding, mode=\"constant\")\n    #return resized image\n    return cv2.resize(img,(PROCESSED_HEIGHT, PROCESSED_WIDTH))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The images are ordered perfectly (proven in separate Kernel), so as long as they are correctly concatenated, everything will be fine"},{"metadata":{"trusted":true},"cell_type":"code","source":"results = []\nfor file_index in range(0,4):\n    print(\"Dealing with \", f\"/kaggle/input/bengaliai-cv19/train_image_data_{file_index}.parquet\")\n    images = load_to_numpy(f\"/kaggle/input/bengaliai-cv19/train_image_data_{file_index}.parquet\")\n    print(\"Number of images: \",np.ma.size(images, axis=0))\n    print(\"Collect after loading parquet: \", gc.collect())\n    for image_index, img in enumerate(images):\n        img = normalize_image(img)\n        img = cut_and_denoise_image(img)\n        images[image_index,0:PROCESSED_HEIGHT, 0:PROCESSED_WIDTH] = img #saving inplace to save RAM\n    images = images[:,0:PROCESSED_HEIGHT, 0:PROCESSED_WIDTH]\n    print(images.shape)\n    print(\"Collect after processing images: \", gc.collect())\n    results.append(images)\n    print(len(results))\n    print(\"Collect after appending: \", gc.collect())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#put everything in one array\ntotal = np.zeros([4*50210, PROCESSED_HEIGHT, PROCESSED_WIDTH], dtype=np.uint8)\nfor i in range(0, 4):\n    total[i*50210:(i+1)*50210,:,:] = results[i]\n    gc.collect()\ndel results\ngc.collect()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Add labels"},{"metadata":{"trusted":true},"cell_type":"code","source":"labels = pd.read_csv(\"/kaggle/input/bengaliai-cv19/train.csv\").iloc[:,1:-1].astype(np.uint8)\ngc.collect()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#shuffling to use last few images in validation file. Not stratified, however\ntotal, labels = shuffle(total, labels, random_state=42)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#1-hot-encoding\nroot_label = pd.get_dummies(labels[\"grapheme_root\"]).values\nvowel_label = pd.get_dummies(labels[\"vowel_diacritic\"]).values\nconsonant_label = pd.get_dummies(labels[\"consonant_diacritic\"]).values\ndel labels","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Save to files"},{"metadata":{"trusted":true},"cell_type":"code","source":"#train files\nfor file_index in range(num_files):\n    tmp = total[file_index*rows_per_file:(file_index+1)*rows_per_file]\n    np.save(f\"processed_{rows_per_file}_{PROCESSED_HEIGHT}_{file_index}.npy\", tmp)\n    tmp = root_label[file_index*rows_per_file:(file_index+1)*rows_per_file]\n    np.save(f\"root_{rows_per_file}_label_{file_index}.npy\", tmp)\n    tmp = vowel_label[file_index*rows_per_file:(file_index+1)*rows_per_file]\n    np.save(f\"vowel_{rows_per_file}_label_{file_index}.npy\", tmp)\n    tmp = consonant_label[file_index*rows_per_file:(file_index+1)*rows_per_file]\n    np.save(f\"consonant_{rows_per_file}_label_{file_index}.npy\", tmp)\n#valid file\ntmp = total[num_files*rows_per_file:]\nnp.save(f\"processed_{rows_per_file}_{PROCESSED_HEIGHT}_valid.npy\", tmp)\ntmp = root_label[num_files*rows_per_file:]\nnp.save(f\"root_{rows_per_file}_label_valid.npy\", tmp)\ntmp = vowel_label[num_files*rows_per_file:]\nnp.save(f\"vowel_{rows_per_file}_label_valid.npy\", tmp)\ntmp = consonant_label[num_files*rows_per_file:]\nnp.save(f\"consonant_{rows_per_file}_label_valid.npy\", tmp)","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":1}