





"""simple docstring"""






from __future__ import annotations

from collections.abc import Callable
from typing import Generic, TypeVar

A	     =					TypeVar('''T''')
A	     =					TypeVar('''U''')




class 			__lowercase     (     Generic[T, U]							):







					def __init__(  self		,     _UpperCAmelCase		,     _UpperCAmelCase       ):
									__a	:  str         =					key
									__a	:  Dict         =					val
									__a	:  DoubleLinkedListNode[T, U] | None         =					None
									__a	:  DoubleLinkedListNode[T, U] | None         =					None







					def __repr__(  self       ):
									return (
									    f"""Node: key: {self.key}, val: {self.val}, """
									    f"""has next: {bool(self.next       )}, has prev: {bool(self.prev       )}"""
									)






class 			__lowercase     (     Generic[T, U]							):







					def __init__(  self       ):
									__a	:  DoubleLinkedListNode[T, U]         =					DoubleLinkedListNode(_UpperCAmelCase		,     _UpperCAmelCase       )
									__a	:  DoubleLinkedListNode[T, U]         =					DoubleLinkedListNode(_UpperCAmelCase		,     _UpperCAmelCase       )
									__a	:  Optional[int]         =					self.rear, self.head







					def __repr__(  self       ):
									__a	:  str         =					['''DoubleLinkedList''']
									__a	:  Any         =					self.head
									while node.next is not None:
													rep.append(str(_UpperCAmelCase       )       )
													__a	:  Tuple         =					node.next
									rep.append(str(self.rear       )       )
									return ",\n    ".join(_UpperCAmelCase       )







					def 	_lowerCamelCase						(  self		,     _UpperCAmelCase       ):
									__a	:  int         =					self.rear.prev

									# All nodes other than self.head are guaranteed to have non-None previous
									assert previous is not None

									__a	:  Optional[Any]         =					node
									__a	:  int         =					previous
									__a	:  Dict         =					node
									__a	:  List[str]         =					self.rear







					def 	_lowerCamelCase						(  self		,     _UpperCAmelCase       ):

									if node.prev is None or node.next is None:
													return None

									__a	:  Any         =					node.next
									__a	:  Dict         =					node.prev
									__a	:  Optional[int]         =					None
									__a	:  str         =					None
									return node






class 			__lowercase     (     Generic[T, U]							):
					__lowerCAmelCase					=					{}







					def __init__(  self		,     _UpperCAmelCase       ):
									__a	:  DoubleLinkedList[T, U]         =					DoubleLinkedList()
									__a	:  List[str]         =					capacity
									__a	:  Any         =					0
									__a	:  int         =					0
									__a	:  List[Any]         =					0
									__a	:  dict[T, DoubleLinkedListNode[T, U]]         =					{}







					def __repr__(  self       ):
									return (
									    f"""CacheInfo(hits={self.hits}, misses={self.miss}, """
									    f"""capacity={self.capacity}, current size={self.num_keys})"""
									)







					def __contains__(  self		,     _UpperCAmelCase       ):
									return key in self.cache







					def 	_lowerCamelCase						(  self		,     _UpperCAmelCase       ):
									# Note: pythonic interface would throw KeyError rather than return None

									if key in self.cache:
													self.hits += 1
													__a	:  DoubleLinkedListNode[T, U]         =					self.cache[key]
													__a	:  List[Any]         =					self.list.remove(self.cache[key]       )
													assert node == value_node

													# node is guaranteed not None because it is in self.cache
													assert node is not None
													self.list.add(_UpperCAmelCase       )
													return node.val
									self.miss += 1
									return None







					def 	_lowerCamelCase						(  self		,     _UpperCAmelCase		,     _UpperCAmelCase       ):

									if key not in self.cache:
													if self.num_keys >= self.capacity:
																	# delete first node (oldest) when over capacity
																	__a	:  Optional[Any]         =					self.list.head.next

																	# guaranteed to have a non-None first node when num_keys > 0
																	# explain to type checker via assertions
																	assert first_node is not None
																	assert first_node.key is not None
																	assert (
																	    self.list.remove(_UpperCAmelCase       ) is not None
																	)  # node guaranteed to be in list assert node.key is not None

																	del self.cache[first_node.key]
																	self.num_keys -= 1
													__a	:  List[str]         =					DoubleLinkedListNode(_UpperCAmelCase		,     _UpperCAmelCase       )
													self.list.add(self.cache[key]       )
													self.num_keys += 1

									else:
													# bump node to the end of the list, update value
													__a	:  Any         =					self.list.remove(self.cache[key]       )
													assert node is not None  # node guaranteed to be in list
													__a	:  Tuple         =					value
													self.list.add(_UpperCAmelCase       )







					@classmethod
					def 	_lowerCamelCase						(  cls		,     _UpperCAmelCase = 128       ):

									def cache_decorator_inner(_UpperCAmelCase       ) -> Callable[..., U]:
													def cache_decorator_wrapper(*_UpperCAmelCase       ) -> U:
																	if func not in cls.decorator_function_to_instance_map:
																					__a	:  Union[str, Any]         =					LRUCache(_UpperCAmelCase       )

																	__a	:  List[Any]         =					cls.decorator_function_to_instance_map[func].get(args[0]       )
																	if result is None:
																					__a	:  Optional[int]         =					func(*_UpperCAmelCase       )
																					cls.decorator_function_to_instance_map[func].put(args[0]		,     _UpperCAmelCase       )
																	return result

													def cache_info() -> LRUCache[T, U]:
																	return cls.decorator_function_to_instance_map[func]

													setattr(_UpperCAmelCase		,     '''cache_info'''		,     _UpperCAmelCase       )  # noqa: B010

													return cache_decorator_wrapper

									return cache_decorator_inner


if __name__ == "__main__":
		import doctest

		doctest.testmod()






"""simple docstring"""






from pathlib import Path

import numpy as np
from PIL import Image

def       __A (     a_  :np.ndarray)						->						np.ndarray:
				__a    ,  __a    ,  __a	:  Union[str, Any]         =					rgb[:, :, 0], rgb[:, :, 1], rgb[:, :, 2]
				return 0.2_9_8_9 * r + 0.5_8_7_0 * g + 0.1_1_4_0 * b

def       __A (     a_  :np.ndarray)						->						np.ndarray:
				return (gray > 1_27) & (gray <= 2_55)

def       __A (     a_  :np.ndarray				,    a_  :np.ndarray)						->						np.ndarray:
				__a	:  Optional[int]         =					np.zeros_like(a_)
				__a	:  Dict         =					np.zeros(
				    (image.shape[0] + kernel.shape[0] - 1, image.shape[1] + kernel.shape[1] - 1))

				# Copy image to padded image
				__a	:  int         =					image

				# Iterate over image & apply kernel
				for x in range(image.shape[1]):
								for y in range(image.shape[0]):
												__a	:  Optional[Any]         =					(
												    kernel * image_padded[y : y + kernel.shape[0], x : x + kernel.shape[1]]
												).sum()
												__a	:  Any         =					int(summation > 0)
				return output


if __name__ == "__main__":
		# read original image
		A	     =					Path(__file__).resolve().parent / '''image_data''' / '''lena.jpg'''
		A	     =					np.array(Image.open(lena_path))
		# kernel to be applied
		A	     =					np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]])
		A	     =					dilation(gray_to_binary(rgb_to_gray(lena)), structuring_element)
		# Save the output image
		A	     =					Image.fromarray(output).convert('''RGB''')
		pil_img.save('''result_dilation.png''')