master 1db823d47bfb cached
25 files
34.1 KB
10.6k tokens
15 symbols
1 requests
Download .txt
Repository: mohammaduzair9/Basic-Image-Processing
Branch: master
Commit: 1db823d47bfb
Files: 25
Total size: 34.1 KB

Directory structure:
gitextract_e0c2i3ac/

├── Centroid/
│   └── Centroid.py
├── Connected Component Labelling/
│   └── ccl4.py
├── Gradient/
│   └── gradient.py
├── Histogram Equalization/
│   ├── hist2.tif
│   └── hist_eq.py
├── Image Negative/
│   └── negative.py
├── Image Segmentation/
│   └── Segmentation.py
├── Local Histogram Analysis/
│   ├── Slidingwindow.py
│   └── tilingGlobal.py
├── Morphology/
│   ├── segment.py
│   └── simple.py
├── README.md
├── Sharpening/
│   └── sharpen.py
├── Skeletonization/
│   └── Skeletonization.py
├── Smoothing/
│   ├── AvergingFilter.py
│   ├── Filter.py
│   ├── gaussian.py
│   ├── inp1.tif
│   ├── inp2.tif
│   ├── inp3.tif
│   ├── median.py
│   ├── unsharp_masking.py
│   └── weightedavg.py
├── Template Matching/
│   └── TemplateMatching.py
└── XY_Cuts/
    └── XY_Cuts.py

================================================
FILE CONTENTS
================================================

================================================
FILE: Centroid/Centroid.py
================================================
import cv2

img = cv2.imread("signature.png", 0)

ret,binary = cv2.threshold(img,10,255,cv2.THRESH_BINARY)

def findBB(im):
    h, w = im.shape[0], im.shape[1] 
    left, top = w, h
    right, bottom = 0, 0
    
    for x in xrange(h):
        for y in xrange(w):
            if (im[x,y] == 0):
                right = x if x > right else right
                left = x if x < left else left
                bottom = y if y > bottom else bottom
                top = y if y < top else top
                
    return (left, right, top, bottom)

def findCentroid(im):
    h, w = im.shape[0], im.shape[1]
    cx, cy, n = 0, 0, 0
    for x in xrange(h):
        for y in xrange(w):
            if (im[x,y] == 0):
                cx += x
                cy += y
                n += 1
    cx /= n
    cy /= n
    return (cx, cy)

def divideImgIntoFour(im, cent):
    h, w = im.shape[0], im.shape[1]
    cx, cy = cent
    img1 = im[0:cx, 0:cy]
    img2 = im[0:cx, cy:w]
    img3 = im[cx:h, 0:cy]
    img4 = im[cx:h, cy:w]
    return [img1, img2, img3, img4]

def calculateTransitions(im):
    h, w = im.shape[0], im.shape[1]
    prev = im[0,0]
    n = 0
    for x in range(1, h):
        for y in range(1, w):
            curr = im[x,y]
            # check if the is black to white transition
            n = n+1 if curr == 255 and prev == 0 else n
            prev = curr
    return n

boundingBox = findBB(binary)
cropImg = binary[boundingBox[0]:boundingBox[1], boundingBox[2]:boundingBox[3]]
centroid = findCentroid(cropImg)
segments = divideImgIntoFour(cropImg, centroid)
transitions = [calculateTransitions(seg) for seg in segments]

print "Bounding Box:", boundingBox
print "Coordinates of centroid:", centroid
print "Black to white transitions (4 segments):", transitions

cv2.imshow("TopLeft", segments[0])
cv2.imwrite("TopLeft.png", segments[0])
cv2.imshow("TopRight", segments[1])
cv2.imwrite("TopRight.png", segments[1])
cv2.imshow("BottomLeft", segments[2])
cv2.imwrite("BottomLeft.png", segments[2])
cv2.imshow("BottomRight", segments[3])
cv2.imwrite("BottomRight.png", segments[3])
cv2.waitKey(0)



================================================
FILE: Connected Component Labelling/ccl4.py
================================================
import Image, ImageOps
import sys
import random
import numpy


def colourize(img):
    height, width = img.shape

    colors = []
    colors.append([])
    colors.append([])
    color = 1
    # Displaying distinct components with distinct colors
    coloured_img = Image.new("RGB", (width, height))
    coloured_data = coloured_img.load()

    for i in range(len(img)):
        for j in range(len(img[0])):
            if img[i][j] > 0:
                if img[i][j] not in colors[0]:
                    colors[0].append(img[i][j])
                    colors[1].append((random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))

                ind = colors[0].index(img[i][j])
                coloured_data[j, i] = colors[1][ind]

    return coloured_img


def binarize(img_array, threshold=130):
    for i in range(len(img_array)):
        for j in range(len(img_array[0])):
            if img_array[i][j] > threshold:
                img_array[i][j] = 0
            else:
                img_array[i][j] = 1
    return img_array


def ccl4(img_array):
    ##### first pass #####
    print "starting first pass"
    curr_label = 1;
    img_array = numpy.array(img_array)
    labels = numpy.array(img_array)

    # storing label conversions
    label_conv = []
    label_conv.append([])
    label_conv.append([])

    count = 0
    for i in range(1, len(img_array)):
        for j in range(1, len(img_array[0])):

            if img_array[i][j] > 0:
                label_x = labels[i][j - 1]
                label_y = labels[i - 1][j]

                if label_x > 0:
                    # both x and y have a label
                    if label_y > 0:

                        if not label_x == label_y:
                            labels[i][j] = min(label_x, label_y)
                            if max(label_x, label_y) not in label_conv[0]:
                                label_conv[0].append(max(label_x, label_y))
                                label_conv[1].append(min(label_x, label_y))
                            elif max(label_x, label_y) in label_conv[0]:
                                ind = label_conv[0].index(max(label_x, label_y))
                                if label_conv[1][ind] > min(label_x, label_y):
                                    l = label_conv[1][ind]
                                    label_conv[1][ind] = min(label_x, label_y)
                                    while l in label_conv[0] and count < 100:
                                        count += 1
                                        ind = label_conv[0].index(l)
                                        l = label_conv[1][ind]
                                        label_conv[1][ind] = min(label_x, label_y)

                                    label_conv[0].append(l)
                                    label_conv[1].append(min(label_x, label_y))

                        else:
                            labels[i][j] = label_y
                    # only x has a label
                    else:
                        labels[i][j] = label_x

                # only y has a label
                elif label_y > 0:
                    labels[i][j] = label_y

                # neither x nor y has a label
                else:
                    labels[i][j] = curr_label
                    curr_label += 1

                    ##### second pass #####
    print "starting second pass"
    count = 1
    for idx, val in enumerate(label_conv[0]):

        if label_conv[1][idx] in label_conv[0] and count < 100:
            count += 1
            ind = label_conv[0].index(label_conv[1][idx])
            label_conv[1][idx] = label_conv[1][ind]

    for i in range(1, len(labels)):
        for j in range(1, len(labels[0])):

            if labels[i][j] in label_conv[0]:
                ind = label_conv[0].index(labels[i][j])
                labels[i][j] = label_conv[1][ind]

    return labels


def main():
    numpy.set_printoptions(threshold=numpy.nan)
    # Open the image
    img = Image.open(sys.argv[1])

    # Threshold the image
    img = img.convert('L')
    img = ImageOps.expand(img, border=1, fill='white')
    img = numpy.array(img)
    img = binarize(img)

    """
    img = [ [0,0,0,0,0,0,0,0,0,0],
            [0,1,1,0,1,1,1,0,1,0],
            [0,1,1,0,1,0,1,0,1,0],
            [0,1,1,1,1,0,0,0,1,0],
            [0,0,0,0,0,0,0,0,1,0],
            [0,1,1,1,1,0,1,0,1,0],
            [0,0,0,0,1,0,1,0,1,0],
            [0,1,1,1,1,0,0,0,1,0],
            [0,1,1,1,1,0,1,1,1,0],
            [0,0,0,0,0,0,0,0,0,0]]
    """

    img = ccl4(img)

    # Colour the image using labels
    coloured_img = colourize(img)

    # Show the coloured image
    coloured_img.show()


if __name__ == "__main__": main()


================================================
FILE: Gradient/gradient.py
================================================
from PIL import Image
import cv2
import numpy as np

#Read Image in GrayScale
img_gray = cv2.imread('lena.jpg',0)               
h,w = img_gray.shape[:2]

grad_img = np.asarray(img_gray)

for i in range(0,h):
    for j in range(0,w-1):

        #applying gradient
	a = min(img_gray[i][j+1],img_gray[i][j])
	if a == img_gray[i][j+1] :
 		temp_arr = img_gray[i][j] - img_gray[i][j+1]
	else :
		temp_arr = img_gray[i][j+1] - img_gray[i][j]
        
        
        grad_img[i,j] = temp_arr

img = Image.fromarray(grad_img)                              
img.save("gradient.jpg")



================================================
FILE: Histogram Equalization/hist_eq.py
================================================
from PIL import Image
import matplotlib.pyplot as plt
import cv2
import numpy as np

img = cv2.imread('hist2.tif',0)

#Initialize intensity values with 256 zeroes
intensity_count = [0] * 256         

height,width = img.shape[:2]        
N = height * width                  

	#Array for new_image
high_contrast = np.zeros(img.shape) 

for i in range(0,height):
    		for j in range(0,width):
        			intensity_count[img[i][j]] += 1     #Find pixels count for each intensity

L = 256

intensity_count,total_values_used = np.histogram(img.flatten(),L,[0,L])      
pdf_list = np.ceil(intensity_count*(L-1)/img.size)                    #Calculate PDF
cdf_list = pdf_list.cumsum()                                            	#Calculate CDF


for y in range(0, height):
    		for x in range(0, width): 
			#Apply the new intensities in our new image
        			high_contrast[y,x] = cdf_list[img[y,x]]                         

	
#PLOT THE HISTOGRAMS
cv2.imwrite('high_contrast.png', high_contrast)                         

plt.hist(img.ravel(),256,[0,256])
plt.xlabel('Intensity Values')
plt.ylabel('Pixel Count')
plt.show()

plt.hist(high_contrast.ravel(),256,[0,256])	
plt.xlabel('Intensity Values')
plt.ylabel('Pixel Count')
plt.show()


================================================
FILE: Image Negative/negative.py
================================================
from PIL import Image
import cv2
import sys
import numpy as np

S = 255

# if in rgb
if(sys.argv[2]=="rgb"):
    # open in rgb
    img = cv2.imread(sys.argv[1],cv2.IMREAD_COLOR)    
    B,G,R = cv2.split(img)
    B[:] = [S-x for x in B]     #inverting blue
    G[:] = [S-x for x in G]     #inverting green    
    R[:] = [S-x for x in R]     #inverting red

    #saving image
    my_img = cv2.merge((B, G, R)) 
    cv2.imwrite(sys.argv[1]+'_inverted.png', my_img)
    cv2.imshow(sys.argv[1]+'_inverted.png', my_img)     

#if in grayscale or binary
else:    
    # open in grayscale
    img = cv2.imread(sys.argv[1],cv2.IMREAD_GRAYSCALE) 
    my_img = np.array([S-x for x in img])
    cv2.imwrite(sys.argv[1]+'_inverted.png', my_img)
    cv2.imshow(sys.argv[1]+'_inverted.png', my_img) 
     







================================================
FILE: Image Segmentation/Segmentation.py
================================================
import cv2
import numpy as np

# Loading the image in RGB
img = cv2.imread("image.png", 0)

# Applying Gaussian blur with kernel size 7 to remove unwanted noise
blurred_image = cv2.GaussianBlur(img,(7,7),0)

# Applying Otsu's thresholding to binarize the image
retval ,binarized_image = cv2.threshold(blurred_image,40,255,cv2.THRESH_BINARY)

# Applying Closing to fill in the holes
filter = np.ones((3,3),np.uint8)
closed_image = cv2.morphologyEx(binarized_image, cv2.MORPH_CLOSE, filter)

# Using connected components to label the image
retval, markers = cv2.connectedComponents(closed_image)

# Mapping the component labels to hue val
label_hue = np.uint8(120*markers/np.max(markers))
blank_ch = 255*np.ones_like(label_hue)
labeled_image = cv2.merge([label_hue, blank_ch, blank_ch])

# changing from HSV to RGB again to show
labeled_image = cv2.cvtColor(labeled_image, cv2.COLOR_HSV2BGR)

# background label set to black
labeled_image[label_hue==0] = 0

# getting the unique colors in the image
unique_colors = np.unique(labeled_image.reshape(-1, labeled_image.shape[2]), axis=0)

print "Colors available in labeled image:"
for x in xrange(unique_colors.shape[0]):
    print str(x+1)+"=> B:"+str(unique_colors[x,0])+"    G:"+str(unique_colors[x,1])+"   R:"+str(unique_colors[x,2])+" "

print "\nSelect one of the colors and give its RGB values "

r = raw_input("B : ")
g = raw_input("G : ")
b = raw_input("R : ")

# making an output image
output_image = np.zeros_like(labeled_image)

# getting the object of user input color
for x in xrange(labeled_image.shape[0]):
    for y in xrange(labeled_image.shape[1]):
        if (labeled_image[x,y,0] == int(r) and labeled_image[x,y,1] == int(g) and labeled_image[x,y,2] == int(b)):
            output_image[x,y,0:3] = labeled_image[x,y,0:3]

# show the output image
cv2.imshow("Selected", labeled_image)
cv2.waitKey(0)


================================================
FILE: Local Histogram Analysis/Slidingwindow.py
================================================
import cv2
import numpy as np
import matplotlib.pyplot as plt
plt.figure(figsize=(12,12))

#reading image as grayscale
img = cv2.imread("mountains.jpg",0)

def slidingWindowEqualization(im, winSize):
    newImg = np.zeros((im.shape[0], im.shape[1]))
    for row in xrange(im.shape[0]-winSize+1):
        for col in xrange(im.shape[1]-winSize+1):
            newImg[row:row+winSize,col:col+winSize] = cv2.equalizeHist(im[row:row+winSize,col:col+winSize])
    return newImg

windowSize = 256
output_img = slidingWindowEqualization(img, windowSize)
plt.subplot(211)
plt.axis('off')
plt.title("Image after transformation")
plt.imshow(output_img, cmap="gray")

#writing output image
cv2.imwrite("SlidingWindow.jpg", output_img)

plt.subplot(212)
plt.hist(output_img.ravel(),256,[0,256])
plt.title("Histogram")

plt.show()



================================================
FILE: Local Histogram Analysis/tilingGlobal.py
================================================
from PIL import Image
import matplotlib.pyplot as plt
import cv2
import numpy as np

img = cv2.imread('mountains.jpg',0)
intensity_count = [0] * 256         #Initialize intensity values with 256 zeroes
L = 255.0

in1 = [0] * 256
in2 = [0] * 256
in3 = [0] * 256
in4 = [0] * 256

height, width = img.shape[:2]        #Get width and height
N = height * width * 1.0                 #Get total Pixels
half_height, half_width = height/2, width/2
half_N = (N / 2) * 1.0

im1 = np.zeros((256,256,0))
im2 = np.zeros((256,256,0))
im3 = np.zeros((256,256,0))
im4 = np.zeros((256,256,0))
high_contrast = np.zeros(img.shape)         #Array for global_equalized_new_image
high_contrast_local = np.zeros(img.shape)   #Array for local_equalized_new_image

for y in range(0, height):
    for x in range(0, width): 
        intensity_count[img[y,x]] += 1          #Increment each gray_level pixel value according to tile (tile 1, 2, 3 or 4)
        if y <= height/2 and x < width/2:
            in1[img[y,x]] += 1
        elif y <= height/2 and x >= width/2:
            in2[img[y,x]] += 1
        elif y > height/2 and x < width/2:
            in3[img[y,x]] += 1
        elif y > height/2 and x >= width/2:
            in4[img[y,x]] += 1

newList1 = [x / half_N for x in in1]
newList2 = [x / half_N for x in in2]
newList3 = [x / half_N for x in in3]
newList4 = [x / half_N for x in in4]

pdf1_array = np.asarray(newList1); cdf1_array = np.cumsum(pdf1_array)
pdf2_array = np.asarray(newList1); cdf2_array = np.cumsum(pdf2_array)
pdf3_array = np.asarray(newList1); cdf3_array = np.cumsum(pdf3_array)
pdf4_array = np.asarray(newList1); cdf4_array = np.cumsum(pdf4_array)

approx1_List = [round(x * L) for x in cdf1_array]
approx2_List = [round(x * L) for x in cdf2_array]
approx3_List = [round(x * L) for x in cdf3_array]
approx4_List = [round(x * L) for x in cdf4_array]

for y in range(0, height):
    for x in range(0, width): 
        if y <= height/2 and x < width/2:
            high_contrast_local[y,x] = approx1_List[img[y,x]]
        elif y <= height/2 and x >= width/2:
            high_contrast_local[y,x] = approx2_List[img[y,x]]
        elif y > height/2 and x < width/2:
            high_contrast_local[y,x] = approx3_List[img[y,x]]
        elif y > height/2 and x >= width/2:
            high_contrast_local[y,x] = approx4_List[img[y,x]]

cv2.imwrite('high_contrast_local.png', high_contrast_local)

newList = [x / N for x in intensity_count]

pdf_array = np.asarray(newList)
cdf_array = np.cumsum(pdf_array)

approx_List = [round(x * L) for x in cdf_array]

for y in range(0,height):
    for x in range(0,width):
        high_contrast[y,x] = approx_List[img[y,x]] 

plt.hist(high_contrast_local.ravel(),256,[0,256])
plt.xlabel('Intensity Values')
plt.ylabel('Pixel Count')
plt.savefig('high_contrast_local.png')
plt.clf()
#plt.show()


cv2.imwrite('high_contrast_global.png', high_contrast)                         #PLOT THE HISTOGRAMS
plt.hist(img.ravel(),256,[0,256])
plt.xlabel('Intensity Values')
plt.ylabel('Pixel Count')
plt.savefig('Original.png')
plt.clf()
#plt.show()
plt.hist(high_contrast.ravel(),256,[0,256])
plt.xlabel('Intensity Values')
plt.ylabel('Pixel Count')
plt.savefig('high_contrast_global.png')
plt.clf()
#plt.show()




================================================
FILE: Morphology/segment.py
================================================
import cv2
import numpy as np

img = cv2.imread('inp.jpg',0)

ret,bin = cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV)

kernel = np.ones((3,3),np.uint8)
opened = cv2.morphologyEx(bin, cv2.MORPH_OPEN, kernel)
cv2.imshow("opened", opened)

kernel = np.ones((5,5),np.uint8)
closed = cv2.morphologyEx(opened, cv2.MORPH_CLOSE, kernel)
ret, output = cv2.threshold(closed,127,255,cv2.THRESH_BINARY_INV)
cv2.imshow("segment", output)
cv2.imwrite("segment.png", output)
cv2.waitKey(0)


================================================
FILE: Morphology/simple.py
================================================
import cv2
import numpy as np

img = cv2.imread('signature.png', 0)
r, img = cv2.threshold(img, 130, 255, cv2.THRESH_BINARY_INV)
cv2.imshow("Original", img)
cv2.waitKey(0)

kernel = np.ones((5,5),np.uint8)

dilated = cv2.dilate(img,kernel,iterations = 1)
cv2.imshow("Dilation", dilated)
cv2.imwrite("dilation.png", dilated)
cv2.waitKey(0)

eroded = cv2.erode(img,kernel,iterations = 1)
cv2.imshow("Erosion", eroded)
cv2.imwrite("erosion.png", eroded)
cv2.waitKey(0)

opened = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
cv2.imshow("Opening", opened)
cv2.imwrite("opening.png", opened)
cv2.waitKey(0)

closed = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
cv2.imshow("Closing", closed)
cv2.imwrite("closing.png", closed)
cv2.waitKey(0)


================================================
FILE: README.md
================================================
# Basic Digital Image Processing Tasks
> This repository contains basic implementations of image processing algorithms in python.

## Required Libraries
*	PIL
```shell
$ pip install pillow
```
*	opencv-python
```shell
$ pip install opencv-python
```

## Algorithms

### Gradient

```shell
$ python gradient.py
```
|Original|Gradient|
|---|---|
|![Gradient-Original](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Gradient/lena.jpg)|![Gradient-Result](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Gradient/gradient.jpg)|

### Image Negative

```shell
$ python negative.py binary.jpeg binary
```
|Original|Binary Negative|
|---|---|
|![Binary-Negative-Original](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Image%20Negative/binary.jpg)|![Binary-Negative-Result](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Image%20Negative/binary_inverted.png)|

```shell
$ python negative.py lena.jpg gray
```
|Original|Grayscale Negative|
|---|---|
|![Gray-Negative-Original](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Image%20Negative/grayscale.png)|![Gray-Negative-Result](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Image%20Negative/grayscale_inverted.png)|


```shell
$ python negative.py lena.jpg rgb
```
|Original|RGB Negative|
|---|---|
|![Rgb-Negative-Original](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Image%20Negative/rgb.jpg)|![Rgb-Negative-Result](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Image%20Negative/rgb_inverted.png)|

### Image Segmentation

```shell
$ python Segmentation.py
```
|Original|Segmented|
|---|---|
|![Segmented-Original](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Image%20Segmentation/image.png)|![Segmented-Result](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Image%20Segmentation/Capture3.PNG)|

### Centroid

```shell
$ python Centroid.py
```
|Original|Centroid|
|---|---|
|![Centroid-Original](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Centroid/Signature.png)|<table><tr><td>Top Left</td><td>Top Right</td></tr><tr><td>![Centroid-TopLeft](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Centroid/TopLeft.png)</td><td>![Centroid-TopRight](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Centroid/TopRight.png)</td></tr><tr><td>Bottom Left</td><td>Bottom Right</td></tr><tr><td>![Centroid-BottomLeft](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Centroid/BottomLeft.png)</td><td>![Centroid-BottomRight](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Centroid/BottomRight.png)</td></tr></table>|

### Connected Component Labelling

```shell
$ python ccl4.py
```
|Original|CCL4 Labelled|
|---|---|
|![CCL4-Original](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Connected%20Component%20Labelling/input.png)|![CCL4-Result](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Connected%20Component%20Labelling/ccl.png)|

### Histogram Equalization

```shell
$ python hist_eq.py
```
|Original|Histogram Equalized|
|---|---|
|![Hist-eq-Original](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Histogram%20Equalization/hist2.jpg)|![Hist-eq-Result](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Histogram%20Equalization/high_contrast.png)|

### Local Histogram Analysis

|Original|Local Histogram|
|---|---|
|![Local-Hist-Original](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Local%20Histogram%20Analysis/mountains.jpg)|![Local-Hist-Result](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Local%20Histogram%20Analysis/high_contrast_local_img.png)|

### Morphology

```shell
$ python Simple.py
```
|Original|Morphology|
|---|---|
|![Morphology-Original](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Morphology/signature.png)|<table><tr><td>Erosion</td><td>Dilation</td></tr><tr><td>![Erosion](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Morphology/erosion.png)</td><td>![Dilation](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Morphology/dilation.png)</td></tr><tr><td>Opening</td><td>Closing</td></tr><tr><td>![Opening](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Morphology/opening.png)</td><td>![Closing](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Morphology/closing.png)</td></tr></table>|

### Sharpening

```shell
$ python sharpen.py
```
|Original|Sharpened|
|---|---|
|![Sharpened-Original](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Sharpening/inp1.jpg)|![Sharpened-Result](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Sharpening/sharpen.jpg)|

### Skeletonization

```shell
$ python Skeletonization.py
```
![Skeletionization](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Skeletonization/output.png)

### Smoothing

```shell
$ python AvergingFilter.py
```
|Original|Averaging Filter|
|---|---|
|![Averaging-Original](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Smoothing/inp1.jpeg)|![Averaging-Result](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Smoothing/averaging.jpg)|

```shell
$ python gaussian.py
```
|Original|Gaussian|
|---|---|
|![gaussian-Original](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Smoothing/inp1.jpeg)|![gaussian-Result](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Smoothing/gaussian.jpg)|

```shell
$ python unsharp_masking.py
```
|Original|Unsharp Masking|
|---|---|
|![Unsharp-Original](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Smoothing/inp2.jpeg)|![Unsharp-Result](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Smoothing/unsharp_masking.jpg)|

```shell
$ python median.py
```
|Original|Median|
|---|---|
|![Unsharp-Original](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Smoothing/inp3.jpeg)|![Unsharp-Result](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Smoothing/median.jpg)|


### XY Cuts

```shell
$ python XY_Cuts.py
```
|Original|XY Cuts|
|---|---|
|![XY-Original](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/XY_Cuts/XY-cuts.png)|![XY-Result](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/XY_Cuts/xycut.png)|

### Template Matching

```shell
$ python TemplateMatching.py
```
|Template|Matched in Image|
|---|---|
|![Template](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Template%20Matching/template.png)|![MatchedTemplate](https://github.com/mohammaduzair9/Basic-Digital-Image-Processing/blob/master/Template%20Matching/matchedTemplate.png)|



================================================
FILE: Sharpening/sharpen.py
================================================
import cv2
import numpy as np
from matplotlib import pyplot as plt
import copy

plt.figure(figsize=(12,12))
#reading image file
im = cv2.imread("inp1.jpg", 1)
#function for sharpening filter
def sharpenFiltering(img):
    inputImg = copy.deepcopy(img.astype(np.float))
    #converting color scale from BGR to GRAY
    inputImg = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    #initialize black image of size equal to given image
    outputImg = np.zeros(inputImg.shape)
    #padding the image with zeros
    inputImg = np.pad(inputImg, (1, 1), 'constant', constant_values=(0))
    #creating two filters for horizontal and vertical edge detection
    fh = np.array([[-1.0,-2.0,-1.0],[0.0,0.0,0.0],[1.0,2.0,1.0]])
    fy = np.array([[-1.0,0.0,1.0],[-2.0,0.0,2.0],[-1.0,0.0,1.0]])
    #looping through image pixels
    for row in range(1, inputImg.shape[0]-1):
        for col in range(1, inputImg.shape[1]-1):
            dx, dy = 0.0, 0.0
            #convolving both filters
            for x_filter in xrange(3):
                for y_filter in xrange(3):
                    dx += inputImg[row+x_filter-1][col+y_filter-1]*fh[x_filter][y_filter]
                    dy += inputImg[row+x_filter-1][col+y_filter-1]*fy[x_filter][y_filter]
            
            #magnitude of gradient (instead of just adding dx and dy. we calculate magnitude)
            pixel = np.sqrt(dx * dx + dy * dy)
            outputImg[row-1][col-1] = pixel
    #normalizing pixels
    outputImg *= 255.0/np.max(outputImg)
    return outputImg

#applying sharpen filters
output = sharpenFiltering(im)
#writing image to image file
cv2.imwrite("sharpen.jpg",output)
#converting color scale from BGR to RGB
im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
#plotting original image
plt.subplot(211)
plt.axis('off')
plt.title("Original Image")
plt.imshow(im)
#plotting transformed image
plt.subplot(212)
plt.axis('off')
plt.title("Transformed Image")
plt.imshow(output, cmap="gray")

plt.show()

================================================
FILE: Skeletonization/Skeletonization.py
================================================
import numpy as np
import cv2

img = cv2.imread('Thumb.png',0)
ret,img = cv2.threshold(img,127,255,cv2.THRESH_BINARY)

# Function for skeletonizing the image
def findSkeleton(im):
    element = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))
    out = np.zeros(im.shape,np.uint8)

    flag = 0 
    while(not flag):
        eroded = cv2.erode(im, element)
        opened = cv2.dilate(eroded, element)
        opened = cv2.subtract(im,opened)
        out = cv2.bitwise_or(out,opened)
        im = eroded.copy()
        zeros = img.size - cv2.countNonZero(im)
        flag = 1 if (zeros == img.size) else 0
    return out

output = findSkeleton(img)

kernel = np.ones((3,3),np.uint8)
output = cv2.dilate(output,kernel)
output = cv2.medianBlur(output, 5)
ret,thresh = cv2.threshold(output,127,255,cv2.THRESH_BINARY_INV)

res = np.hstack((img, thresh))
cv2.imshow("output", cv2.resize(res, dsize=None,fx=0.5, fy=0.5))
cv2.imwrite("task1_output.png", res)
cv2.waitKey(0)



================================================
FILE: Smoothing/AvergingFilter.py
================================================
import cv2
import numpy as np
from matplotlib import pyplot as plt
from Filter import applyFilter

plt.figure(figsize=(12,12))

#reading image from file
im = cv2.imread("inp1.tif", 0).astype(np.float)

size = int(raw_input("> Enter the size of averaging filter: "))
#applying filter on image
output = applyFilter(im, filterSize=size)

#writing image to image file
cv2.imwrite("averaging.jpg",output)

#plotting original image
plt.subplot(211)
plt.axis('off')
plt.title("Original Image")
plt.imshow(im, cmap="gray")

#plotting smoothed image
plt.subplot(212)
plt.axis('off')
plt.title("Smoothed Image (avg. filter"+str(size)+"x"+str(size)+")")
plt.imshow(output, cmap="gray")

plt.show()

================================================
FILE: Smoothing/Filter.py
================================================
import numpy as np
import copy

#function to apply filter on image
#call it with just filter size (averaging filter)
#call it with given filter in imFilter
def applyFilter(img, filterSize=None, imFilter=None):
    filteredImg = copy.deepcopy(img)
    #if filter is provided
    if (imFilter is not None):
        imgFilter = imFilter
        filterSize = len(imFilter)
    #if filter is not provided, create an averging filter
    else:
        imgFilter = (1.0/(filterSize*filterSize))*np.ones((filterSize,filterSize)) #creating filter
    
    paddingSize = filterSize/2
    #padding the image with zeros
    filteredImg = np.pad(filteredImg, (paddingSize, paddingSize), 'constant', constant_values=(0))
    
    for row in range(paddingSize, filteredImg.shape[0]-paddingSize):
        for col in range(paddingSize, filteredImg.shape[1]-paddingSize):
            pixel = 0.0
            #convolving the filter
            for x_filter in xrange(filterSize):
                for y_filter in xrange(filterSize):
                    pixel += filteredImg[row+x_filter-paddingSize][col+y_filter-paddingSize]*imgFilter[x_filter][y_filter]
            filteredImg[row,col] = pixel
    #removing padded pixels
    filteredImg = filteredImg[paddingSize:filteredImg.shape[0]-paddingSize, paddingSize:filteredImg.shape[1]-paddingSize]
    return filteredImg

================================================
FILE: Smoothing/gaussian.py
================================================
import cv2
import numpy as np
from matplotlib import pyplot as plt
from Filter import applyFilter

plt.figure(figsize=(12,12))

im = cv2.imread("inp1.tif", 0).astype(np.float)

#creating gaussian filter 
gaussianFilter = np.array([[1,1,2,2,2,1,1],
                           [1,2,2,4,2,2,1],
                           [2,2,4,8,4,2,2],
                           [2,4,8,16,8,4,2],
                           [2,2,4,8,4,2,2],
                           [1,2,2,4,2,2,1],
                           [1,1,2,2,2,1,1]], np.float)
gaussianFilter /= np.sum(gaussianFilter*1.0)

#applying gaussian filter
output = applyFilter(im, imFilter=gaussianFilter)
#writing image to image file
cv2.imwrite("gaussian.jpg",output)
#plotting original image
plt.subplot(211)
plt.axis('off')
plt.title("Original Image")
plt.imshow(im, cmap="gray")

#plotting smoothed image 
plt.subplot(212)
plt.axis('off')
plt.title("Smoothed Image (gaussian filter7x7 (sigma 1.4))")
plt.imshow(output, cmap="gray")
plt.show()

================================================
FILE: Smoothing/median.py
================================================
import cv2
import numpy as np
from matplotlib import pyplot as plt
import copy

plt.figure(figsize=(12,12))
#reading image from file
im = cv2.imread("inp3.tif", 0).astype(np.float)
#function for median filtering
def medianFiltering(img, filterSize):
    #making deep copy of image
    filteredImg = copy.deepcopy(img)
    #calculating padding size
    paddingSize = filterSize/2
    #padding the image
    filteredImg = np.pad(filteredImg, (paddingSize, paddingSize), 'constant', constant_values=(0))
    #loop through image pixels
    for row in range(paddingSize, filteredImg.shape[0]-paddingSize):
        for col in range(paddingSize, filteredImg.shape[1]-paddingSize):
            kernal = []
            for x_filter in xrange(filterSize):
                for y_filter in xrange(filterSize):
                    kernal.append(filteredImg[row+x_filter-paddingSize][col+y_filter-paddingSize])
            #calculating median of list
            filteredImg[row,col] = np.median(kernal)
    #removing zero padding 
    filteredImg = filteredImg[paddingSize:filteredImg.shape[0]-paddingSize, paddingSize:filteredImg.shape[1]-paddingSize]
    return filteredImg

size = int(raw_input("> Enter the size of median filter: "))
#applying meadian filtering on image
output = medianFiltering(im, filterSize=size)
#writing file to image file
cv2.imwrite("median.jpg",output)
#plotting original image
plt.subplot(211)
plt.axis('off')
plt.title("Original Image")
plt.imshow(im, cmap="gray")
#plotting transformed image
plt.subplot(212)
plt.axis('off')
plt.title("Transformed Image")
plt.imshow(output, cmap="gray")

plt.show()

================================================
FILE: Smoothing/unsharp_masking.py
================================================
import cv2
import numpy as np
from matplotlib import pyplot as plt
import copy
from Filter import applyFilter

plt.figure(figsize=(12,12))
#reading image from file
im = cv2.imread("inp2.tif", 0).astype(np.float)

#function for unsharp masking
def unsharpMasking(img):
    inputImg = copy.deepcopy(img)
    blurredImg = applyFilter(inputImg, filterSize=5)
    mask = inputImg - blurredImg
    result = inputImg + mask
    return result
#unsharp masking the image
output = unsharpMasking(im)
#writing image to image file
cv2.imwrite("unsharp_masking.jpg",output)
#plotting original image
plt.subplot(211)
plt.axis('off')
plt.title("Original Image")
plt.imshow(im, cmap="gray")
#plotting transformed image
plt.subplot(212)
plt.axis('off')
plt.title("Transformed Image")
plt.imshow(output, cmap="gray")

plt.show()

================================================
FILE: Smoothing/weightedavg.py
================================================
import cv2
import numpy as np
from matplotlib import pyplot as plt
from Filter import applyFilter

plt.figure(figsize=(12,12))

#reading image from file
im = cv2.imread("inp1.tif", 0).astype(np.float)

#creating weighted filter:
#[1 2 1],
#[2 4 2],
#[1 2 1]
weightedFilter = (1.0/16)*np.array([[1,2,1],[2,4,2],[1,2,1]], np.int32)

#applying filter on image
output = applyFilter(im, imFilter=weightedFilter)

#writing image to image file
cv2.imwrite("weightedavg.jpg",output)

#plotting original image
plt.subplot(211)
plt.axis('off')
plt.title("Original Image")
plt.imshow(im, cmap="gray")

#plotting smoothed image
plt.subplot(212)
plt.axis('off')
plt.title("Smoothed Image (weighted avg. filter3x3)")
plt.imshow(output, cmap="gray")
plt.show()

================================================
FILE: Template Matching/TemplateMatching.py
================================================
import cv2
import numpy as np

image = cv2.imread('image.png')
template = cv2.imread('template.png')
(templateHeight, templateWidth) = template.shape[:2]

matchResult = cv2.matchTemplate(image, template, cv2.TM_CCOEFF)
(_, _, minLoc, maxLoc) = cv2.minMaxLoc(matchResult)

topLeft = maxLoc
botRight = (topLeft[0] + templateWidth, topLeft[1] + templateHeight)
roi = image[topLeft[1]:botRight[1], topLeft[0]:botRight[0]]
 
mask = np.zeros(image.shape, dtype = "uint8")
image = cv2.addWeighted(image, 0.25, mask, 0.75, 0)

image[topLeft[1]:botRight[1], topLeft[0]:botRight[0]] = roi
 
cv2.imwrite("matchedTemplate.png", image)


================================================
FILE: XY_Cuts/XY_Cuts.py
================================================
import cv2
from matplotlib import pyplot as plt

def xycut(image, image_path):
    # reading image
    img = plt.imread(image_path)
    fig, ax = plt.subplots()
    ax.imshow(img)

    black_pix = []
    white_lines = []
    # detecting the white lines
    for i in range(0, bin_img.shape[0]):
        # if number of white phixels in row is greater than 750
        if (bin_img[i].sum() / 255 > 750):

            # draw horizontal lines
            ax.axhline(y=i, color='green')
        else:
            # black pixels on x-axis
            for j in range(0, bin_img.shape[1]):
                if (bin_img[i][j] == 0):
                    black_pix.append(j)

    if len(black_pix) != 0:
        # draw first & last vertical line only
        ax.axvline(x=min(black_pix), linewidth=3, color='green')
        ax.axvline(x=max(black_pix), linewidth=3, color='green')

    # remove the axis
    ax.set_axis_off()
    # saving new figure
    plt.savefig('xycut.png', bbox_inches='tight')
    # show the figure
    plt.show()


# load image as greyscale
image = cv2.imread("XY-cuts.png", 0)
# binarize image
(_, bin_img) = cv2.threshold(image, 120, 255, cv2.THRESH_BINARY)
# call xycut function
xycut(bin_img, "XY-cuts.png")
Download .txt
gitextract_e0c2i3ac/

├── Centroid/
│   └── Centroid.py
├── Connected Component Labelling/
│   └── ccl4.py
├── Gradient/
│   └── gradient.py
├── Histogram Equalization/
│   ├── hist2.tif
│   └── hist_eq.py
├── Image Negative/
│   └── negative.py
├── Image Segmentation/
│   └── Segmentation.py
├── Local Histogram Analysis/
│   ├── Slidingwindow.py
│   └── tilingGlobal.py
├── Morphology/
│   ├── segment.py
│   └── simple.py
├── README.md
├── Sharpening/
│   └── sharpen.py
├── Skeletonization/
│   └── Skeletonization.py
├── Smoothing/
│   ├── AvergingFilter.py
│   ├── Filter.py
│   ├── gaussian.py
│   ├── inp1.tif
│   ├── inp2.tif
│   ├── inp3.tif
│   ├── median.py
│   ├── unsharp_masking.py
│   └── weightedavg.py
├── Template Matching/
│   └── TemplateMatching.py
└── XY_Cuts/
    └── XY_Cuts.py
Download .txt
SYMBOL INDEX (15 symbols across 9 files)

FILE: Centroid/Centroid.py
  function findBB (line 7) | def findBB(im):
  function findCentroid (line 22) | def findCentroid(im):
  function divideImgIntoFour (line 35) | def divideImgIntoFour(im, cent):
  function calculateTransitions (line 44) | def calculateTransitions(im):

FILE: Connected Component Labelling/ccl4.py
  function colourize (line 7) | def colourize(img):
  function binarize (line 31) | def binarize(img_array, threshold=130):
  function ccl4 (line 41) | def ccl4(img_array):
  function main (line 119) | def main():

FILE: Local Histogram Analysis/Slidingwindow.py
  function slidingWindowEqualization (line 9) | def slidingWindowEqualization(im, winSize):

FILE: Sharpening/sharpen.py
  function sharpenFiltering (line 10) | def sharpenFiltering(img):

FILE: Skeletonization/Skeletonization.py
  function findSkeleton (line 8) | def findSkeleton(im):

FILE: Smoothing/Filter.py
  function applyFilter (line 7) | def applyFilter(img, filterSize=None, imFilter=None):

FILE: Smoothing/median.py
  function medianFiltering (line 10) | def medianFiltering(img, filterSize):

FILE: Smoothing/unsharp_masking.py
  function unsharpMasking (line 12) | def unsharpMasking(img):

FILE: XY_Cuts/XY_Cuts.py
  function xycut (line 4) | def xycut(image, image_path):
Condensed preview — 25 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (37K chars).
[
  {
    "path": "Centroid/Centroid.py",
    "chars": 2107,
    "preview": "import cv2\n\nimg = cv2.imread(\"signature.png\", 0)\n\nret,binary = cv2.threshold(img,10,255,cv2.THRESH_BINARY)\n\ndef findBB(i"
  },
  {
    "path": "Connected Component Labelling/ccl4.py",
    "chars": 4746,
    "preview": "import Image, ImageOps\nimport sys\nimport random\nimport numpy\n\n\ndef colourize(img):\n    height, width = img.shape\n\n    co"
  },
  {
    "path": "Gradient/gradient.py",
    "chars": 603,
    "preview": "from PIL import Image\r\nimport cv2\r\nimport numpy as np\r\n\r\n#Read Image in GrayScale\r\nimg_gray = cv2.imread('lena.jpg',0)  "
  },
  {
    "path": "Histogram Equalization/hist_eq.py",
    "chars": 1238,
    "preview": "from PIL import Image\nimport matplotlib.pyplot as plt\nimport cv2\nimport numpy as np\n\nimg = cv2.imread('hist2.tif',0)\n\n#I"
  },
  {
    "path": "Image Negative/negative.py",
    "chars": 832,
    "preview": "from PIL import Image\r\nimport cv2\r\nimport sys\r\nimport numpy as np\r\n\r\nS = 255\r\n\r\n# if in rgb\r\nif(sys.argv[2]==\"rgb\"):\r\n  "
  },
  {
    "path": "Image Segmentation/Segmentation.py",
    "chars": 1865,
    "preview": "import cv2\nimport numpy as np\n\n# Loading the image in RGB\nimg = cv2.imread(\"image.png\", 0)\n\n# Applying Gaussian blur wit"
  },
  {
    "path": "Local Histogram Analysis/Slidingwindow.py",
    "chars": 818,
    "preview": "import cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\nplt.figure(figsize=(12,12))\n\n#reading image as grayscale\ni"
  },
  {
    "path": "Local Histogram Analysis/tilingGlobal.py",
    "chars": 3238,
    "preview": "from PIL import Image\nimport matplotlib.pyplot as plt\nimport cv2\nimport numpy as np\n\nimg = cv2.imread('mountains.jpg',0)"
  },
  {
    "path": "Morphology/segment.py",
    "chars": 478,
    "preview": "import cv2\nimport numpy as np\n\nimg = cv2.imread('inp.jpg',0)\n\nret,bin = cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV)"
  },
  {
    "path": "Morphology/simple.py",
    "chars": 739,
    "preview": "import cv2\nimport numpy as np\n\nimg = cv2.imread('signature.png', 0)\nr, img = cv2.threshold(img, 130, 255, cv2.THRESH_BIN"
  },
  {
    "path": "README.md",
    "chars": 7279,
    "preview": "# Basic Digital Image Processing Tasks\n> This repository contains basic implementations of image processing algorithms i"
  },
  {
    "path": "Sharpening/sharpen.py",
    "chars": 1953,
    "preview": "import cv2\nimport numpy as np\nfrom matplotlib import pyplot as plt\nimport copy\n\nplt.figure(figsize=(12,12))\n#reading ima"
  },
  {
    "path": "Skeletonization/Skeletonization.py",
    "chars": 968,
    "preview": "import numpy as np\nimport cv2\n\nimg = cv2.imread('Thumb.png',0)\nret,img = cv2.threshold(img,127,255,cv2.THRESH_BINARY)\n\n#"
  },
  {
    "path": "Smoothing/AvergingFilter.py",
    "chars": 686,
    "preview": "import cv2\nimport numpy as np\nfrom matplotlib import pyplot as plt\nfrom Filter import applyFilter\n\nplt.figure(figsize=(1"
  },
  {
    "path": "Smoothing/Filter.py",
    "chars": 1348,
    "preview": "import numpy as np\nimport copy\n\n#function to apply filter on image\n#call it with just filter size (averaging filter)\n#ca"
  },
  {
    "path": "Smoothing/gaussian.py",
    "chars": 987,
    "preview": "import cv2\nimport numpy as np\nfrom matplotlib import pyplot as plt\nfrom Filter import applyFilter\n\nplt.figure(figsize=(1"
  },
  {
    "path": "Smoothing/median.py",
    "chars": 1618,
    "preview": "import cv2\nimport numpy as np\nfrom matplotlib import pyplot as plt\nimport copy\n\nplt.figure(figsize=(12,12))\n#reading ima"
  },
  {
    "path": "Smoothing/unsharp_masking.py",
    "chars": 810,
    "preview": "import cv2\nimport numpy as np\nfrom matplotlib import pyplot as plt\nimport copy\nfrom Filter import applyFilter\n\nplt.figur"
  },
  {
    "path": "Smoothing/weightedavg.py",
    "chars": 745,
    "preview": "import cv2\nimport numpy as np\nfrom matplotlib import pyplot as plt\nfrom Filter import applyFilter\n\nplt.figure(figsize=(1"
  },
  {
    "path": "Template Matching/TemplateMatching.py",
    "chars": 623,
    "preview": "import cv2\nimport numpy as np\n\nimage = cv2.imread('image.png')\ntemplate = cv2.imread('template.png')\n(templateHeight, te"
  },
  {
    "path": "XY_Cuts/XY_Cuts.py",
    "chars": 1223,
    "preview": "import cv2\nfrom matplotlib import pyplot as plt\n\ndef xycut(image, image_path):\n    # reading image\n    img = plt.imread("
  }
]

// ... and 4 more files (download for full content)

About this extraction

This page contains the full source code of the mohammaduzair9/Basic-Image-Processing GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 25 files (34.1 KB), approximately 10.6k tokens, and a symbol index with 15 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!