Tuesday 24 April 2018

Creating a new user and restricting him to a specific folder in AWS EC2 Linux

Creating a new user and restricting him to a specific folder in AWS EC2 Linux

Creating a new website? Need to give someone access to the website folder on your EC2 instance without giving him the complete access to your EC2? If the answer to that is yes you may find this blog helpful. 
I had to work on a website with my project partner when I thought I should use EC2 so that we both can collaborate as well as see the website live. So I needed to give access to my friend so that he can also work on the website, but at the same time, I didn't want him to get access to all the other folders that I had on EC2. 
The whole process can be divided into the following steps
  1. Create a new group and user
  2. Generating SSH keys to login to EC2 from SFTP
  3. Connecting to EC2 as the new user using FileZilla.

Let's dig deeper to understand how each of these steps works. 

Create a new group and user


  • Start by creating a new group. We will add the user we create in the following steps to this group. Use the following code to create a new group.

sudo addgroup exchangefiles

  • Create the root directory for the group. After creating the directory we change the permissions of that directory. We set it to read and execute. You can set this according to your needs. All users in this group will be able to read and execute from this folder. The can write only in their specific folders.
sudo mkdir /var/www/GroupFolder/
sudo chmod g+rx /var/www/GroupFolder/

  • Now create another directory for the user. Give it write permission as well. Same as above you can give the permissions according to your needs. Also, You don't have to create two different directories, you can create just one directory and give it the permissions you need.
sudo mkdir -p /var/www/GroupFolder/files/
sudo chmod g+rwx /var/www/GroupFolder/files/
  • Assign both these directories to the group we created.
sudo chgrp -R exchangefiles /var/www/GroupFolder/




  • Edit /etc/ssh/sshd_config and make sure to add the following at the end of the file:
  •   # Force the connection to use SFTP and chroot to the required directory.  
      ForceCommand internal-sftp  
      ChrootDirectory /var/www/GroupFolder/  
      # Disable tunneling, authentication agent, TCP and X11 forwarding.  
      PermitTunnel no  
      AllowAgentForwarding no  
      AllowTcpForwarding no  
      X11Forwarding no  





  • Now let's create a new user. 
  • sudo adduser -g exchangefiles obama 
    • If you get a command not found error It might be because your environment doesn't include the /usr/sbin directory that holds such system programs. The quick fix should be to use /usr/sbin/adduser instead of just adduser
    • Now that we have made the proper changes let's restart ssh so that it can reflect the changes.
    sudo /sbin/service sshd restart
    You are all set. You have created a new user and group and given the permission of the folder to that group. The user can connect only using SFTP protocol. You can use FileZilla for connecting using you the new user. When you log in you will be in the folder you created above. You cannot go out of that folder.

    Generating SSH keys to login to EC2 from SFTP

    Now for connecting to EC2 as the new user you first need to create the public and private ssh keys. The public ssh key will be in the home folder of the new user and you will download the private key on your system. You have to use this key file(permanent key) on FileZilla to connect to EC2.
    • Go to the home directory of the new user and execute the following commands to create a new folder and set permissions to it.
    cd
    mkdir .ssh
    chmod 700 .ssh
    • Now create a file in .ssh and set its permissions
    touch .ssh/authorized_keys
    chmod 600 .ssh/authorized_keys
    • Now generate your public and private keys using the following command. replace username with the name of the new user that you created
    ssh-keygen -f username
    • This will generate two files username and username.pub. username is your private key and username.pub is your public key.
    • Copy the public key, and then use the Linux cat command to paste the public key into the .ssh/authorized_keys file for the new user.
    cat username.pub > .ssh/authorized_keys
    • Download the private key file to your local system. This will be used to login using SFTP. 

    Connecting to EC2 as the new user using FileZilla.

    • Open FileZilla. Go to File->site manager->New Site. Enter the details here. The host is the public DNS of your EC2. leave the port empty, change the protocol to SFTP, set logon type to "key file", set the user to the new user that you created, browse to where you downloaded your private key and set it in "key file".
    • Click on connect

    That's it, you have now configured your EC2 to give limited accedd to a user. I hope you liked this blog. If you get error or get stuck on some point comment below, I will try my best to help you.

    Sunday 22 April 2018

    Creating Database on AWS and using it on MySQL Workbench

    The relational databases in AWS are under the name of RDS which stands for Relational Database Service. AWS has a lot of different databases supported like Amazon Aurora, PostgreSQL, MySQL, MariaDB, Oracle, and Microsoft SQL Server. You can use the AWS Database Migration Service to easily migrate or replicate your existing databases to Amazon RDS.
    Creating and using a database on AWS can be a little tricky if you are new to it. In this blog I will show you how to create a database in RDS and how to connect to it from MySQL Workbench.

    Steps for creating database instance on AWS RDS


    • click on Launch DB instance
    • Select MySQL and click Next


    • In the next page select Dev/Test MySQL and click Next
    • In the Instance specificationsCheck the Only enable the options eligible for RDS free usage tier. By checking this the options available in the next setting DB instance class is set to db.t2.micro. If you want a bigger database instance you can uncheck this and select the DB instance class which you want. Please keep in mind that you would be billed accordingly.

    • Leave all other options to the default value in Instance specifications.
    • In the Settings part, put in your DB instance identifier, username and password. This username and password would be required to access you database later from MySQL Workbench.

    • Click on Next
    • In Network & Security part set Public accessibility to Yes. This setting will be required to connect from MySQL Workbench.

    • Leave all other settings to default value.
    • In Database options part, Put in your database name.
    • You can leave all other options on this page to the default value.
    • Click on Launch DB Instance at the bottom of the page.
    • It will take 10-12 minutes to create your DB instance.




    Steps for connecting to AWS RDS DB instance from MySQL workbench

    • On your AWS console go to RDS and click on instances. Here you will see the DB instance you just created. 
    • Click on the DB instance to see its details.
    • On you DB instance page scroll down to security groups.



    • Here on the security group for Inbound traffic, you will see an IP address written in the rule. This was your public IP address from which you created the database instance. What this means is it will only allow you to connect to this database instance from this IP address. We want it to be able to connect from anywhere. So to do this we need to edit this rule. 
    • Click on the security group for Inbound connection.
    • It will open a new page. On this page click on the Inbound tab of the security group.



    • Click on edit. 


    • Remove the IP address from source and put 0.0.0.0/0 . It should look like this.

    • Click on Save to save the settings. You have changed the security settings. You will now be able to connect to this database instance from any IP.


     


    • To connect to a database we need four things, host, port, username and password.
    • To see where your database instance is hosted go to your database instance page. See the Connect part. Here you will see the endpoint and the port. The endpoint is the host. Copy these values.
    • Open MySQL Workbench.
    • Click on the + next to MySQL Connections.


    • Enter any name in Connection Name 
    • Enter the host, port, username.
    • Click on test connection
    • If the host, port and username is right it will ask for your password, enter it.
    • If the password is right it will show a success message. Now just click on OK to save it.












    Thursday 12 April 2018

    Implementing lottery scheduling on XV6

    About

    In this blog, I will show you how to implement lottery scheduling for XV6 operating system. XV6 by default uses Round Robin algorithm. Please note that in this implementation, the process sets its own tickets which is not ideal as it may take a huge number and the other processes would have very less chance to run.

    What is XV6?

    xv6 is a re-implementation of Dennis Ritchie's and Ken Thompson's Unix Version 6 (v6).  xv6 loosely follows the structure and style of v6, but is implemented for a modern x86-based multiprocessor using ANSI C.
    source

    What is lottery scheduling?

    Lottery scheduling is a probabilistic scheduling algorithm for processes in an operating system. Processes are each assigned some number of lottery tickets, and the scheduler draws a random ticket to select the next process. The distribution of tickets need not be uniform; granting a process more tickets provides it a relatively higher chance of selection. This technique can be used to approximate other scheduling algorithms, such as Shortest job next and Fair-share scheduling.
    Lottery scheduling solves the problem of starvation. Giving each process at least one lottery ticket guarantees that it has a non-zero probability of being selected at each scheduling operation.

    How lottery scheduling works?

    1. We have two processes A and B. A has 60 tickets (ticket number 1 to 60) and B have 40 tickets (ticket no. 61 to 100).
    2. Scheduler picks a random number from 1 to 100. If the picked no. is from 1 to 60 then A is executed otherwise B is executed. (source)


    Steps

    We need to do the following things to implement lottery scheduling in XV6
    1. Make a system call which allows you to set the tickets for a process.
    2. Code to generate a random number.
    3. In the scheduler function count the total number of tickets for all processes that are runnable.
    4. Generate a random number between 0 and the total tickets calculated above.
    5. When looping through the processes keep the counter of the total number of tickets passed.
    6. Just when the counter becomes greater the random value we got, run the process.
    7. Put a break at the end of for loop so that we don't execute the processes following the process we just run.

    Making a system call

    Each process has a ticket value assigned to it. So to implement this we need to change the proc structure which holds the information about a process and add int tickets which will store its ticket value. 
    Follow this blog to know how to add system call. In the part where you have to write the actual implementation use the following code which sets the number of tickets for a process.

     int sys_settickets(void)  
     {  
       int n;  
      if(argint(0, &n) < 0)  
       return -1;  
      proc->tickets = n;  
      return n;  
     }  


    Allocate default value of tickets when a process is created

    In the proc.c file, enter the following line of code in "allocproc" function just below "found".

     p->tickets = 10;  

    This sets the default ticket value to 10.


    Creating Random number

    1. Include rand.o\ in OBJS in Makefile
    2. Copy files rand.c and rand.h in the XV6 directory.

    After doing these two steps you can include rand.h header file in your proc.c file which is responsible for scheduling all processes.

    Scheduler Function

    The scheduler function has two for loops, one inside the other. The outer for loop just keeps on running forever. The inside for loop loops over all the processes and selects the first process that it finds in a RUNNABLE state. It then runs this process till its time quanta expire or the process yields voluntarily. It then selects the next process in RUNNABLE state and so on. This is the way it implements Round Robin.
    For lottery scheduling, we need the total number of tickets of the processes that are in the RUNNABLE state. To calculate this we put a for loop inside the outer for loop just before the inside for loop executes. This calculates the total number of tickets.
    Now we generate a random number between 0 and the total number of tickets. After we have the random number we execute the for loop that runs processes. When this for loop loops over processes, we keep counting the total number of tickets passed of processed that are in a RUNNABLE state. As soon as the total tickets passed get higher than the random number we got we run that process. 
    Now, after the process runs, we need to put a break at the end of for loop which executes all processes. This is because:
    If we don't break the value of total tickets passed will keep on increasing and will always be higher than the random number.
    Also, after we run a winning process we need to recompute the total number of tickets of all RUNNABLE processes as that value might have changed.












    Tuesday 10 April 2018

    Adding a system call in XV6 OS

    Adding a system call in XV6 OS

    In this blog, I will show you how to add a system call to XV6 OS. We will just add a simple HelloWorld system call which will print hello world and the argument passed to the system call.

    Steps:

    For adding the system call we need to make changes in the following files:
    1. syscall.c
    2. syscall.h
    3. user.h
    4. usys.S
    5. sysproc.c


    First, we add the call to the list in syscall.c





    Next, assign it a number in syscall.h




    give it a prototype in user.h:




    Add it to usys.S, which generates the user-space assembly code for it





    Finally, we add the implementation somewhere (e.g. sysproc.c)



    Testing

    To test if the system call works, create a c file and use the system call in it. Remember to add the c file in Makefile so that you can use it.




    Adding it to make file


    Tuesday 3 April 2018

    Mining on a budget PC

    Building a budget mining PC

    There are lots of youtube videos about building a "budget" mining rig. But most of them at least spend a $1000 on their build and get around $100 a month which makes the payback period 10 months. So, I recently decided to build my own mining PC for just $300 dollars which could generate around $150-$170  per month. My aim was just to test these and I don't actually mine any cryptocurrency.

    Parts

    Motherboard - ASUS M5A78L-M/USB3
    Processor - AMD Fx-8320E
    Power supply - CORSAIR CX Series CX450
    Case - Thermaltake V3 Black Edition
    RAM - Patriot Signature 4GB DDR3
    GPU - MSI RX 570 4Gb gaming X

    All these parts cost me around $300( to see the detailed price of each item you can check out this blog).


    Mining with stock settings

    When I was mining on the stock settings for both the CPU and the GPU, I was getting around 180-200H/s while mining with Minergate and around 22-225H/s with Nicehash for mining Monero(with CPU) and around 500-550H/s with Minergate and 550-600H/s with NiceHash(with GPU). For Ethereum, I was getting a really bad performance of just 10Mh/s with Miner gate. With Nicehash I was around 20Mh/s, which is a lot better than MinerGate but still not the best that RX 570 can do.

    Note: At this point, the gaming driver of AMD RX 570 was installed. I didn't know that AMD has released a different driver for improving mining performance. To download this driver go to this link.


    Optimizing RX 570 to give around 28-29Mh/s while mining eEthereum (Dagger Hashimoto). 

    I did the following modifications to get 28-29Mh/s from my RX 570.

    1. Install mining driver( improves hash rate by around 2Mh/s)
    2. Modify RX 570 ROM ( improved hash rate by around2-2.5Mh/s)
    3. Undervolt RX 570 using afterburner
    4. overclock memory(improved hash rate around 3Mh/s)
    5. Overclocking the core clock speed doesn't help much
    Installing the mining driver is fairly simple. Just go to the link I provided above, download the driver and install it. Modifying the ROM is easy but it fails it can make your GPU useless and you may have to take it to the service center. To modify the ROM I followed this youtube video.
    Undervolting: I saw that undervolting little bit doesn't affect the hash rate at all and brings down the power usage and temperature a little. You can try different values for this in afterburner, I have set it to -100mv.
    Overclocking the memory: This is the step which gave the most performance gain. It was showing  1750Mhz on MSI afterburner, I increased it up to 2022Mhz. If you increase it too much the system will not be stable. The screen will go blank and then come back again with default settings. Settings for my RX 570 are the following:


    Warning: don't overvolt if you are not 100% sure what you are doing. Don't let the fan run at 100% all the time as it may decrease the life of the graphics card. I saw that the fan was running at 99% so I ordered cooler master 120mm 4 case fans set for $11 and it has helped a lot. The fan speed remains around 35% now. If you can keep it with the case open it will help reduce the temperature a lot. I had to close the case as I wanted more protection for the expensive graphics card. If you are sure that your CPU is in a very safe place and keeping the case open wouldn't be a problem then you can keep the case open and use a table fan to remove heat.

    Optimizing PC to get around 330H/s for mining Monero(Cryptonight).

    There were two things I did to get around 330H/s while mining Monero

    1. Overclock the Processor.
    2. Lock Pages in Memory

    Overclock

    You can overclock the processor either through the BIOS or through the software. I don't know much about overclocking so I used the AMD overdrive to overclock the processor. SAVE all your work before trying to overclock the processor as it may make the system unstable and the PC restarts( I learned this the hard way). Always remember that setting the core frequency very high will most probably not harm your computer as long as you are not increasing the voltage. 

    Open AMD OverDrive as administrator(right click -> run as administrator). Click on clock/performance which is under "performance control". Now click on "Turbo Core Control".




    In the Turbo Core Control window on the top right side, you will see the drop-down menu which lets you select the number of cores you want to overclock, change it according to your preferences. On the top left side make sure "Enable Turbo Core" is checked. In this Window, you will see what the CPU core multiplier is by default.


     Now, increase the CPU 0 core multipler slowly by increments of 1 or 0.5. You want to make sure that your system is stable at that multiplier. If you set this value too high the system might restart and the default values will be restored. Not that along with setting the multiplier to 20X, I have also increased the voltage little bit as I saw it was giving a better performance at this value. Don't set the voltage too high as it may damage your system permanently. 



    Locking page files in memory

    Doing this increased the hash rate around 30%.
    To enable the lock pages in memory option
    1. On the Start menu, click Run. In the Open box, type gpedit.msc.
    2. The Group Policy dialog box opens.
    3. On the Group Policy console, expand Computer Configuration, and then expand Windows Settings.
    4. Expand Security Settings, and then expand Local Policies.
    5. Select the User Rights Assignment folder.
    6. The policies will be displayed in the details pane.
    7. In the pane, double-click Lock pages in memory.
    8. In the Local Security Policy Setting dialog box, click Add user or group.
    9. In the window that opens, enter your computer's username and click on search names and it will add your user account. I would suggest adding both your account and the administrator account there.
    10. Click on Ok and then apply.

    Performance after changing the setting to get max output

    Minergate: Monero hash rate- 190-200H/s on CPU,  650-700H/s on GPU. Whenever I start mining( auto mining, which according to them mines currencies which give you the best profit that time) it only mines Monero with both CPU and GPU. I manually started Ethereum mining to see how much hash rate I am getting after all the optimization. Even after all the optimizations, I was only getting 13-13.5Mh/s.

    Nicehash: Monero hash rate- 310-330H/s on CPU. Ethereum hash rate- 28.5Mh/s. So, as you can see from the results Nicehash was performing a lot better than Minergate.

    Minergate vs Nicehash

    As you can see from the results Nicehash is far better than Minergate. If you are using Linux setting up Nicehash may be a little difficult compared to Minergate. If you are mining on a system like this, it may be a month or so before you get your first payment. Nicehash pays you when your balance reaches 0.01BTC for external wallets. No matter which coin you are mining Nicehash automatically converts your earnings to Bitcoin and keeps it until it reaches 0.01BTC. 



    Note: Please try all these modifications at your own risk.



    Detecting Parallelogram in Images

    Detecting Parallelogram in Images



    The steps used in the detection of a parallelogram

    1. Load the raw image and convert it to grayscale using the formula
         2.   Plot the histogram to see what could be the threshold value for thresholding
         3.   Use canny edge detection to detect the edges
         4.   Threshold the image
         5.   Apply hough transform to get the accumulator and the points corresponding to
                each accumulator array value.
         6.  Find every Two pairs of parallel lines and then find two more pairs of parallel lines
                and find their intersection points.
         7.  Check if the intersection points are actually a part of the edge.
         8.  Using the intersection point found, draw the line on the original image to show the
                Parallelogram.


    1.  Loading image and converting it to grayscale

           


    Fig 1: The original color image(left) and the grayscale image(right)


    In this part, we load the raw image and convert it into a grayscale image. The data format of the raw image is as follows:
    The data arrangement for the interleaved raw image format is illustrated below, assuming an image of size  M X N (rows X columns).
    R(0,0) G(0,0) B(0,0) R(0,1) G(0,1) B(0,1) ……………………………………R(0,N-1) G(0,N-1) B(0,N-1)
    R(1,0) G(1,0) B(1,0) R(1,1) G(1,1) B(1,1) ……………………………………R(1,N-1) G(1,N-1) B(1,N-1)

    When we load the raw image, it is thrice as wide as its grayscale image would be. To convert it to grayscale we loop over the raw image and multiply the R,G,B with their respective ration in luminance formula.
    for i in range(0,rows):
        for j in range(0,columns):
            img_gray_man[i][j] = (0.3*img_color[i][j*3] +0.59*img_color[i][j*3+1] +0.11*img_color[i][j*3+2])


     2.  Plotting a histogram to get the threshold
           The histogram of an image can be used to get an idea of what the threshold value should be. The point where we see a deep valley between two peaks in the histogram can be a value of the threshold. Instead of a manual threshold, automatic thresholding can also be used to get the threshold value but during this project, I found that the automatic thresholding technique which uses peakiness to get the threshold value doesn’t perform well in all the cases. Peakiness depends on finding a valley between two peaks in the histogram of an image but if the histogram is a convex shape, i.e. it has a single peak the peakiness method doesn’t perform well.

    Fig 2: Histogram of image 1.
    In Fig 2 we see that the threshold value can be anything in the middle of these peaks.

     3.  canny edge detection to detect the edges
              In this part, we perform the Canny edge detection to detect the edges in the image. The Canny edge detection consists of 4 steps which are:
         Use gaussian smoothing to smooth the image. In this step, we apply a Gaussian filter to the image to remove noise.
         Apply the edge operator to the image. This will give back the image with thick edges.
         Quantize the gradient angle of the edges into three sectors.
         Apply non-maxima suppression to the image with thick edges to get thin edges. This is done with the help of the gradient angles found in the previous step. We take the gradient of a pixel location and see the value of the gradient along the sector of its gradient angle. If it is less than any of the two pixels along that direction we set its value to zero.

                The formula to calculate the gradient and the gradient angles are as follows:


    Fig 3: Formula to calculate gradient value and gradient angle( Source: Wiki)


    Fig 4: Value of Sectors depending on gradient angle





       g = create_guassian_mask([3,3] , 0.0 , 1)   
       img_after_gaussian_mask = (apply_map(image , g , False) / sum(sum(g)))  
       #sobels operator returns the gradient magnitude array and the gradient direction array  
       image_after_sobels_operator , theta = edge_operator(img_after_gaussian_mask , "sobels")  
       quantized_theta = quantize_gradient_angle_to_secotrs(theta)  
       image_after_non_maxima_suppression = non_maxima_suppression(image_after_sobels_operator , quantized_theta)  
       return image_after_non_maxima_suppression      
    This function is in the edgedetction.py file and all the functions it calls are also in the same file. These files get called by the houghtransform.py file. All these files can be found on my Github. It first creates a gaussian mask ‘g’ which is then applied to the image to smooth it and remove noise. Then we use the Sobels edge operator to get the gradient magnitudes and gradient angles. These gradient angles are then quantized by the function quantize_gradient_angle_to_secotrs.



    Fig 5: Image after canny edge detection








    4.  Threshold Image
           In this part we threshold the image. We use the threshold value we found above to convert the grayscale image to binary image. This is based on simple comparison, if the value of the pixel is above threshold set it to 255 and if it is below set it to 0( this will be reversed depending on if you want the background to be black and foreground to be white or vice versa). After you threshold the image you will get a binary image on which we can perform the hough transform.


    Fig 6: Image after thresholding

    5.    Apply hough transform
           In this step we apply the hough transform to get the accumulator and the positions of the pixels that correspond to incrementing a particular location in the accumulator.

    The Hough transform is a feature extraction technique used in image analysis, computer vision, and digital image processing.[1] The purpose of the technique is to find imperfect instances of objects within a certain class of shapes by a voting procedure. This voting procedure is carried out in a parameter space, from which object candidates are obtained as local maxima in a so-called accumulator space that is explicitly constructed by the algorithm for computing the Hough transform.
    The simplest case of Hough transform is detecting straight lines. In general, the straight line y = mx + b can be represented as a point (b, m) in the parameter space. However, vertical lines pose a problem. They would give rise to unbounded values of the slope parameter m.




    Fig 7: Formula for the line in theta, r

    Fig 8


    Hough Transform Algorithm (for straight lines)
    1. Quantize the parameter space appropriately
    2. Assume that each cell in the parameter space is an accumulator. Initialize all cells to zero
    3. For each point (x,y) in the image space increment by 1 each of the accumulators that satisfy the equation
    4. Maxima in the accumulator array correspond to the parameters of model instances


    Hough transform implementation:
    1. First I create the theta array depending on the step size using the following code:
         def get_theta_list(step_size):
        if 360 % step_size != 0:
            print("step_size not a factor of 180")
        no_of_steps = int(round(360/step_size))

        return np.linspace(0 , 360 ,no_of_steps )+(round(step_size/2))

    1. Then for all the points in the binary image that are foreground( 0 or 1 depending on how the foreground is represented) we increment the value of the accumulator array for all the values of theta in theta array that we got in previous step. We also store the location of the points that incremented a particular position in accumulator array in the dictionary accumulator_positions The key of this dictionary is the p or r  and the theta and the value is the list of points. The code to create the accumulator array is:

    def increment_accumulator(image , thetas , theta_step_size  = step_size_theta, p_step_size  = step_size_p):
        accumulator_positions = {}
        n,m = image.shape
        accumulator  = create_accumulator(len(thetas) , p_step_size)
        accumulator_rows , accumulator_columns = accumulator.shape
        # this line get the index of all the points that are non zero in the binary image
        y_idxs, x_idxs = np.nonzero(image)
        for z in range(len(y_idxs)):
            i = y_idxs[z]
            j = x_idxs[z]
            for theta in thetas:
                theta_position_in_accumulator = int(round((theta)/step_size_theta))-1
                result = int(round((calculatep(theta , j , i)+diag_len)/step_size_p))
                accumulator[result][theta_position_in_accumulator] += 1
                #check if the point is already at this loc in dictionary
                if((result,theta_position_in_accumulator) in accumulator_positions.keys()):
                    accumulator_positions[result,theta_position_in_accumulator].append((i,j))
                else:
                    accumulator_positions[result,theta_position_in_accumulator] = [(i,j)]

        return accumulator , accumulator_positions


    1. Once we get the accumulator array we threshold it to remove all the points that may not correspond to a straight line.
    2. After thresholding we find the points of intersection of parallel lines to get the corners of the parallelogram. To do this first we find two pair of parallel lines from the accumulator_pos dictionary and check if these parallel lines are some distance apart to prevent detecting very narrow parallelograms which may be of the same edge. Then we find two more parallel lines using the same dictionary and check if they are some distance apart. Now we have 4 lines which may form a parallelogram.
    3. To check if the 4 lines actually form a parallelogram we use the equation of these four lines and find the four intersection points. Then we check if there are enough points in between these corner points to say if they actually for a parallelogram. To check this we can find the distance between the corner points and then check if the ratio of the distance and the number points between these corner point is above some threshold.
    4. Once we have the corner points of the parallelogram we can draw it over the original image using the cv2.line function.





    Fig 6: Image if the accumulator array(X-axis: theta , Y-axis:p)



    Fig 7: Image after thresholding of accumulator array



    Image after drawing the parallelogram over it