{"cells":[{"metadata":{"trusted":true},"cell_type":"code","source":"pip install pyspark","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Importing patient data\n\nData is imported into spark dataframe"},{"metadata":{"trusted":true},"cell_type":"code","source":"from pyspark.sql import SparkSession\nfrom pyspark.sql.functions import col\nimport pandas as pd\n\npd.set_option('display.max_rows', 500)\npd.set_option('display.max_columns', 500)\npd.set_option('display.width', 150)\n\n\nspark = SparkSession \\\n.builder \\\n.appName(\"Test data prediction\") \\\n.getOrCreate()\n\nspark.conf.set(\"spark.sql.execution.arrow.enabled\", \"false\")\nrawData = spark.read \\\n.format('csv') \\\n.option('header', 'true') \\\n.load('../input/osic-pulmonary-fibrosis-progression/train.csv')\n\nrawData = rawData.select(col('FVC').cast('float'), col('Percent').cast('float'), \\\n                        col('Age').cast('float'), col('Sex'), col('SmokingStatus'), \\\n                        col('Patient'), col('Weeks').cast('float'))\n\nrawData.toPandas().head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from pyspark.ml.stat import Summarizer\nfrom pyspark.ml.feature import VectorAssembler\n\nstat = VectorAssembler(\n    inputCols= ['Percent', 'Age', 'FVC', 'Weeks'],\n    outputCol= 'feature',\n    handleInvalid=\"keep\"\n    ).transform(rawData)\nsummarizer = Summarizer.metrics(\"mean\", \"min\", \"max\", \"variance\")\nstat.select(summarizer.summary(stat.feature)).toPandas()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Correlation of Patient data"},{"metadata":{"trusted":true},"cell_type":"code","source":"from pyspark.ml.stat import Correlation\nimport matplotlib.pyplot as plt\nfrom pyspark.ml.feature import Normalizer\nimport seaborn as sns\nimport pandas as pd\n\nnormalizedFeature = Normalizer(inputCol='feature', outputCol='normalized feature').transform(stat)\npearsonCorr = Correlation.corr(normalizedFeature, 'normalized feature').collect()[0][0].toArray()\ndf = pd.DataFrame(pearsonCorr, columns= ['Percent', 'Age', 'FVC', 'Weeks'] )\nsns.heatmap(df, xticklabels= ['Percent', 'Age', 'FVC', 'Weeks'], yticklabels= ['Percent', 'Age', 'FVC', 'Weeks'])\n\nprint(\"From heatmap Age and Percentage is highly positively correlated, Also FVC and Weeks are highly negatively correlated. As Weeks go by FVC keeps on decreasing\" )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Function to get dicom data\nfrom pydicom.filebase import DicomBytesIO\nfrom pydicom.dataset import Dataset\n\ndef getDicomData(binry):\n    dicom_bytes = DicomBytesIO(binry)\n    return dicom_bytes\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Image feature extraction\n\nI am using opencv as image processing tool."},{"metadata":{"trusted":true},"cell_type":"code","source":" #Function to get and display plotted contours\nimport numpy as np\nimport cv2\nimport matplotlib.pyplot as plt\nfrom pydicom import dcmread\n\n\ndef getLungContours(data, display = False):\n    \n    try:\n        ds = dcmread(data, force = True)\n    except Exception:\n        return \"Unknown\", 0, list(np.array([[[0, 0]]]))\n    \n    try:\n        pixel_data = ds.pixel_array\n    except RuntimeError:\n        pixel_data  = np.ones((512, 512))\n        \n    img_2d = pixel_data.astype(float)\n    img_2d_scaled = (np.maximum(img_2d,0) / img_2d.max()) * 255.0\n    img_2d_scaled = np.uint8(img_2d_scaled)\n    kernel = np.ones((3,3),np.uint8)\n    gray = img_2d_scaled.copy()\n\n    \n    norm_image = cv2.normalize(gray, None, alpha=0, beta=1, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_32F)\n    \n    middle = norm_image[int(512/5):int(512/5*4),int(512/5):int(512/5*4)] \n    mean = np.mean(middle)  \n    max = np.max(norm_image)\n    min = np.min(norm_image)\n    \n    norm_image[norm_image==max]=mean\n    norm_image[norm_image==min]=mean\n    \n\n    # Define criteria = ( type, max_iter = 10 , epsilon = 1.0 )\n    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)\n    # Set flags (Just to avoid line break in the code)\n    flags = cv2.KMEANS_RANDOM_CENTERS\n    # Apply KMeans\n    z = np.reshape(middle,[np.prod(middle.shape),1])\n\n    compactness,labels,centers = cv2.kmeans(z, \\\n                                           2,None,criteria,10,flags)\n      \n    \n    threshold_value = np.mean(centers)\n\n    ret,thresh = cv2.threshold(gray, threshold_value,1,cv2.THRESH_BINARY)\n\n    img_erosion = cv2.erode(thresh, np.ones((3,3),np.uint8), iterations=2) \n    img_erosion = np.uint8(img_erosion * 255)\n    invert = 255 - img_erosion\n\n    no_of_labels, output, stats, centroids = cv2.connectedComponentsWithStats(invert, connectivity=8)\n    \n    good_labels = set()\n    \n    for label in range(no_of_labels):\n        stat = stats[label]\n        x_start = stat[0]\n        x_end = x_start + stat[2]\n        y_start = stat[1]\n        y_end = y_start + stat[3]\n        \n        if x_start > 20 and x_end < 500 and y_start > 50 and y_end < 500:\n            good_labels.add(label)        \n\n    for row in range(512):\n        for col in range(512):\n            if output[row][col] not in good_labels:\n                output[row][col] = 0\n                \n    lungs = np.uint8(73*output/np.max(output))\n    lung_fmask = np.uint8(255*output/np.max(output))\n    masked_lungs = cv2.bitwise_or(gray, lung_fmask)\n\n    blank_ch = 255 * np.ones_like(lungs)\n\n    hsv = cv2.merge([lungs, blank_ch, blank_ch])\n\n    hsv = cv2.cvtColor(hsv,cv2.COLOR_HSV2BGR) \n\n    contours, hierarchy = cv2.findContours(lung_fmask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n\n    cnts_image = cv2.drawContours(hsv.copy(), contours, -1, (0,0,0), 6)\n    \n    week = str(ds[0x20, 0x13].value)\n    patient_id = str(ds[0x10, 0x20].value)\n#################DISPLAY###FUNCTIONS################\n\n    if display:\n        \n        plt.imshow(gray, cmap='Greys')\n        plt.title(\"Grey image \" + week)\n        plt.show()\n\n        plt.imshow(thresh, cmap='Greys')\n        plt.title(\"Binary Threshold \" + week)\n        plt.show()\n\n        plt.imshow(img_erosion, cmap='Greys')\n        plt.title(\"erosion \" + week)\n        plt.show()\n\n        plt.imshow(invert, cmap='Greys')\n        plt.title(\"inversion image\" + week)\n        plt.show()\n\n        plt.imshow(masked_lungs, cmap='Greys', vmin=0, vmax=255)\n        plt.title(\"Masked Lungs \" + week)\n        plt.show()\n\n        plt.imshow(hsv, cmap='Greys')\n        plt.title(\"HSV Image of Lungs \" + week)\n        plt.show()\n\n        plt.imshow(cnts_image, cmap='Greys', vmin=0, vmax=255)\n        plt.title(\"Contours Detected \" + week)\n        plt.show()\n    else:\n        return patient_id, int(week), contours\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Extracted Contour information from CT scan image**"},{"metadata":{"trusted":true},"cell_type":"code","source":"getLungContours('../input/osic-pulmonary-fibrosis-progression/train/ID00007637202177411956430/5.dcm', True)\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Feature extraction using counour data\n\nHere I am extracting 4 feature from image data\n* Total Area of all the contours generated.\n* Percentage of area enclosed by contour.\n* Total aspect ratio.\n    Aspect ratio is ratio of width of the contour enclosed by erctange to it's height. Summing of all such aspect ratios of all the contours detected gives Total aspect ratio\n    \n* Number of contours detected.    "},{"metadata":{"trusted":true},"cell_type":"code","source":"# Calculating total areas and Total average aspect ratios of all contours in a CT Scan.\n#Percentage Ratio of total areas of contours to the total area of canvas\n\ndef getContourProperties(contours, display = False):\n    \n    contour_areas = [cv2.contourArea(cnt) for cnt in contours]\n\n    total_contour_area = sum(contour_areas)\n    \n    percent_ratio = float(100 * total_contour_area)/(512 * 512)\n\n    aspect_ratios_list = []\n\n    for cnt in contours:\n        x,y,w,h = cv2.boundingRect(cnt)\n        if h == 0:\n            #print(\"Bounding Rectangle hieght is zero\")\n            aspect_ratios_list.append(0)\n        else:\n            \n            aspect_ratio = float(w)/h\n            aspect_ratios_list.append(aspect_ratio)\n\n    total_aspect_ratios = sum(aspect_ratios_list)\n    \n    if len(aspect_ratios_list) == 0:\n        #print(\"Aspect Ratio List is zero length\")\n        avg_aspect_ratio = 0\n    else:\n        avg_aspect_ratio = total_aspect_ratios/len(aspect_ratios_list)\n\n    if display:\n        \n        print(\"Total average aspect ratio -> \" + str(avg_aspect_ratio))\n        print(\"Percentage Area Ratio -> \" + str(percent_ratio))\n        print(\"Total contour area -> \" + str(total_contour_area))\n        print(\"Number of contours detected -> \" + str(len(contours)))\n        \n    else:\n        \n        return float(avg_aspect_ratio), float(percent_ratio), float(total_contour_area), float(len(contours))\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# This is a sample feature data from above image"},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_contour_info = getLungContours('../input/osic-pulmonary-fibrosis-progression/train/ID00007637202177411956430/5.dcm')\ngetContourProperties(sample_contour_info[2], True)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Extracting Metadata from Image"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Getting CT Scan image's meta data\n\ndef get_observation_data(data, display = False):\n    int_columns = [\"SliceThickness\", \"KVP\", \"DistanceSourceToDetector\", \n        \"DistanceSourceToPatient\", \"GantryDetectorTilt\", \"TableHeight\", \n        \"XRayTubeCurrent\", \"GeneratorPower\", \"WindowCenter\", \"WindowWidth\", \n        \"SliceLocation\", \"RescaleIntercept\", \"RescaleSlope\"]\n    \n    bad_data = {}\n    try:\n        image_data = dcmread(data, force = True)\n    except Exception:\n        \n        for k in int_columns:\n            bad_data[k] = 0.0\n        return bad_data\n    \n#     image_data = dcmread(data, force = True)\n    # Dictionary to store the information from the image\n    observation_data = {}\n\n    # Integer columns\n    \n    for k in int_columns:\n        if k in image_data:\n            try:\n                k_value = int(image_data.get(k))\n            except TypeError:\n                k_value = 0\n        else:\n            k_value = 0\n        observation_data[k] = k_value\n    if display:\n        \n        for i in observation_data:\n            print(i + \" -> \" + str(observation_data[i]))\n            \n    else:\n        \n        return observation_data","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Extracting both Meta and Feature data"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Get Meta + contour properties \n\n\ndef getMetaAndContour(data, display = False):\n    patient, week, contour = getLungContours(data)\n    avg_aspect_ratio, percent_ratio, total_contour_area, no_of_contours = getContourProperties(contour)\n    metadata = get_observation_data(data)\n    \n    output = {\n        'Patient': str(patient),\n        'Weeks': float(week),\n        'AverageAspectRatio': float(avg_aspect_ratio),\n        'PercentageRatio': float(percent_ratio),\n        'TotalContourArea': float(total_contour_area),\n        'NumberOfContours': float(no_of_contours),\n        'SliceThickness': float(metadata['SliceThickness']),\n        'KVP': float(metadata['KVP']),\n        'DistanceSourceToDetector': float(metadata['DistanceSourceToDetector']),\n        'DistanceSourceToPatient': float(metadata['DistanceSourceToPatient']),\n        'GantryDetectorTilt': float(metadata['GantryDetectorTilt']),\n        'TableHeight': float(metadata['TableHeight']),\n        'XRayTubeCurrent': float(metadata['XRayTubeCurrent']),\n        'GeneratorPower': float(metadata['GeneratorPower']),\n        'WindowCenter': float(metadata['WindowCenter']),\n        'WindowWidth': float(metadata['WindowWidth']),\n        'SliceLocation': float(metadata['SliceLocation']),\n        'RescaleIntercept': float(metadata['RescaleIntercept']),\n        'RescaleSlope': float(metadata['RescaleSlope'])\n    }\n    \n    if display:\n        print(output)\n    else:\n        return output\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Running Spark job\n\nI ran below spark job in aws spark cluster for exctracting image feature data from nearly 33k dcm files.  I saved the data in parquet format for furthur analysis since running the job each time cost more money."},{"metadata":{"trusted":true},"cell_type":"code","source":"from pydicom import dcmread\nfrom pydicom.filebase import DicomBytesIO\nfrom pyspark.sql.types import StringType, FloatType, StructType, StructField, MapType, ByteType, Row\nfrom pyspark.sql.functions import udf, col, lit\nfrom pyspark import SparkContext, SparkConf\nfrom pyspark.sql import SparkSession\n\n\nsc = SparkContext.getOrCreate()\nspark = SparkSession(sc)\n\n# Dicom parser    \ndef getMap(data):\n    binary = getDicomData(data[1])\n    dict_ouptut = getMetaAndContour(binary)\n    return (dict_ouptut, )\n    \nrdd = sc.binaryFiles('../input/osic-pulmonary-fibrosis-progression/train/*/*.dcm').map(lambda x: getMap(x))\nhasattr(rdd, \"toDF\")\n                                                    \n # Converting RDD to Dataframe                                                    \ndf_image = rdd.toDF().cache()\n\ndef foldl(zero, combine, elements):\n    if callable(zero):\n        result = zero()\n    else:\n        result = zero\n    for x in elements:\n        result = combine(result, x)\n    return result\n\ndef operator(df, elem):\n    if elem == \"Patient\":\n        df.withColumn(elem, df)\n        \n# Setting correct data Type for each column        \ndfWithCols = df_image.select(df_image['_1.Patient'], df_image['_1.Weeks'], df_image['_1.AverageAspectRatio'], \\\n               df_image['_1.NumberOfContours'], df_image['_1.PercentageRatio'], \\\n               df_image['_1.TotalContourArea']).withColumn('Week', col('Weeks').cast(FloatType())).\\\n                withColumn('AverageAspectRatio', col('AverageAspectRatio').cast(FloatType())).\\\n                withColumn('NumberOfContours', col('NumberOfContours').cast(FloatType())).\\\n                withColumn('PercentageRatio', col('PercentageRatio').cast(FloatType())).\\\n                withColumn('TotalContourArea', col('TotalContourArea').cast(FloatType())).drop(col('Weeks'))\n\n# Writing data to S3 bucket in parquet format, Parquest maintain the data types of each column while extracting again.\ndfWithCols.write.mode('overwrite').partitionBy('Patient', 'Week').parquet('s3://osis-parquet/CTProps/props')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Joining Patient data with Image data"},{"metadata":{},"cell_type":"markdown","source":"# Reading Final data from parquet to Spark dataframe# "},{"metadata":{"trusted":true},"cell_type":"code","source":"dataframe = spark.read.parquet(\"../input/parquet/CTProps/final_data\")\ndataframe.toPandas()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Finding Correlation between Image data and patient data\n\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"from pyspark.ml.stat import Correlation\nimport matplotlib.pyplot as plt\nfrom pyspark.ml.feature import Normalizer, VectorAssembler\nimport seaborn as sns\nimport pandas as pd\nfinal_cols = [\n    'Age',\n    'Week',\n    'TotalContourArea',\n    'AverageAspectRatio',\n    'PercentageRatio',\n    'NumberOfContours',\n    'FVC',\n    'Percent'\n]\nfig, ax = plt.subplots(figsize=(10,10))\nvector = VectorAssembler(\n    inputCols= final_cols,\n    outputCol= 'vector',\n    handleInvalid=\"keep\"\n    ).transform(dataframe)\nnormalized_vector = Normalizer(inputCol='vector', outputCol='normalized_vector').transform(vector)\npearsonCorr = Correlation.corr(normalized_vector, 'normalized_vector').collect()[0][0].toArray()\ndf = pd.DataFrame(pearsonCorr, columns=final_cols )\nsns.heatmap(df, xticklabels= final_cols, yticklabels= final_cols, ax = ax)\nprint(\"Following Observations are made from correlation matrix\")\nprint(\"1. Total Contour Area is very negatively correlated with FVC\")\nprint(\"2. Average Aspect Ratio is very positively correlated with FVC\")\nprint(\"3. Percent Ratio is very negatively correlated with FVC\")\nprint(\"4. Number Of Contours is very LESS correlated with FVC, Its value is around 0.0 - 0.25\")\nprint(\"I think it is better to neglect Number Of contours from analysis\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Normalizing Week in 100"},{"metadata":{"trusted":true},"cell_type":"code","source":"\nfrom pyspark.sql.functions import col\nfrom pyspark.sql import functions as F\neffective_dataframe = dataframe.join( dataframe.groupBy(\"Patient\")\\\n                                    .agg(F.max(\"Week\").alias(\"max_week\")),\\\n                                    on=['Patient'],\\\n                                    how='inner').withColumn(\"effective_Week\", \\\n                                                            col('Week')/col('max_week')*100)\neffective_dataframe.toPandas()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Scatter plots among different columns in dataframe"},{"metadata":{"trusted":true},"cell_type":"code","source":"\nfig, axs = plt.subplots(3, 2, figsize=(20,20))\n# pd_dataframe = dataframe.toPandas()\npd_dataframe = effective_dataframe.toPandas()\ngroups = pd_dataframe.groupby(\"SmokingStatus\")\n\nfor name, group in groups:\n    axs[0, 0].set_title('FVC vs Total Conntour Area', fontweight = 'bold' , fontsize = 14)\n    axs[0, 0].plot(group[\"FVC\"], group[\"TotalContourArea\"], marker=\"o\",alpha = 0.8, linestyle=\"\", label=name)\naxs[0, 0].legend()\naxs[0, 0].set_xlabel(\"FVC\", fontweight = 'bold' , fontsize = 14)\naxs[0, 0].set_ylabel(\"Contour Area\", fontweight = 'bold' , fontsize = 14)\n\nfor name, group in groups:\n    axs[0, 1].set_title('FVC vs Percentage Ration of Area', fontweight = 'bold' , fontsize = 14)\n    axs[0, 1].plot(group[\"FVC\"], group[\"PercentageRatio\"], marker=\"o\",alpha = 0.8, linestyle=\"\", label=name)\naxs[0, 1].legend()\naxs[0, 1].set_xlabel(\"FVC\", fontweight = 'bold' , fontsize = 14)\naxs[0, 1].set_ylabel(\"Percentage Ratio\", fontweight = 'bold' , fontsize = 14)\n\n\nfor name, group in groups:\n    axs[1, 0].set_title('Week vs Total Conntour Area', fontweight = 'bold' , fontsize = 14)\n    axs[1, 0].plot(group[\"Week\"], group[\"TotalContourArea\"], marker=\"o\",alpha = 0.8, linestyle=\"\", label=name)\naxs[1, 0].legend()\naxs[1, 0].set_xlabel(\"Week\", fontweight = 'bold' , fontsize = 14)\naxs[1, 0].set_ylabel(\"Contour Area\", fontweight = 'bold' , fontsize = 14)\n\nfor name, group in groups:\n    axs[1, 1].set_title('Week vs Percentage Ration of Area', fontweight = 'bold' , fontsize = 14)\n    axs[1, 1].plot(group[\"Week\"], group[\"PercentageRatio\"], marker=\"o\",alpha = 0.8, linestyle=\"\", label=name)\naxs[1, 1].legend()\naxs[1, 1].set_xlabel(\"Week\", fontweight = 'bold' , fontsize = 14)\naxs[1, 1].set_ylabel(\"Percentage Ratio\", fontweight = 'bold' , fontsize = 14)\n\n\n\n# for name, group in groups:\n#     axs[1, 0].set_title('Week vs Total Conntour Area', fontweight = 'bold' , fontsize = 14)\n#     axs[1, 0].plot(group[\"effective_Week\"], group[\"TotalContourArea\"], marker=\"o\",alpha = 0.8, linestyle=\"\", label=name)\n# axs[1, 0].legend()\n# axs[1, 0].set_xlabel(\"Week\", fontweight = 'bold' , fontsize = 14)\n# axs[1, 0].set_ylabel(\"Contour Area\", fontweight = 'bold' , fontsize = 14)\n\n# for name, group in groups:\n#     axs[1, 1].set_title('Week vs Percentage Ration of Area', fontweight = 'bold' , fontsize = 14)\n#     axs[1, 1].plot(group[\"effective_Week\"], group[\"PercentageRatio\"], marker=\"o\",alpha = 0.8, linestyle=\"\", label=name)\n# axs[1, 1].legend()\n# axs[1, 1].set_xlabel(\"Week\", fontweight = 'bold' , fontsize = 14)\n# axs[1, 1].set_ylabel(\"Percentage Ratio\", fontweight = 'bold' , fontsize = 14)\n\n\n\n\n\n\nfor name, group in groups:\n    axs[2, 0].set_title('Age vs Aspect Ratio', fontweight = 'bold' , fontsize = 14)\n    axs[2, 0].plot(group[\"Age\"], group[\"AverageAspectRatio\"], marker=\"o\",alpha = 0.8, linestyle=\"\", label=name)\naxs[2, 0].legend()\naxs[2, 0].set_xlabel(\"Age\", fontweight = 'bold' , fontsize = 14)\naxs[2, 0].set_ylabel(\" Aspect Ratio\", fontweight = 'bold' , fontsize = 14)\n\nfor name, group in groups:\n    axs[2, 1].set_title('FVC vs Aspect Ratio', fontweight = 'bold' , fontsize = 14)\n    axs[2, 1].plot(group[\"FVC\"], group[\"AverageAspectRatio\"], marker=\"o\",alpha = 0.8, linestyle=\"\", label=name)\naxs[2, 1].legend()\naxs[2, 1].set_xlabel(\"FVC\", fontweight = 'bold' , fontsize = 14)\naxs[2, 1].set_ylabel(\"Aspect Ratio\", fontweight = 'bold' , fontsize = 14)\n\nplt.show()\n\nprint(\"Values of Percentage Ratio and Contour Area against FVC and Weeks are much lower for Patients who never smoked And is much higher for Ex smokers\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Creating a machine learning model using Lasso + Ridge Regression"}],"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":4}