Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 138 additions & 45 deletions ciphers/baconian_cipher.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
"""
Program to encode and decode Baconian or Bacon's Cipher
Wikipedia reference : https://en.wikipedia.org/wiki/Bacon%27s_cipher
Bacon's/Baconian Cipher Encoder and Decoder

provides functions to encode and decode messages
using Bacon's/Baconian cipher with any 2 symbols, not just A & B.

Source: https://en.wikipedia.org/wiki/Bacon's_cipher
"""

encode_dict = {
ENCODE_DICT = {
"a": "AAAAA",
"b": "AAAAB",
"c": "AAABA",
Expand Down Expand Up @@ -33,57 +37,146 @@
" ": " ",
}

DECODE_DICT = {value: key for key, value in ENCODE_DICT.items()}

decode_dict = {value: key for key, value in encode_dict.items()}


def encode(word: str) -> str:
def encode(message: str, symbols: tuple[str, str] = ("A", "B")) -> str:
"""
Encodes to Baconian cipher

>>> encode("hello")
'AABBBAABAAABABAABABAABBAB'
>>> encode("hello world")
'AABBBAABAAABABAABABAABBAB BABAAABBABBAAAAABABAAAABB'
>>> encode("hello world!")
Traceback (most recent call last):
...
Exception: encode() accepts only letters of the alphabet and spaces
Encode a message using Bacon's cipher.

Parameters
----------
message : str
The message to encode (letters and spaces only).
symbols : tuple[str, str], optional
Custom symbols to replace A and B, by default ("A", "B").

Returns
-------
str
Encoded message using the chosen symbols.

Raises
------
ValueError
If the message contains invalid characters.

Examples
--------
>>> encode("abc")
'AAAAA AAAAB AAABA'
>>> encode("abc", symbols=("X", "Y"))
'XXXXX XXXXY XXYXX'
>>> encode("hi there")
'AABBB ABAAA BAABA AABBB AABAA BAAAA AABAA'
"""
encoded = ""
for letter in word.lower():
if letter.isalpha() or letter == " ":
encoded += encode_dict[letter]
a_sym, b_sym = symbols
encoded_message = ""

for char in message.lower():
if char.isalpha() or char == " ":
bacon = ENCODE_DICT[char]
encoded_message += bacon.replace("A", a_sym).replace("B", b_sym)
else:
raise Exception("encode() accepts only letters of the alphabet and spaces")
return encoded
raise ValueError("Message can only contain letters and spaces.")

return encoded_message


def decode(coded: str) -> str:
def decode(cipher: str, symbols: tuple[str, str] = ("A", "B")) -> str:
"""
Decodes from Baconian cipher

>>> decode("AABBBAABAAABABAABABAABBAB BABAAABBABBAAAAABABAAAABB")
'hello world'
>>> decode("AABBBAABAAABABAABABAABBAB")
'hello'
>>> decode("AABBBAABAAABABAABABAABBAB BABAAABBABBAAAAABABAAAABB!")
Traceback (most recent call last):
...
Exception: decode() accepts only 'A', 'B' and spaces
Decode a Bacon's cipher message.

Parameters
----------
cipher : str
The encoded message using two symbols.
symbols : tuple[str, str], optional
Symbols used in the cipher, by default ("A", "B").

Returns
-------
str
Decoded message.

Raises
------
ValueError
If the cipher contains invalid symbols or cannot be decoded.

Examples
--------
>>> decode("AAAAA AAAAB AAABA")
'abc'
>>> decode("XXXXX XXXXY XXYXX", symbols=("X","Y"))
'abc'
"""
if set(coded) - {"A", "B", " "} != set():
raise Exception("decode() accepts only 'A', 'B' and spaces")
decoded = ""
for word in coded.split():
while len(word) != 0:
decoded += decode_dict[word[:5]]
word = word[5:]
decoded += " "
return decoded.strip()
sym1, sym2 = symbols
unique_symbols = set(cipher.replace(" ", ""))
if unique_symbols - {sym1, sym2}:
raise ValueError(f"Cipher must contain only symbols {sym1} and {sym2}.")

Check failure on line 117 in ciphers/baconian_cipher.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (EM102)

ciphers/baconian_cipher.py:117:26: EM102 Exception must not use an f-string literal, assign to variable first

candidates = []
for mapping in [(sym1, sym2), (sym2, sym1)]:
s1, s2 = mapping
standard = cipher.replace(s1, "A").replace(s2, "B")
try:
decoded = ""
for word in standard.split():
while word:
chunk = word[:5]
if chunk not in DECODE_DICT:
raise ValueError
decoded += DECODE_DICT[chunk]
word = word[5:]
decoded += " "
candidates.append(decoded.strip())
except ValueError:
candidates.append(None)

if __name__ == "__main__":
from doctest import testmod
for candidate in candidates:
if candidate is not None:
return candidate

raise ValueError("No valid decoding found.")


def detect_unique_symbols(cipher: str) -> tuple[str, str]:
"""
Detects the two unique symbols used in a cipher.

Parameters
----------
cipher : str
Encoded message containing exactly two unique symbols.

testmod()
Returns
-------
tuple[str, str]
The two unique symbols found in the cipher.

Raises
------
ValueError
If cipher does not contain exactly two unique symbols.

Examples
--------
>>> detect_unique_symbols("XXXYX YXXYX")
('X', 'Y')
"""
letters_only = [char for char in set(cipher.replace(" ", "")) if char.isalpha()]
if len(letters_only) != 2:
raise ValueError("Cipher must contain exactly two unique alphabetic symbols.")
return tuple(letters_only)


if __name__ == "__main__":
# Example usage
cipher_text = (
"FEEFE EEFFF EEFEE EFFFF FEEFF EFEEE EEEFE "
"EFEEF EEEEF FEEEE EFFEF FEFEE EFFEE EEFEF EFFEF FEFEF"
)
sym1, sym2 = detect_unique_symbols(cipher_text)
decoded_message = decode(cipher_text, symbols=(sym1, sym2))
print(decoded_message) # Expected: 'the quick brown fox'
131 changes: 131 additions & 0 deletions computer_vision/hsv_threshold
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""
HSV Filter Tool

This file lets the user to interactively select an HSV color range
from an image using trackbars and outputs the selected HSV range.

1. Set `IMAGE_PATH` to your image file.
2. Run the script.
3. Adjust the trackbars to select the desired HSV range.
4. Enter to print the HSV lower and upper bounds.
"""

import cv2
import numpy as np

#Example Path
IMAGE_PATH = r"C:\Users\username\New folder\your_image.png"


def load_image(path):
"""
Load an image from the specified path.

Parameters
----------
path : str
Path to the image file.

Returns
-------
np.ndarray
Loaded BGR image.

Raises
------
FileNotFoundError
If the image cannot be loaded.
"""
image = cv2.imread(path)
if image is None:
raise FileNotFoundError(f"Failed to load image at path: {path}")
return image


def create_hsv_trackbars(window_name):
"""
Create HSV trackbars for filtering.

Parameters
----------
window_name : str
Name of the OpenCV window.
"""
cv2.createTrackbar("H Lower", window_name, 0, 179, lambda x: None)
cv2.createTrackbar("S Lower", window_name, 0, 255, lambda x: None)
cv2.createTrackbar("V Lower", window_name, 0, 255, lambda x: None)
cv2.createTrackbar("H Upper", window_name, 179, 179, lambda x: None)
cv2.createTrackbar("S Upper", window_name, 255, 255, lambda x: None)
cv2.createTrackbar("V Upper", window_name, 255, 255, lambda x: None)


def get_hsv_bounds(window_name):
"""
Read the HSV lower and upper bounds from the trackbars.

Parameters
----------
window_name : str
Name of the OpenCV window.

Returns
-------
tuple[np.ndarray, np.ndarray]
Lower and upper HSV bounds as NumPy arrays.
"""
lower_bound = np.array([
cv2.getTrackbarPos("H Lower", window_name),
cv2.getTrackbarPos("S Lower", window_name),
cv2.getTrackbarPos("V Lower", window_name),
])
upper_bound = np.array([
cv2.getTrackbarPos("H Upper", window_name),
cv2.getTrackbarPos("S Upper", window_name),
cv2.getTrackbarPos("V Upper", window_name),
])
return lower_bound, upper_bound


def main():
"""
Main function to run the HSV filter tool.

Opens the image, creates trackbars, shows filtered results,
and prints the final HSV bounds when Enter is pressed.
"""
try:
image = load_image(IMAGE_PATH)
except FileNotFoundError as e:
print(e)
return

window_name = "HSV Filter"
cv2.namedWindow(window_name)
create_hsv_trackbars(window_name)

while True:
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

lower, upper = get_hsv_bounds(window_name)

mask = cv2.inRange(hsv_image, lower, upper)
filtered_result = cv2.bitwise_and(image, image, mask=mask)

cv2.imshow("Original", image)
cv2.imshow("Filtered", filtered_result)

key = cv2.waitKey(1) & 0xFF
if key == 13: # Enter key
print(f"HSV Range (Lower): {lower}")
print(f"HSV Range (Upper): {upper}")
print(
f"CSV Format: {lower[0]},{lower[1]},{lower[2]},"
f"{upper[0]},{upper[1]},{upper[2]}"
)
break

cv2.destroyAllWindows()


if __name__ == "__main__":
main()
6 changes: 4 additions & 2 deletions geometry/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,11 @@ def area(self) -> float:
def perimeter(self) -> float:
"""
>>> Ellipse(5, 10).perimeter
47.12388980384689
48.44222344723793
"""
return math.pi * (self.major_radius + self.minor_radius)
a, b = self.major_radius, self.minor_radius
# uses ramanujans approximation
return math.pi * (3 * (a + b) - ((3 * a + b) * (a + 3 * b)) ** 0.5)


class Circle(Ellipse):
Expand Down
2 changes: 1 addition & 1 deletion sorts/bubble_sort.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def bubble_sort_recursive(collection: list[Any]) -> list[Any]:
Examples:
>>> bubble_sort_recursive([0, 5, 2, 3, 2])
[0, 2, 2, 3, 5]
>>> bubble_sort_iterative([])
>>> bubble_sort_recursive([])
[]
>>> bubble_sort_recursive([-2, -45, -5])
[-45, -5, -2]
Expand Down
Loading