# --- # jupyter: # jupytext: # formats: ipynb,py:percent # text_representation: # extension: .py # format_name: percent # format_version: '1.3' # jupytext_version: 1.14.0 # kernelspec: # display_name: Python 3 (ipykernel) # language: python # name: python3 # --- # %% [markdown] toc=true slideshow={"slide_type": "notes"} #

Table of Contents

#
# %% RECAP [markdown] slideshow={"slide_type": "slide"} # # Fondamenti di Programmazione # # # **Andrea Sterbini** # # lezione 14 - 21 novembre 2022 # %%% Immagini come matrici di pixel [markdown] slideshow={"slide_type": "subslide"} # # RECAP: Immagini # - creazione/load/save # - rotazione # - disegno di linee verticali/orizzontali/diagonali # - disegno di rettangoli ed ellissi # - trasformazione di colori (grigio/negativo/luninosità/contrasto) # - trasformazione tramite filtri con e senza posizione # %% CLASSI e oggetti [markdown] slideshow={"slide_type": "subslide"} # # Programmazione ad oggetti (OOP) # # - Gli oggetti sono la fusione di: # - **attributi**: i dati che caratterizzano una particolare entità
# (per esempio un cane ha un peso, un nome, un genere, una età, un colore ...) # - **metodi**: le funzionalità caratteristiche quella particolare entità
# (un cane abbaia, si muove, scodinzola, mangia, morde, si accoppia ...) # # La descrizione di una tipologia di oggetto si chiama **classe** (i Cani) # # Ciascun individuo di una certa tipologia si chiama **istanza** della **classe** # # Abbiamo usato ampiamente gli oggetti (str, int, dict, tuple, float, bool ...) e i loro metodi # %% [markdown] slideshow={"slide_type": "subslide"} # # Come definire un nuovo tipo di oggetto # ```python # class NomeDellaClasse (EstendendoLaClasse): # attributo_di_classe = valore # valori condivisi tra tutte le istanze/individui # ... # # def __init__(self, ): # metodo speciale che inizializza l'istanza # self.attributo_individuale = valore1 # definisco un valore personale di un individuo/istanza # ... # # def metodo1(self, ): # comportamenti di tutti gli individui # corpo del metodo # ... # ``` # %% [markdown] slideshow={"slide_type": "subslide"} # # Traformiamo i colori in oggetti # Per poter (ri)scrivere le trasformazioni che abbiamo fatto sui colori come espressioni semplici # # Per esempio: # - **luminosità(colore, k)** diventa **colore*k** # - **contrasto(colore,k)** diventa **(colore-grigio)*k + grigio** # # Come? # - basta ridefinire i metodi che realizzano le operazioni matematiche (**`__add__`**, **`__mul__`**, ...) # - così definiamo una **matematica dei colori** # %% [markdown] slideshow={"slide_type": "subslide"} # ## Per prima cosa definiamo la classe ed il suo costruttore # Il metodo **`__init__(self, ...)`** è speciale e serve ad **inizializzare** l'individuo/istanza # %%% Colori come oggetti slideshow={"slide_type": "notes"} init_cell=true # libreria che permette di definire una classe spezzata in più celle Jupyter import jdc # ATTENZIONE: nella realtà i metodi devono essere INDENTATI dentro la classe # %%% Colori come oggetti slideshow={"slide_type": "subslide"} code_folding=[] class Colore: # rappresentazione di un colore RGB # definisco un elenco di nomi di colori standard, ma li creiamo dopo black : 'Colore' # i tipi li metto come stringa perchè white : 'Colore' # la classe Colore non è ancora red : 'Colore' # completamente definita green : 'Colore' blue : 'Colore' cyan : 'Colore' yellow : 'Colore' purple : 'Colore' grey : 'Colore' def __init__(self, R : float, G : float, B : float): "un colore contiene i tre canali R,G,B" self._R = R self._G = G self._B = B # NOTA: tutti i metodi devono essere INDENTATI # dentro la classe A = Colore(123, 234, 12) A._R # %% [markdown] slideshow={"slide_type": "subslide"} # ## Poi (ri)definiamo somma e differenza (`__add__` e `__sub__`) # %%% Colori come oggetti slideshow={"slide_type": "fragment"} code_folding=[] # %%add_to Colore # trucco del modulo jdc per aggiungere metodi alla classe in una cella diversa def __add__(self, other : 'Colore') -> 'Colore': # C1 + C2 "somma tra due colori" # FIXME: prima controllo che l'altro sia un colore assert type(other)== Colore, "Il secondo argomento non è un Colore" return Colore(self._R+other._R, self._G+other._G, self._B+other._B) def __sub__(self, other : 'Colore') -> 'Colore': # C1 - C2 "differenza tra due colori" # FIXME: prima controllo che l'altro sia un colore return Colore(self._R-other._R, self._G-other._G, self._B-other._B) # %% [markdown] slideshow={"slide_type": "subslide"} # ## e prodotto o divisione per una costante K (`__mul__` e `__truediv__`) # %%% Colori come oggetti slideshow={"slide_type": "fragment"} code_folding=[] # %%add_to Colore def __mul__( self, k : float) -> 'Colore': # moltiplicazione*k "moltiplicazione di un colore per una costante" # FIXME: controllare che k sia float return Colore(self._R*k, self._G*k, self._B*k) def __truediv__(self, k : float) -> 'Colore': # divisione/k "divisione di un colore per una costante" # FIXME: controllare che k sia float != 0 return Colore(self._R/k, self._G/k, self._B/k) # %% [markdown] slideshow={"slide_type": "subslide"} # ## più un paio di metodi di comodo # - conversione da colore a tripla (per poi salvare i file con **images.save**) # - visualizzazione del colore come stringa (**`__repr__`** oppure **`__str__`**) # %%% Colori come oggetti slideshow={"slide_type": "fragment"} code_folding=[] # %%add_to Colore def _asTriple(self) -> tuple[int,int,int] : # C -> (R,G,B) "creo la tripla di interi tra 0 e 255 che serve per le immagini PNG" # NOTA: solo quando devo creare un file PNG mi assicuro che il pixel sia intero nel range 0..255 def bound(C): return min(255, max(0, int(round(C)))) return bound(self._R), bound(self._G), bound(self._B) def __repr__(self) -> str: # C -> "Colore(R,G,B)" "stringa che deve essere visualizzata per stampare il colore" # uso una f-stringa che mostra i 3 valori return f"Colore({self._R},{self._G},{self._B})" # %% [markdown] slideshow={"slide_type": "subslide"} # # e un paio di metodi generali # - luminosità media # - grigio # %%% Colori come oggetti slideshow={"slide_type": "fragment"} code_folding=[] # %%add_to Colore def luminosità(self) -> float: # C -> luminosità "calcolo la luminosità media di un pixel (senza badare se è un valore intero)" return (self._R + self._G + self._B)/3 def grigio(self) -> 'Colore': # C -> grigio "creo un colore grigio con la stessa luminosità" L = self.luminosità() return Colore(L,L,L) # %% [markdown] slideshow={"slide_type": "subslide"} # ## A questo punto fa comodo avere un po' di colori con nomi "umani" # %%% Colori come oggetti slideshow={"slide_type": "fragment"} code_folding=[] # solo dopo che ho definito la classe Color posso definire dei colori # posso aggiungerle degli *attributi di classe* # che contengono *istanze di Color* # ad esempio alcuni colori standard # NON INDENTATO SOTTO Colore Colore.white = Colore(255, 255, 255) Colore.black = Colore( 0, 0, 0) Colore.red = Colore(255, 0, 0) Colore.green = Colore( 0, 255, 0) Colore.blue = Colore( 0, 0, 255) Colore.yellow= Colore(255, 255, 0) Colore.purple= Colore(255, 0, 255) Colore.cyan = Colore( 0, 255, 255) Colore.grey = Colore.white / 2 # STO USANDO __truediv__ !!! Colore.grey # %% [markdown] slideshow={"slide_type": "subslide"} # ## Introduciamo alcune trasformazioni del pixel # - negativo # - luminosità # - contrasto # %%% Colori come oggetti slideshow={"slide_type": "subslide"} code_folding=[] # %%add_to Colore def negativo(self): # C -> inverso "ottengo il colore 'inverso'" return Colore.white-self def illumina(self, k : float): # C-> colore più luminoso/scuro 'ottengo il colore schiarito/scurito di un fattore k' return self*k def contrasto(self, k : float): # C -> colore più/meno contrastato "ottengo il colore allontanato/avvicinato di un fattore k dal grigio" return Colore.grey + (self - Colore.grey)*k # %% [markdown] # # Esempi # %%% Colori come oggetti slideshow={"slide_type": "subslide"} # Esempi rosso = Colore(255, 0, 0) verde = Colore( 0,255, 0) p3 = rosso + verde # uso l'operatore somma tra due colori che ho definito #print(p3) p4 = p3 * 0.5 # uso l'operatore prodotto per una costante che ho definito #print(p4) # media di 4 colori LC = [rosso, verde, p3, p4] #print(sum(LC, Colore.black)/len(LC)) C = Colore(56, 200, 31) print(C.illumina(0.8)) print(C.contrasto(1.5)) #print(C.contrasto(0.8)) Colore.contrasto(C, 0.8) # %% [markdown] slideshow={"slide_type": "slide"} # # Definiamo ora una classe Immagine # - che contiene una **lista di liste di Colore** invece che di triple RGB # - e magari anche **le proprie dimensioni** # - che sa applicare filtri semplici o filtri XY # - che posso caricare da un file # - che posso salvare su un file # - sulla quale posso disegnare (line, pixel, rectangle ...) # %% [markdown] slideshow={"slide_type": "subslide"} # ## Comincio con classe e costruttore `__init__` # Posso creare una immagine in due modi: # - leggendola da un file PNG e convertendo le triple in Color # - o fornendo dimensioni e colore di sfondo # # in entrambi i casi mi segno le dimensioni una volta per tutte # %%% Immagini come oggetti slideshow={"slide_type": "subslide"} code_folding=[] import images from math import dist from typing import Optional, Callable class Immagine: def __init__(self, larghezza : Optional[int] =None, altezza : Optional[int] =None, sfondo : Optional[Colore]=None, filename : Optional[str] =None): # FIXME controllare che i valori siano corretti if filename: # leggo la immagine e la converto in una matrice di Colore e ne ricordo le dimensioni img = images.load(filename) self._img = [ [ Colore(R,G,B) for R,G,B in riga ] for riga in img ] self._W = len(img[0]) self._H = len(img) else: # altrimenti creo una immagine monocolore e ne ricordo le dimensioni self._W = larghezza self._H = altezza self._img = [ [ sfondo for _ in range(larghezza) ] for _ in range(altezza) ] # %% [markdown] slideshow={"slide_type": "subslide"} # ## poi definisco un paio di metodi di utilità # - visualizzazione della immagine come stringa (`__repr__`) # - salvataggio su file # - conversione da Colori a triple # - visualizzazione in Spyder/Jupyter/Ipython # %%% Immagini come oggetti slideshow={"slide_type": "subslide"} code_folding=[] # %%add_to Immagine def __repr__(self): # I -> "Immagine(WxH)" "per stampare l'immagine ne mostro solo le dimensioni" return f"Immagine({self._W}x{self._H})" def save(self, filename : str) -> 'Immagine': # salvataggio "si salva l'immagine dopo averla convertita in matrice di triple" images.save(self._asTriples(), filename) return self # metodo "privato", inizia per '_' def _asTriples(self) -> list[list[tuple[int,int,int]]]: # conversione in liste di liste di triple "conversione della immagine da matrice di Colore a matrice di triple" return [ [c._asTriple() for c in riga] for riga in self._img ] def visualizza(self): # mostra l'immagine in Spyder/Jupyter "visualizzo l'immagine in Spyder/Jupyter" return images.visd(self._asTriples()) # %%% Immagini come oggetti slideshow={"slide_type": "subslide"} code_folding=[] trecime = Immagine(filename='3cime.png') trecime.visualizza() # %% [markdown] slideshow={"slide_type": "subslide"} # ## e un paio di metodi per scrivere o leggere un pixel senza sbordare # %%% Immagini come oggetti slideshow={"slide_type": "fragment"} code_folding=[] # %%add_to Immagine def set_pixel(self, x: float, y: float, color : Colore) -> 'Immagine': # cambio il pixel "cambio un pixel se è dentro l'immagine" x = int(round(x)) # float -> int y = int(round(y)) # float -> int if 0 <= x < self._W and 0 <= y <= self._H: self._img[y][x] = color return self def get_pixel(self, x: float, y: float) -> Colore : # leggo il pixel più vicino "leggo un pixel se è dentro l'immagine oppure torno il più vicino sul bordo" x = int(round(x)) # float -> int y = int(round(y)) # float -> int x = min(self._W-1, max(0, x)) y = min(self._H-1, max(0, y)) return self._img[y][x] # %% [markdown] slideshow={"slide_type": "subslide"} # ## dei metodi per disegnare linee # - orizzontale # - verticale # - inclinata (qualsiasi) # %%% Immagini come oggetti slideshow={"slide_type": "slide"} code_folding=[1, 8] # %%add_to Immagine def draw_line_H(self, x: int, y: int, x1: int, color: Colore) -> 'Immagine': # linea orizzontale "disegno una linea orizzontale" x,x1 = min(x,x1),max(x,x1) for X in range(x, x1+1): self.set_pixel(X,y, color) return self def draw_line_V(self, x: int, y: int, y1: int, color: Colore) -> 'Immagine': # linea verticale "disegno una linea verticale" y,y1 = min(y,y1),max(y,y1) for Y in range(y, y1+1): self.set_pixel(x,Y, color) return self def draw_line(self, x1: int,y1: int, x2:int,y2:int, color: Colore) -> 'Immagine': # linea qualsiasi "disegno una linea qualsiasi" dx = x1-x2 dy = y1-y2 if dx == 0: # se dx=0 mi muovo in verticale self.draw_line_V(x1,y1,y2) elif dy == 0: # se dy=0 mi muovo in orizzontale self.draw_line_H(x1,y1,x2) elif abs(dx) > abs(dy): # se dx più grande itero sulle X m = dy/dx # FIXME: direzione da x1 a x2 for X in range(x1,x2+1): Y = m*(X-x1) + y1 self.set_pixel(X,Y,color) else: # altrimenti sulle Y m = dx/dy # FIXME: direzione da y1 a y2 for Y in range(y1,y2+1): X = m*(Y-y1) + x1 self.set_pixel(X,Y,color) return self # %% [markdown] slideshow={"slide_type": "subslide"} # ## e dei modi di disegnare figure # - rettangoli vuoti o pieni # - triangoli vuoti? # - poligoni regolari? # - ellissi e cerchi # %%% Immagini come oggetti slideshow={"slide_type": "subslide"} code_folding=[1, 7, 15, 25] # %%add_to Immagine def draw_rectangle_full(self, x: int, y: int, x1: int, y1: int, color: Colore) -> 'Immagine': # rettangolo pieno "disegno un rettangolo pieno disegnando tante linee orizzontali" for Y in range(y, y1+1): self.draw_line_H(x,Y,x1, color) return self def draw_rectangle(self, x: int, y: int, x1: int, y1: int, color: Colore) -> 'Immagine': # rettangolo "disegno un rettangolo vuoto (4 linee)" self.draw_line_H(x ,y ,x1,color) self.draw_line_H(x ,y1,x1,color) self.draw_line_V(x ,y ,y1,color) self.draw_line_V(x1,y ,y1,color) return self def draw_ellipse_full(self, x1: int,y1: int, x2: int,y2: int, D: int, color: Colore ) -> 'Immagine': "una ellisse piena" for x in range(self._W): for y in range(self._H): D1 = dist((x,y),(x1,y1)) D2 = dist((x,y),(x2,y2)) if D1+D2 < D: self.set_pixel(x,y,color) return self def draw_ellipse(self, x1: int,y1: int, x2: int,y2: int, D: int, color: Colore ) -> 'Immagine': "una ellisse vuota" for x in range(self._W): for y in range(self._H): D1 = dist((x,y),(x1,y1)) D2 = dist((x,y),(x2,y2)) if D1+D2 - D < 1: self.set_pixel(x,y,color) return self def draw_circle_full(self, xc: int, yc: int, r: int, color: Colore) -> 'Immagine': "un cerchio è una ellisse con i due fuochi coincidenti e D=2*r" return self.draw_ellipse_full(xc, yc, xc, yc, 2*r, color) def draw_circle(self, xc: int, yc: int, r: int, color: Colore) -> None: "un cerchio è una ellisse con i due fuochi coincidenti e D=2*r" return self.draw_ellipse(xc, yc, xc, yc, 2*r, color) # %% [markdown] slideshow={"slide_type": "subslide"} # ## e i meccanismi per applicare un filtro # - **applica_filtro** (solo al singolo pixel) # - **applica_filtro_XY** (per filtri che devono conoscere la posizione) # %%% Immagini come oggetti slideshow={"slide_type": "fragment"} code_folding=[1] # %%add_to Immagine def applica_filtro(self, filtro : Callable[[Colore], Colore]) -> 'Immagine': "creo una nuova immagine applicando a tutti i pixel il filtro" nuova = Immagine(self._W, self._H, Colore.black) for y,riga in enumerate(self._img): for x,pixel in enumerate(riga): nuova._img[y][x] = filtro(pixel) return nuova def applica_filtro_XY(self, filtro : Callable[[int, int, 'Immagine'], Colore]) -> 'Immagine': "creo una nuova immagine applicando a tutti i pixel il filtro XY" # non c'è bisogno di passare W,H perchè sono già nella immagine nuova = Immagine(self._W, self._H, Colore.black) for y in range(self._H): for x in range(self._W): nuova._img[y][x] = filtro(x,y,self) return nuova # %%% Immagini come oggetti slideshow={"slide_type": "slide"} # ESEMPI img = Immagine(larghezza=50, altezza=100, sfondo=Colore.red) A = Immagine(filename='3cime.png') #img.visualizza() A.visualizza() A.draw_ellipse(50,50, 100, 100, 100, Colore.red) A.visualizza() A.draw_rectangle(20,60, 100, 80, Colore.green) A.visualizza() A.draw_circle(-20,60, 100, Colore.green) A.visualizza() #img.draw_circle(10,60, 10, Colore.green).visualizza() # %%%% pixellation slideshow={"slide_type": "subslide"} code_folding=[0] # per pixellare una immagine su una dimensione S # costruisco una scacchiera di passo S # ogni quadrato ha il colore di: # il pixel centrale # oppure la media dei suoi pixel def pixella(x : int,y : int, I : Immagine, S : int) -> Colore : X = x - x%S + S//2 Y = y - y%S + S//2 return I.get_pixel(X,Y) # se sbordo ci pensa da solo def pixella10(x,y,I): # potremmo definire una funziona ad hoc return pixella(x,y,I,10) # oppure usare una lambda img2.applica_filtro_XY(lambda x,y,I: pixella(x,y,I,10)).visualizza() # %% code_folding=[] from random import randint def rumore(x, y, I, k): # leggo un pixel a caso nell'intorno [-k, k] "sostituisco il pixel con un vicino" X = x + randint(-k,k) Y = y + randint(-k,k) return I.get_pixel(X,Y) img2.applica_filtro_XY(lambda x,y,I: rumore(x,y,I,5)).visualizza() # %%%% random noise slideshow={"slide_type": "subslide"} # per aggiungere rumore casuale ad una immagine # possiamo aggiungere a ciascun pixel un piccolo valore random (filtro locale) # %%%% lens slideshow={"slide_type": "subslide"} # per dare l'effetto lente # nella zona della lente # mettiamo dei pixel che stanno a distanza K volte # quella che si ha dal centro della lente # %% slideshow={"slide_type": "subslide"} # TODO: generalizzare le operazioni di disegno # Definiamo una classe FiguraGeometrica con i metodi (da specializzare) # draw(x, y, Immagine) che usa SOLO Immagine.set_pixel # area() che ne calcola l'area # ... # Definiamo le figure # Punto # Linea # Rettangolo # Triangolo # PoligonoRegolare # Quadrato # TriangoloEquilatero # Pentagono # Ellisse # Cerchio