## Initial setup

```python
from google.colab import drive
drive.mount('/content/drive')
```

    Mounted at /content/drive

```python
import tensorflow as tf
print(tf.__version__)
import torch
print(torch.__version__)
import matplotlib
print(matplotlib.__version__)
```

    2.8.0
    1.10.0+cu111
    3.2.2

```python
!nvidia-smi
```

    Fri Jan 21 19:41:32 2022
    +-----------------------------------------------------------------------------+
    | NVIDIA-SMI 495.46       Driver Version: 460.32.03    CUDA Version: 11.2     |
    |-------------------------------+----------------------+----------------------+
    | GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
    | Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
    |                               |                      |               MIG M. |
    |===============================+======================+======================|
    |   0  Tesla T4            Off  | 00000000:00:04.0 Off |                    0 |
    | N/A   34C    P8     9W /  70W |      0MiB / 15109MiB |      0%      Default |
    |                               |                      |                  N/A |
    +-------------------------------+----------------------+----------------------+

    +-----------------------------------------------------------------------------+
    | Processes:                                                                  |
    |  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
    |        ID   ID                                                   Usage      |
    |=============================================================================|
    |  No running processes found                                                 |
    +-----------------------------------------------------------------------------+

```python
# Other imports
! pip install tensorflow_addons
! pip install tensorflow_io

import os
from tensorflow.keras.layers import *
from tensorflow.keras.models import *
from keras.callbacks import Callback, EarlyStopping, ModelCheckpoint
from tensorflow.keras.applications.resnet50 import preprocess_input
from tensorflow.keras.preprocessing.image import load_img

import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
from matplotlib.ticker import MultipleLocator, FormatStrFormatter, AutoMinorLocator
from imutils import paths
from tqdm import tqdm
import tensorflow as tf
import tensorflow_addons as tfa
import tensorflow_datasets as tfds
import tensorflow_io as tfio
import tensorflow_hub as hub
import numpy as np
import cv2
import pandas as pd
import seaborn as sns
from scipy.stats import mannwhitneyu
from sklearn.preprocessing import LabelEncoder
from sklearn.cluster import KMeans
import sklearn.manifold
from sklearn.metrics.pairwise import cosine_similarity as cos
from sympy.utilities.iterables import multiset_permutations
from sklearn.metrics import accuracy_score, f1_score,precision_score, recall_score, roc_auc_score, confusion_matrix
from sklearn.model_selection import *
from sklearn.preprocessing import StandardScaler
from IPython.display import Image, display

import zipfile
import concurrent.futures

# Random seed fix
random_seed = 42
tf.random.set_seed(random_seed)
np.random.seed(random_seed)
```

    Collecting tensorflow_addons
      Downloading tensorflow_addons-0.16.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (1.1 MB)
         |████████████████████████████████| 1.1 MB 6.4 MB/s
    Requirement already satisfied: typeguard>=2.7 in /usr/local/lib/python3.7/dist-packages (from tensorflow_addons) (2.7.1)
    Installing collected packages: tensorflow-addons
    Successfully installed tensorflow-addons-0.16.1
    Collecting tensorflow_io
      Downloading tensorflow_io-0.24.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (23.4 MB)
         |████████████████████████████████| 23.4 MB 1.8 MB/s
    Requirement already satisfied: tensorflow-io-gcs-filesystem==0.24.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow_io) (0.24.0)
    Installing collected packages: tensorflow-io
    Successfully installed tensorflow-io-0.24.0

## Dataset gathering and preparation

```python
%cd /content/drive/MyDrive/nanowire-morphology-classification-project
```

```python
training_batch_size = 4

BATCH_SIZE = training_batch_size

imageSize = 224

category_names = ['bundle', 'dispersed', 'network', 'singular']
color_method = ['C0', 'C1', 'C2', 'C3', 'C4']
color = ['black', 'magenta', 'cyan', 'yellow']
marker = ['o', 's', '<', '>', '^']
seaborn_palette = sns.color_palette("colorblind")
```

# generating the jpg images

```python
# generating jpg images from the original tif images

np.random.seed(random_seed)
peptide_morph_train_path = "/content/drive/MyDrive/TEM image datasets/2022-nanowire-morphology"
peptide_morph_images_train = list(paths.list_files(basePath=peptide_morph_train_path, validExts='tif'))
peptide_morph_images_train = np.random.choice(np.array(peptide_morph_images_train), len(peptide_morph_images_train), replace=False)
print(len(peptide_morph_images_train))

for i in range(len(peptide_morph_images_train)):
  img = cv2.imread(peptide_morph_images_train[i])
  if img is None:
    continue
  img = cv2.imread(peptide_morph_images_train[i])
  cv2.imwrite('%s.jpg' %peptide_morph_images_train[i].split(".")[0], img)
```

    400

## image data augmentation for the singular morphology

```python
# generating augmented images for the singular morphology
np.random.seed(random_seed)
peptide_morph_train_path = "/content/drive/MyDrive/TEM image datasets/2022-nanowire-morphology/singular"
peptide_morph_images_train = list(paths.list_files(basePath=peptide_morph_train_path, validExts='jpg'))
peptide_morph_images_train = np.random.choice(np.array(peptide_morph_images_train), len(peptide_morph_images_train), replace=False)
print(len(peptide_morph_images_train))

for i in range(len(peptide_morph_images_train)):
  # these are for augmentating the singular morphology by generating 3 different rotated images (90, 180, 270 degrees)
  img = cv2.rotate(img, cv2.cv2.ROTATE_90_CLOCKWISE)
  cv2.imwrite('%s_1.jpg' %peptide_morph_images_train[i].split(".")[0], img)
  img = cv2.rotate(img, cv2.cv2.ROTATE_90_CLOCKWISE)
  cv2.imwrite('%s_2.jpg' %peptide_morph_images_train[i].split(".")[0], img)
  img = cv2.rotate(img, cv2.cv2.ROTATE_90_CLOCKWISE)
  cv2.imwrite('%s_3.jpg' %peptide_morph_images_train[i].split(".")[0], img)

```

# generating segmentation ground truth binary maps (seg_mask.npz files)

```python
np.random.seed(random_seed)
peptide_morph_seglabel_train_path = "/content/drive/MyDrive/TEM image datasets/2022-nanowire-morphology"
peptide_morph_images_train_seglabel = list(paths.list_files(basePath=peptide_morph_seglabel_train_path, validExts='png'))
peptide_morph_images_train_seglabel = np.random.choice(np.array(peptide_morph_images_train_seglabel), len(peptide_morph_images_train_seglabel), replace=False)
print(len(peptide_morph_images_train_seglabel))
```

```python
def generate_ground_truth_images(image, resolution):
  image_bool = np.ones((resolution, resolution))
  for i in range(image.shape[0]):
    for j in range(image.shape[1]):
      if image[i, j, 1] == image[i, j, 2]:
        image_bool[i, j] = 0            # background is black with code of 0
      else:
        image_bool[i, j] = 1            # nanowire is white with code of 1
  return image_bool
```

```python
segmentation_class_labels = []
for i in range(peptide_morph_images_train_seglabel.shape[0]):
  seg_class_label = peptide_morph_images_train_seglabel[i].split("/")[-2]
  segmentation_class_labels.append(seg_class_label)
le = LabelEncoder()
peptide_morph_train_seg_enc = le.fit_transform(segmentation_class_labels)
```

```python
image_mask = np.zeros((len(peptide_morph_images_train_seglabel), imageSize, imageSize))

for i in range(len(peptide_morph_images_train_seglabel)):

  # these were used to create the ground truth grayscale images from the manual segmentation labels.
  image_string = tf.io.read_file(peptide_morph_images_train_seglabel[i])
  image = tf.image.decode_image(image_string, channels=3) / 255
  image = tf.image.resize(image, (imageSize, imageSize))
  image = tf.image.convert_image_dtype(image, tf.float32)
  trans_nd_image_array = image.numpy()
  image_mask[i] = generate_ground_truth_images(trans_nd_image_array, imageSize)

np.savez_compressed('seg_mask_res512.npz', mask=image_mask)

# once we have the seg_mask saved, we can directly load from npz file
# image_mask = np.load('seg_mask_res%i.npz' % (imageSize), allow_pickle=True)['mask']
```

    WARNING:tensorflow:5 out of the last 5 calls to <function parse_images at 0x7f9490054b90> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.
    WARNING:tensorflow:6 out of the last 6 calls to <function parse_images at 0x7f9490054b90> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.


```python
pip install cirq
```

    Collecting cirq
      Downloading cirq-0.13.1-py3-none-any.whl (7.7 kB)
    Collecting cirq-rigetti==0.13.1
      Downloading cirq_rigetti-0.13.1-py3-none-any.whl (55 kB)
         |████████████████████████████████| 55 kB 2.2 MB/s
    Collecting cirq-aqt==0.13.1
      Downloading cirq_aqt-0.13.1-py3-none-any.whl (18 kB)
    Collecting cirq-pasqal==0.13.1
      Downloading cirq_pasqal-0.13.1-py3-none-any.whl (29 kB)
    Collecting cirq-ionq==0.13.1
      Downloading cirq_ionq-0.13.1-py3-none-any.whl (47 kB)
         |████████████████████████████████| 47 kB 4.3 MB/s
    Collecting cirq-core==0.13.1
      Downloading cirq_core-0.13.1-py3-none-any.whl (1.6 MB)
         |████████████████████████████████| 1.6 MB 8.8 MB/s
    Collecting cirq-google==0.13.1
      Downloading cirq_google-0.13.1-py3-none-any.whl (437 kB)
         |████████████████████████████████| 437 kB 55.1 MB/s
    Collecting cirq-web==0.13.1
      Downloading cirq_web-0.13.1-py3-none-any.whl (328 kB)
         |████████████████████████████████| 328 kB 58.8 MB/s
    Requirement already satisfied: requests~=2.18 in /usr/local/lib/python3.7/dist-packages (from cirq-aqt==0.13.1->cirq) (2.23.0)
    Requirement already satisfied: networkx~=2.4 in /usr/local/lib/python3.7/dist-packages (from cirq-core==0.13.1->cirq) (2.6.3)
    Requirement already satisfied: scipy in /usr/local/lib/python3.7/dist-packages (from cirq-core==0.13.1->cirq) (1.4.1)
    Requirement already satisfied: tqdm in /usr/local/lib/python3.7/dist-packages (from cirq-core==0.13.1->cirq) (4.62.3)
    Requirement already satisfied: sympy in /usr/local/lib/python3.7/dist-packages (from cirq-core==0.13.1->cirq) (1.7.1)
    Collecting duet~=0.2.0
      Downloading duet-0.2.3-py3-none-any.whl (30 kB)
    Requirement already satisfied: typing-extensions in /usr/local/lib/python3.7/dist-packages (from cirq-core==0.13.1->cirq) (3.10.0.2)
    Requirement already satisfied: pandas in /usr/local/lib/python3.7/dist-packages (from cirq-core==0.13.1->cirq) (1.3.5)
    Requirement already satisfied: numpy~=1.16 in /usr/local/lib/python3.7/dist-packages (from cirq-core==0.13.1->cirq) (1.21.5)
    Requirement already satisfied: sortedcontainers~=2.0 in /usr/local/lib/python3.7/dist-packages (from cirq-core==0.13.1->cirq) (2.4.0)
    Requirement already satisfied: matplotlib~=3.0 in /usr/local/lib/python3.7/dist-packages (from cirq-core==0.13.1->cirq) (3.2.2)
    Requirement already satisfied: google-api-core[grpc]<2.0.0dev,>=1.14.0 in /usr/local/lib/python3.7/dist-packages (from cirq-google==0.13.1->cirq) (1.26.3)
    Requirement already satisfied: protobuf>=3.13.0 in /usr/local/lib/python3.7/dist-packages (from cirq-google==0.13.1->cirq) (3.17.3)
    Collecting pyjwt~=1.7.1
      Downloading PyJWT-1.7.1-py2.py3-none-any.whl (18 kB)
    Collecting rfc3339~=6.2
      Downloading rfc3339-6.2-py3-none-any.whl (5.5 kB)
    Collecting iso8601~=0.1.14
      Downloading iso8601-0.1.16-py2.py3-none-any.whl (10 kB)
    Collecting qcs-api-client~=0.8.0
      Downloading qcs_api_client-0.8.0-py3-none-any.whl (97 kB)
         |████████████████████████████████| 97 kB 5.8 MB/s
    Requirement already satisfied: idna~=2.10 in /usr/local/lib/python3.7/dist-packages (from cirq-rigetti==0.13.1->cirq) (2.10)
    Collecting httpcore~=0.11.1
      Downloading httpcore-0.11.1-py3-none-any.whl (52 kB)
         |████████████████████████████████| 52 kB 1.3 MB/s
    Requirement already satisfied: python-dateutil~=2.8.1 in /usr/local/lib/python3.7/dist-packages (from cirq-rigetti==0.13.1->cirq) (2.8.2)
    Collecting pyquil~=3.0.0
      Downloading pyquil-3.0.1-py3-none-any.whl (220 kB)
         |████████████████████████████████| 220 kB 66.0 MB/s
    Collecting retrying~=1.3.3
      Downloading retrying-1.3.3.tar.gz (10 kB)
    Collecting attrs~=20.3.0
      Downloading attrs-20.3.0-py2.py3-none-any.whl (49 kB)
         |████████████████████████████████| 49 kB 5.4 MB/s
    Collecting sniffio~=1.2.0
      Downloading sniffio-1.2.0-py3-none-any.whl (10 kB)
    Collecting rfc3986~=1.5.0
      Downloading rfc3986-1.5.0-py2.py3-none-any.whl (31 kB)
    Collecting certifi~=2021.5.30
      Downloading certifi-2021.5.30-py2.py3-none-any.whl (145 kB)
         |████████████████████████████████| 145 kB 65.9 MB/s
    Collecting h11~=0.9.0
      Downloading h11-0.9.0-py2.py3-none-any.whl (53 kB)
         |████████████████████████████████| 53 kB 1.7 MB/s
    Collecting six~=1.16.0
      Downloading six-1.16.0-py2.py3-none-any.whl (11 kB)
    Collecting pydantic~=1.8.2
      Downloading pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl (10.1 MB)
         |████████████████████████████████| 10.1 MB 25.5 MB/s
    Collecting toml~=0.10.2
      Downloading toml-0.10.2-py2.py3-none-any.whl (16 kB)
    Collecting httpx~=0.15.5
      Downloading httpx-0.15.5-py3-none-any.whl (65 kB)
         |████████████████████████████████| 65 kB 3.0 MB/s
    Requirement already satisfied: google-auth<2.0dev,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq-google==0.13.1->cirq) (1.35.0)
    Requirement already satisfied: setuptools>=40.3.0 in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq-google==0.13.1->cirq) (57.4.0)
    Requirement already satisfied: pytz in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq-google==0.13.1->cirq) (2018.9)
    Requirement already satisfied: packaging>=14.3 in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq-google==0.13.1->cirq) (21.3)
    Requirement already satisfied: googleapis-common-protos<2.0dev,>=1.6.0 in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq-google==0.13.1->cirq) (1.54.0)
    Requirement already satisfied: grpcio<2.0dev,>=1.29.0 in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq-google==0.13.1->cirq) (1.43.0)
    Requirement already satisfied: pyasn1-modules>=0.2.1 in /usr/local/lib/python3.7/dist-packages (from google-auth<2.0dev,>=1.21.1->google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq-google==0.13.1->cirq) (0.2.8)
    Requirement already satisfied: cachetools<5.0,>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from google-auth<2.0dev,>=1.21.1->google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq-google==0.13.1->cirq) (4.2.4)
    Requirement already satisfied: rsa<5,>=3.1.4 in /usr/local/lib/python3.7/dist-packages (from google-auth<2.0dev,>=1.21.1->google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq-google==0.13.1->cirq) (4.8)
    Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib~=3.0->cirq-core==0.13.1->cirq) (3.0.7)
    Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib~=3.0->cirq-core==0.13.1->cirq) (1.3.2)
    Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/dist-packages (from matplotlib~=3.0->cirq-core==0.13.1->cirq) (0.11.0)
    Requirement already satisfied: pyasn1<0.5.0,>=0.4.6 in /usr/local/lib/python3.7/dist-packages (from pyasn1-modules>=0.2.1->google-auth<2.0dev,>=1.21.1->google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq-google==0.13.1->cirq) (0.4.8)
    Collecting lark<0.12.0,>=0.11.1
      Downloading lark-0.11.3.tar.gz (229 kB)
         |████████████████████████████████| 229 kB 54.2 MB/s
    Collecting scipy
      Downloading scipy-1.7.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (38.1 MB)
         |████████████████████████████████| 38.1 MB 1.3 MB/s
    Collecting importlib-metadata<4.0.0,>=3.7.3
      Downloading importlib_metadata-3.10.1-py3-none-any.whl (14 kB)
    Collecting rpcq<4.0.0,>=3.6.0
      Downloading rpcq-3.9.2.tar.gz (43 kB)
         |████████████████████████████████| 43 kB 1.9 MB/s
    Collecting retry<0.10.0,>=0.9.2
      Downloading retry-0.9.2-py2.py3-none-any.whl (8.0 kB)
    Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata<4.0.0,>=3.7.3->pyquil~=3.0.0->cirq-rigetti==0.13.1->cirq) (3.7.0)
    Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests~=2.18->cirq-aqt==0.13.1->cirq) (1.24.3)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests~=2.18->cirq-aqt==0.13.1->cirq) (3.0.4)
    Requirement already satisfied: py<2.0.0,>=1.4.26 in /usr/local/lib/python3.7/dist-packages (from retry<0.10.0,>=0.9.2->pyquil~=3.0.0->cirq-rigetti==0.13.1->cirq) (1.11.0)
    Requirement already satisfied: decorator>=3.4.2 in /usr/local/lib/python3.7/dist-packages (from retry<0.10.0,>=0.9.2->pyquil~=3.0.0->cirq-rigetti==0.13.1->cirq) (4.4.2)
    Collecting msgpack<1.0,>=0.6
      Downloading msgpack-0.6.2-cp37-cp37m-manylinux1_x86_64.whl (243 kB)
         |████████████████████████████████| 243 kB 38.9 MB/s
    Collecting python-rapidjson
      Downloading python_rapidjson-1.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.6 MB)
         |████████████████████████████████| 1.6 MB 42.3 MB/s
    Requirement already satisfied: pyzmq>=17 in /usr/local/lib/python3.7/dist-packages (from rpcq<4.0.0,>=3.6.0->pyquil~=3.0.0->cirq-rigetti==0.13.1->cirq) (22.3.0)
    Collecting ruamel.yaml
      Downloading ruamel.yaml-0.17.21-py3-none-any.whl (109 kB)
         |████████████████████████████████| 109 kB 51.8 MB/s
    Collecting ruamel.yaml.clib>=0.2.6
      Downloading ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl (546 kB)
         |████████████████████████████████| 546 kB 50.5 MB/s
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.7/dist-packages (from sympy->cirq-core==0.13.1->cirq) (1.2.1)
    Building wheels for collected packages: lark, retrying, rpcq
      Building wheel for lark (setup.py) ... done
      Created wheel for lark: filename=lark-0.11.3-py2.py3-none-any.whl size=99648 sha256=e00413a300dcc7c31665a6a84daffa63402e5eb25916521a2eb67811ec94ac9f
      Stored in directory: /root/.cache/pip/wheels/d7/61/3c/9ac365f55966367be8d77dbeb21a3ddece3c466e660121e8d6
      Building wheel for retrying (setup.py) ... done
      Created wheel for retrying: filename=retrying-1.3.3-py3-none-any.whl size=11447 sha256=f4be6e4398f507241c3daeffc5503293ac6d6dadbff0542c6babd88de4f47c83
      Stored in directory: /root/.cache/pip/wheels/f9/8d/8d/f6af3f7f9eea3553bc2fe6d53e4b287dad18b06a861ac56ddf
      Building wheel for rpcq (setup.py) ... done
      Created wheel for rpcq: filename=rpcq-3.9.2-py3-none-any.whl size=45877 sha256=13e9c19f1bffacc3eca5bdcb1781e0a6b0386b0b5dd8d12be9b78a88ce9d3875
      Stored in directory: /root/.cache/pip/wheels/96/fb/72/b2179c8c6be1c6ded2d987247ac425957ca56208d81e99a2f2
    Successfully built lark retrying rpcq
    Installing collected packages: sniffio, six, rfc3986, h11, ruamel.yaml.clib, httpcore, certifi, toml, ruamel.yaml, rfc3339, retrying, python-rapidjson, pyjwt, pydantic, msgpack, iso8601, httpx, attrs, scipy, rpcq, retry, qcs-api-client, lark, importlib-metadata, duet, pyquil, cirq-core, cirq-web, cirq-rigetti, cirq-pasqal, cirq-ionq, cirq-google, cirq-aqt, cirq
      Attempting uninstall: six
        Found existing installation: six 1.15.0
        Uninstalling six-1.15.0:
          Successfully uninstalled six-1.15.0
      Attempting uninstall: certifi
        Found existing installation: certifi 2021.10.8
        Uninstalling certifi-2021.10.8:
          Successfully uninstalled certifi-2021.10.8
      Attempting uninstall: msgpack
        Found existing installation: msgpack 1.0.3
        Uninstalling msgpack-1.0.3:
          Successfully uninstalled msgpack-1.0.3
      Attempting uninstall: attrs
        Found existing installation: attrs 21.4.0
        Uninstalling attrs-21.4.0:
          Successfully uninstalled attrs-21.4.0
      Attempting uninstall: scipy
        Found existing installation: scipy 1.4.1
        Uninstalling scipy-1.4.1:
          Successfully uninstalled scipy-1.4.1
      Attempting uninstall: importlib-metadata
        Found existing installation: importlib-metadata 4.11.1
        Uninstalling importlib-metadata-4.11.1:
          Successfully uninstalled importlib-metadata-4.11.1
    ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
    tensorflow 2.8.0 requires tf-estimator-nightly==2.8.0.dev2021122109, which is not installed.
    markdown 3.3.6 requires importlib-metadata>=4.4; python_version < "3.10", but you have importlib-metadata 3.10.1 which is incompatible.
    google-colab 1.0.0 requires six~=1.15.0, but you have six 1.16.0 which is incompatible.
    datascience 0.10.6 requires folium==0.2.1, but you have folium 0.8.3 which is incompatible.
    albumentations 0.1.12 requires imgaug<0.2.7,>=0.2.5, but you have imgaug 0.2.9 which is incompatible.
    Successfully installed attrs-20.3.0 certifi-2021.5.30 cirq-0.13.1 cirq-aqt-0.13.1 cirq-core-0.13.1 cirq-google-0.13.1 cirq-ionq-0.13.1 cirq-pasqal-0.13.1 cirq-rigetti-0.13.1 cirq-web-0.13.1 duet-0.2.3 h11-0.9.0 httpcore-0.11.1 httpx-0.15.5 importlib-metadata-3.10.1 iso8601-0.1.16 lark-0.11.3 msgpack-0.6.2 pydantic-1.8.2 pyjwt-1.7.1 pyquil-3.0.1 python-rapidjson-1.6 qcs-api-client-0.8.0 retry-0.9.2 retrying-1.3.3 rfc3339-6.2 rfc3986-1.5.0 rpcq-3.9.2 ruamel.yaml-0.17.21 ruamel.yaml.clib-0.2.6 scipy-1.7.3 six-1.16.0 sniffio-1.2.0 toml-0.10.2

```python
"""
Created on Thu Feb 24 21:54:47 2022

@author: Ananyo
"""
import cirq
import math
import random
import fractions
import time
import matplotlib.pyplot as plt
import sympy
from typing import Callable, Optional, Sequence, Union

"""This Code is capable of implementing the Classical and Quantum Period Finding Part of Shor's Algorithm"""
##############################################################################
#################################  SECTION 1  ################################
##############################################################################

"""Functions Implementing the Quantum Period Finding Section of Algorithm"""

"""
    Class employs Quantum Modular Exponentiation (x^e mod(N)) implementation together
    with the Quantum Phase Estimation to compute the order of x mod(N)
"""
class QModExp(cirq.ArithmeticOperation):

    def __init__(
        self,
        target: Sequence[cirq.Qid],
        exponent: Union[int, Sequence[cirq.Qid]],
        base: int,
        modulus: int,
    ) -> None:
        if len(target) < modulus.bit_length():
            raise ValueError(
                f'Register with {len(target)} qubits is too small for modulus {modulus}'
            )
        self.target = target
        self.exponent = exponent
        self.base = base
        self.modulus = modulus

    def registers(self) -> Sequence[Union[int, Sequence[cirq.Qid]]]:
        return self.target, self.exponent, self.base, self.modulus

    def with_registers(
        self,
        *new_registers: Union[int, Sequence['cirq.Qid']],) -> 'QModExp':
        if len(new_registers) != 4:
            raise ValueError(
                f'Expected 4 registers (target, exponent, base, '
                f'modulus), but got {len(new_registers)}'
            )
        target, exponent, base, modulus = new_registers
        if not isinstance(target, Sequence):
            raise ValueError(f'Target must be a qubit register, got {type(target)}')
        if not isinstance(base, int):
            raise ValueError(f'Base must be a classical constant, got {type(base)}')
        if not isinstance(modulus, int):
            raise ValueError(f'Modulus must be a classical constant, got {type(modulus)}')
        return QModExp(target, exponent, base, modulus)

    def apply(self, *register_values: int) -> int:
        assert len(register_values) == 4
        target, exponent, base, modulus = register_values
        if target >= modulus:
            return target
        return (target * base ** exponent) % modulus

    """
        Declaring Sybmols to be used for Circuit for Quantum Period Finding
        Can be uncommented in case the circuit needs to be printed
    """

    # def _circuit_diagram_info_(
    #     self,
    #     args: cirq.CircuitDiagramInfoArgs,
    # ) -> cirq.CircuitDiagramInfo:
    #     assert args.known_qubits is not None
    #     wire_symbols: List[str] = []
    #     t, e = 0, 0
    #     for qubit in args.known_qubits:
    #         if qubit in self.target:
    #             if t == 0:
    #                 if isinstance(self.exponent, Sequence):
    #                     e_str = 'e'
    #                 else:
    #                     e_str = str(self.exponent)
    #                 wire_symbols.append(f'QModExp(t*{self.base}**{e_str} % {self.modulus})')
    #             else:
    #                 wire_symbols.append('t' + str(t))
    #             t += 1
    #         if isinstance(self.exponent, Sequence) and qubit in self.exponent:
    #             wire_symbols.append('e' + str(e))
    #             e += 1
    #     return cirq.CircuitDiagramInfo(wire_symbols=tuple(wire_symbols))

"""
    Creating the Circuit for Quantum Period Finding
        x: positive integer whose order modulo n is to be found
        N: modulus relative to which the order of x is to be found
    Returns:
        Quantum circuit for finding the order of x modulo N
"""
def quantum_period_finding_circuit(x: int, N: int) -> cirq.Circuit:
    L = N.bit_length()
    target = cirq.LineQubit.range(L)
    exponent = cirq.LineQubit.range(L, 3 * L + 3)
    return cirq.Circuit(
        cirq.X(target[L - 1]),
        cirq.H.on_each(*exponent),
        QModExp(target, exponent, x, N),
        cirq.qft(*exponent, inverse=True),
        cirq.measure(*exponent, key='exponent'),

    )

    """
       Function used to evaluate s/r where r is the order of x modulo N
        x: integer whose order is to be computed, must be greater than one
           and belong to the multiplicative group of integers modulo n (which
           consists of positive integers relatively prime to n),
        n: modulus of the multiplicative group.
    Returns:
        Smallest positive integer r such that x**r == 1 mod n

    """

def read_eigenphase(result: cirq.Result) -> float:

    exponent_as_integer = result.data['exponent'][0]
    exponent_num_bits = result.measurements['exponent'].shape[1]
    return float(exponent_as_integer / 2 ** exponent_num_bits)

    """
    """
def find_order(x: int, N: int) -> Optional[int]:

    if x < 2 or N <= x or math.gcd(x, N) > 1:
        raise ValueError(f'Invalid x={x} for modulus n={N}.')

    circuit = quantum_period_finding_circuit(x, N)
    result = cirq.sample(circuit)
    eigenphase = read_eigenphase(result)
    f = fractions.Fraction.from_float(eigenphase).limit_denominator(N)
    if f.numerator == 0:
        return None  # coverage: ignore
    r = f.denominator
    if x ** r % N != 1:
        return None  # coverage: ignore
    return r

##############################################################################
#################################  SECTION 2  ################################
##############################################################################
"""
     CLassical Computation of Shor's Algorithm
"""

"""
    Function takes a compostite number N and returns its non-trivial factor
"""
def find_factor_of_prime_power(n: int) -> Optional[int]:

    for k in range(2, math.floor(math.log2(n)) + 1):
        c = math.pow(n, 1 / k)
        c1 = math.floor(c)
        if c1 ** k == n:
            return c1
        c2 = math.ceil(c)
        if c2 ** k == n:
            return c2
    return None

"""
    Function checks for prime factors
"""
def find_factor(
    n: int, order_finder: Callable[[int, int], Optional[int]], max_attempts: int = 50
) -> Optional[int]:

    if sympy.isprime(n):
        return None
    if n % 2 == 0:
        return 2
    c = find_factor_of_prime_power(n)
    if c is not None:
        return c
    for _ in range(max_attempts):
        x = random.randint(2, n - 1)
        c = math.gcd(x, n)
        if 1 < c < n:
            return c  # coverage: ignore
        r = order_finder(x, n)
        if r is None:
            continue  # coverage: ignore
        if r % 2 != 0:
            continue  # coverage: ignore
        y = x ** (r // 2) % n
        assert 1 < y < n
        c = math.gcd(y - 1, n)
        if 1 < c < n:
            return c
    return None  # coverage: ignore

##############################################################################
#################################  SECTION 3  ################################
##############################################################################
"""
    Main Function Compiling all parts and implementing Shor's Algorithm
"""
#Algorithm can evaluate all the prime factors of the given number
def main(
    n: int,
    order_finder: Callable[[int, int], Optional[int]] = find_order,
):
    if n < 2:
        raise ValueError('Expected positive integer N must be greater than one.')

    d = find_factor(n, order_finder)

    if d is None:
 #       print(f'No non-trivial factor of {n} found. It is probably a prime.')
      print(f'{n} is Prime')
    else:
        e=n//d
        f=[e]
        g=0
        while(sympy.isprime(e)==False and g!=None):
            g = find_factor(e, order_finder)
            f.append(g)
            e=e//g

        if d!=e and e!=f[0]:
            print(f'Prime Factors of {n}: {d}, {e}', end=" ")
            for i in range(1,len(f)):
                if(i!=len(f)-1 and f[i]!=f[1]):
                    print(",",f[i], end=" ")
                elif(f[i]!=f[1]):
                    print(f[i])
                else:
                    print("")
                    break
        elif d!=e:
            print(f'Prime Factors of {n}: {d}, {e}')

        else:
            print(f'Prime Factor of {n}: {d}')

        assert 1 < d < n
        assert n % d == 0

if __name__ == '__main__':

    t=[]
    for i in range(2,36):
        st=time.time()
        main(i,find_order)
        end=time.time()
        t.append(end-st)

#Plotting the Runtime Vs Integer Plot
plt.scatter(list(range(2,36)), t)
plt.title(" Runtime for Prime Factorization Vs Integer ")
plt.xlabel("Value of Integer")
plt.ylabel("Runtime")
plt.show()
```

    2 is Prime
    3 is Prime
    Prime Factor of 4: 2
    5 is Prime
    Prime Factors of 6: 2, 3
    7 is Prime
    Prime Factor of 8: 2
    Prime Factor of 9: 3
    Prime Factors of 10: 2, 5
    11 is Prime
    Prime Factors of 12: 2, 3
    13 is Prime
    Prime Factors of 14: 2, 7
    Prime Factors of 15: 3, 5
    Prime Factor of 16: 2
    17 is Prime
    Prime Factors of 18: 2, 3
    19 is Prime
    Prime Factors of 20: 2, 5