# --- # jupyter: # jupytext: # formats: ipynb,py:percent # text_representation: # extension: .py # format_name: percent # format_version: '1.3' # jupytext_version: 1.15.2 # kernelspec: # display_name: Python 3 (ipykernel) # language: python # name: python3 # --- # %% [markdown] slideshow={"slide_type": "slide"} # # Fondamenti di Programmazione # # # **Andrea Sterbini** # # lezione 12 - 2 novembre 2023 # %% [markdown] slideshow={"slide_type": "subslide"} # # RECAP: # - **query in un gruppo di files con tf-idf (term frequency - inverse document frequency)** # - files **json** # - files **yaml** # - lettura di pagine e file da web con **requests** # %% [markdown] slideshow={"slide_type": "subslide"} # # OGGI: immagini # # ![](Rgb-raster-image.svg.png) # # - sono griglie rettangolari di pixel (**pic**ture **el**ement) colorati # - ogni posizione a coordinate x,y contiene un colore # - **[RGB](https://en.wikipedia.org/wiki/RGB_color_model)** è un modo di codificare i colori # (altri [Color spaces](https://en.wikipedia.org/wiki/Color_space): HSV/L/B, CMYK) # - R: luminosità della componente rossa (red) # - G: luminosità della componente verde (green) # - B: luminosità della componente blu (blue) # # Questi valori in genere sono codificati in un byte ciascuno [0 .. 255] quindi un pixel occupa 3*8=24 bit (16M colori) # %% # %load_ext nb_mypy # %% IMMAGINI come matrici di pixel slideshow={"slide_type": "subslide"} Pixel = tuple[int,int,int] # con valori tra 0 e 255 compresi (1 byte) # definiamo qualche colore black : Pixel = 0, 0, 0 # luminosità minime white : Pixel = 255, 255, 255 # luminosità massime red : Pixel = 255, 0, 0 green : Pixel = 0, 255, 0 blue : Pixel = 0, 0, 255 cyan : Pixel = 0, 255, 255 yellow: Pixel = 255, 255, 0 purple: Pixel = 255, 0, 255 grey : Pixel = 128, 128, 128 # %% [markdown] slideshow={"slide_type": "subslide"} # ## Immagine = matrice di pixel = lista di liste di triple # - per semplicità usiamo una **lista di liste** # - la **lista esterna** è l'immagine, che contiene **righe orizzontali di pixel** # - ciascuna **riga** di pixel è una **lista di pixel** # - ciascun **pixel** è rappresentato da una terna **(R, G, B)** con valori interi tra [0..255] # - tutte le righe hanno la stessa lunghezza # - le righe nella immagine sono disposte dall'alto in basso (l'asse Y va in giù) # - ciascuna riga va da sinistra a destra (l'asse X va verso destra) # # **NOTA:** Le immagini definite così sono una scusa per farvi manipolare matrici. Per manipolare immagini davvero si usa la libreria **[Pillow](https://pillow.readthedocs.io/)** che non useremo nel corso. # %% import images # esempio di bandiera rossa bianca verde img = [ [(255,0,0), (255,0,0), (255,0,0), (255,255,255), (255,255,255), (255,255,255), (0,255,0), (0,255,0), (0,255,0) ], [(255,0,0), (255,0,0), (255,0,0), (255,255,255), (255,255,255), (255,255,255), (0,255,0), (0,255,0), (0,255,0) ], [(255,0,0), (255,0,0), (255,0,0), (255,255,255), (255,255,255), (255,255,255), (0,255,0), (0,255,0), (0,255,0) ], [(255,0,0), (255,0,0), (255,0,0), (255,255,255), (255,255,255), (255,255,255), (0,255,0), (0,255,0), (0,255,0) ], [(255,0,0), (255,0,0), (255,0,0), (255,255,255), (255,255,255), (255,255,255), (0,255,0), (0,255,0), (0,255,0) ], [(255,0,0), (255,0,0), (255,0,0), (255,255,255), (255,255,255), (255,255,255), (0,255,0), (0,255,0), (0,255,0) ], [(255,0,0), (255,0,0), (255,0,0), (255,255,255), (255,255,255), (255,255,255), (0,255,0), (0,255,0), (0,255,0) ], [(255,0,0), (255,0,0), (255,0,0), (255,255,255), (255,255,255), (255,255,255), (0,255,0), (0,255,0), (0,255,0) ], [(255,0,0), (255,0,0), (255,0,0), (255,255,255), (255,255,255), (255,255,255), (0,255,0), (0,255,0), (0,255,0) ], [(255,0,0), (255,0,0), (255,0,0), (255,255,255), (255,255,255), (255,255,255), (0,255,0), (0,255,0), (0,255,0) ], [(255,0,0), (255,0,0), (255,0,0), (255,255,255), (255,255,255), (255,255,255), (0,255,0), (0,255,0), (0,255,0) ], ] images.visd(img) # %% [markdown] # ## Come trovare un pixel # # Se **img** contiene la lista di righe # # **img[y]** è la riga **y** esima (partendo dall'alto) # # **img[y][x]** è il pixel della riga **y** che si trova a colonna **x** (da sinistra a destra) # %% [markdown] slideshow={"slide_type": "subslide"} # # Creiamo di una immagine/matrice monocolore !!! MA nel modo sbagliato !!! # %%% costruire una immagine WxH di un dato colore slideshow={"slide_type": "fragment"} Line = list[Pixel] Picture = list[Line] def crea_immagine_errata(larghezza : int, altezza : int, colore : Pixel) -> Picture : riga = [ colore ] * larghezza # creo una lista di pixel img = [ riga ] * altezza # creo una lista di righe return img # %%% costruire una immagine WxH di un dato colore slideshow={"slide_type": "fragment"} img2 : Picture = crea_immagine_errata(30, 40, blue) img2[5][7] = red # Provo a colorare un solo pixel images.visd(img2) # e trovo una colonna rossa!!! # %% [markdown] slideshow={"slide_type": "subslide"} # ## PERCHE' viene una riga verticale invece che un punto? # # L'istruzione **`riga = [ colore ] * larghezza`** costruisce una lista di RIFERIMENTI ad un **unico** colore in memoria # # Ciascuna posizione della lista/riga indica la stessa unica tripla RGB # %% editable=true slideshow={"slide_type": ""} jupyterlab-hide-cells="hidden" jupyter={"source_hidden": true} # figura di una lista di riferimenti allo stesso colore from pygraphviz import AGraph G = AGraph(rankdir='TD', directed=True) for N in range(10): if N: G.add_edge(N-1, N, size=0, color='white') G.add_edge(N, "(0,0,0)\nblack") G.add_subgraph(list(range(10)), rank="same") G.layout('dot') G # %% [markdown] slideshow={"slide_type": "subslide"} # L'istruzione **`img = [ riga ] * altezza`** costruisce una lista di RIFERIMENTI ad una unica riga in memoria # %% editable=true slideshow={"slide_type": ""} jupyter={"source_hidden": true} # figura di una lista di riferimenti alla stessa lista G = AGraph(rankdir='LR', directed=True) for N in range(5): if N: G.add_edge(N-1, N, size=0, color='white') G.add_edge(N, "[ la riga di pixel creata prima ]") G.add_subgraph(list(range(5)), rank="same") G.layout('dot') G # %% [markdown] slideshow={"slide_type": "subslide"} # Mentre **la prima istruzione va bene** perchè tutti i colori sono tuple, **immutabili** # # **La seconda NON VA BENE** perchè vogliamo che le righe siano **modificabili indipendentemente** l'una dall'altra # %% [markdown] slideshow={"slide_type": "slide"} # ## Creiamo l'immagine nel modo giusto # %%% costruire una immagine WxH di un dato colore slideshow={"slide_type": "fragment"} def crea_immagine(larghezza : int, altezza : int, colore : Pixel=black) -> Picture: "costruzione di una immagine con una list-comprehension" return [ [ colore ]*larghezza # creo una nuova riga di pixel 'colore' for _ in range(altezza) # e lo faccio 'altezza' volte indipendentemente ] def crea_imm(larghezza : int, altezza : int, colore: Pixel=black) -> Picture: "qui faccio lo stesso ma senza list-comprehension" img = [] # immagine = lista di righe inizialmente vuota for y in range(altezza): # per altezza volte riga = [] # creo una riga = lista di pixel inizialmente vuota for x in range(larghezza): # e per larghezza volte riga.append(colore) # aggiungo il pixel alla riga corrente img.append(riga) # e poi aggiungo la riga all'immagine return img # torno la lista di liste finale # %%% costruire una immagine WxH di un dato colore slideshow={"slide_type": "fragment"} img = crea_immagine(30, 40, red) img[5][7] = blue # coloro un pixel images.visd(img) # e ora va molto meglio, si colora un solo punto # %% [markdown] slideshow={"slide_type": "subslide"} # # Come caricare/salvare una immagine da/su disco # %%% caricare e salvare una immagine con la libreria images slideshow={"slide_type": "fragment"} # images.load(filename : str) -> Picture img3 = images.load('3cime.png') print(len(img3), len(img3[0])) # altezza e larghezza images.visd(img3) # visualizza la Picture in Jupyter # %%% caricare e salvare una immagine con la libreria images slideshow={"slide_type": "fragment"} img3[40][30:250] = [red]*220 # coloriamo una fila orizzontale di pixel usando le slice # ATTENZIONE: il numero di pixel deve essere quello giusto # images.save(img : Picture, filename : str) -> None images.save(img3, '3cime-2.png') images.visd(img3) # %% [markdown] slideshow={"slide_type": "subslide"} # # Disegnare un pixel a coordinate qualsiasi # **MA ... senza generare errori se le coordinate sono fuori dall'immagine** # %%% evitare di sbordare slideshow={"slide_type": "fragment"} # Opzione 1 --- controllando le posizioni def draw_pixel(img : Picture, x : int|float, y : int|float, colore : Pixel): # ricavo l'altezza e larghezza dell'immagine contando righe e colonne A,L = len(img), len(img[0]) x = int(round(x)) # voglio gestire anche coordinate float y = int(round(y)) # cambio il pixel solo se è dentro l'immagine if 0 <= x < L and 0 <= y < A: img[y][x] = colore # nella riga y e nella colonna x metto il colore # %%% evitare di sbordare slideshow={"slide_type": "fragment"} # Riprendo l'esempio draw_pixel(img3, 20, 20, red) images.visd(img3) # %% [markdown] slideshow={"slide_type": "subslide"} # # Gestione degli errori con **Try/except/finally** # # Per catturare eventuali errori e gestirli nel proprio programma si usa # ```python # try: # codice che potrebbe produrre un errore # except TipoDiErrore as e: # codice da eseguire se TipoDiErrore # except AltroTipo: # ... # finally: # codice da eseguire alla fine SEMPRE # ``` # # **NOTA:** la clausola **except:** che da sola cattura TUTTE le eccezioni **E' FORTEMENTE SCONSIGLIATA** # perchè potrebbe nascondere degli errori che non vi aspettate e che vanno gestiti diversamente # %%% evitare di sbordare slideshow={"slide_type": "subslide"} # Secondo modo: --- usando try-except per catturare l'errore di sbordamento? def draw_pixel_wrong(img : Picture, x : int, y : int, colore : Pixel): # mi preparo a catturare l'errore (try) try: img[y][x] = colore # provo a disegnare il pixel a coordinate x,y except IndexError: pass # se c'è errore di index nelle liste lo ignoro # --- BEWARE of negative indexes!!! (che non producono errori ma lavorano a ritroso nelle liste) # --- BEWARE of generic 'catch-all' except clauses!!!!! (che nascondono TROPPI errori) for i in range(-1000,0): draw_pixel_wrong(img3, i, i, green) # disegno fuori in alto a sinistra images.visd(img3) # %%% evitare di sbordare slideshow={"slide_type": "subslide"} editable=true def draw_pixel_maybe_better(img : Picture, x : int|float, y : int|float, colore : Pixel): x = int(round(x)) # voglio gestire anche coordinate float y = int(round(y)) A,L = len(img), len(img[0]) # mi preparo a catturare l'errore (try) try: # controllo le coordinate e lancio un errore se sbordo assert 0 <= x < L and 0 <= y < A , f"coordinate FUORI {x},{y}" img[y][x] = colore # disegno il pixel except AssertionError as e: # se l'asserzione era falsa ho sbordato dall'immagine print(e) # stampo l'eccezione pass # MA questo è lo stesso che usare un if!!! NON NE VALE LA PENA for i in range(-20,0): draw_pixel_maybe_better(img3, i, i, red) images.visd(img3) # %% [markdown] slideshow={"slide_type": "slide"} # # Rotazione di 90° a sinistra (antioraria) # ![](rotazione.png) # %%% ruotare una immagine di 90° in senso anti/orario slideshow={"slide_type": "subslide"} # X_destinazione = y_sorgente # Y_destinazione = larghezza_sorgente - 1 - x_sorgente def ruota_sx(img): altezza, larghezza = len(img),len(img[0]) # ottengo le dimensioni img2 = crea_immagine(altezza, larghezza) # creo una immagine nera con altezza e larghezza scambiate for y, riga in enumerate(img): # per ogni pixel della immagine originale for x, pixel in enumerate(riga): X = y # calcolo le coordinate X,Y della destinazione Y = larghezza -1 -x img2[Y][X] = pixel # e copio il pixel nella destinazione return img2 # torno l'immagine ruotata img_r = ruota_sx(img3) images.visd(img_r) # --- PER CASA: rotazione destra # %% [markdown] slideshow={"slide_type": "subslide"} # # Disegnare una linea orizzontale o verticale # %%% Disegnare una retta orizzontale/verticale slideshow={"slide_type": "fragment"} def draw_h_line(img, x, y, x2, colore): altezza = len(img) if 0 <= y < altezza: # SOLO se la y è tra 0 e altezza if x>x2 : x, x2 = x2, x # scambio i valori se non sono nell'ordine giusto for X in range(x, x2+1): # scandisco le X da x a x2 draw_pixel(img, X, y, colore) # riusiamo la draw_pixel che controlla di non sbordare # oppure prima intersechiamo la linea con l'immagine e poi la disegnamo senza controllare def draw_h_line2(img, x, y, x2, colore): altezza = len(img) if 0 <= y < altezza: # SOLO se la y è tra 0 e altezza if x>x2 : x, x2 = x2, x # scambio i valori se non sono nell'ordine giusto larghezza = len(img[0]) # la parte da disegnare ha estremi non maggiori di larghezza-1 e non minori di 0 xmin = min(max(x, 0), larghezza-1) xmax = max(min(x2, larghezza-1),0) # una volta aggiustati gli estremi la si disegna SENZA CONTROLLI! img[y][xmin:xmax+1] = [colore]*(xmax-xmin+1) # con un assegnamento a slice img = images.load('3cime.png') # %time draw_h_line( img, 300, 100, 200, red) # x>x2 # %time draw_h_line2(img, 300, 150, 200, red) # x>x2 questa è più rapida images.visd(img) # %%% Disegnare una retta orizzontale/verticale slideshow={"slide_type": "fragment"} # lo stesso per una linea verticale def draw_v_line(img, x, y, y2, colore): larghezza = len(img[0]) if 0 <= x < larghezza: # SOLO se la x è tra 0 e larghezza if y>y2 : y, y2 = y2, y # scambio i valori se non sono nell'ordine giusto for Y in range(y, y2+1): # riusiamo la draw_pixel che controlla di non sbordare draw_pixel(img, x, Y, colore) # oppure prima intersechiamo la linea con l'immagine e poi la disegnamo senza controllare def draw_v_line2(img, x, y, y2, colore): larghezza = len(img[0]) if 0 <= x < larghezza: # SOLO se la x è tra 0 e larghezza if y>y2 : y, y2 = y2, y # scambio i valori se non sono nell'ordine giusto altezza = len(img) # la parte da disegnare ha estremi non maggiori di altezza-1 e non minori di 0 ymin = min(max(y, 0), altezza-1) ymax = max(min(y2, altezza-1),0) # una volta aggiustati gli estremi la si disegna SENZA CONTROLLI! for Y in range(ymin, ymax+1): # qui è necessario fare un ciclo img[Y][x] = colore # %%% Disegnare una retta orizzontale/verticale slideshow={"slide_type": "fragment"} img = images.load('3cime.png') # %time draw_v_line( img, 50, 100, 200, red) # %time draw_v_line2(img, 30, 100, 200, red) # più rapida images.visd(img) # %% [markdown] slideshow={"slide_type": "subslide"} # # E per le diagonali? # # ![](Bresenham.png) # # Conviene scandire il cateto **più lungo** in modo che non ci siano colonne/righe senza pixel # %%% Disegnare una retta orizzontale/verticale slideshow={"slide_type": "fragment"} # --- e diagonale??? come??? # dipende dalla direzione def draw_slope(img, x1, y1, x2, y2, colore): dx = x2-x1 dy = y2-y1 # ci si sposta lungo la direzione più lunga # se |dx| > |dy| si calcola la y per ciascuna x in [x1 .. x2] if abs(dx) >= abs(dy): if x1 > x2 : x1, x2 = x2, x1 # mi assicuro che x1 y2 : y1, y2 = y2, y1 # mi assicuro che y1 int: 'per convertire le cifre in numero passo per la stringa corrisponente' return int(''.join(map(str,cifre))) # concateno e converto in intero def scomponi(numero : int) -> Sequenza: 'per scomporre un numero nelle sue cifre passo per la stringa' return list(map(int, str(numero))) # converto tutto in stringa e poi in intero un carattere per volta # Esempio calcola((4, 7, 2, 5, 1)), scomponi(123456) # %% def controlla(permutazione : Sequenza, X : int, verbose : bool=False) -> bool: 'controllo che il prodotto della permutazione per X dia le stesse cifre' N = calcola(permutazione) # ottengo il numero cifre = scomponi(N*X) # moltiplico e scompongo if verbose: print(f"{N} x {X} = {N*X}") # se voglio visualizzo il prodotto return sorted(cifre) == sorted(permutazione) # verifico che siano le stesse cifre (i set non vanno bene) # esempi presi dal quesito della Susi controlla([1, 5, 3, 8, 4, 6], 3, True), controlla([1, 5, 3, 8, 4, 6], 4, True) # %% # %%time # e finalmente controlliamo tutte le permutazioni for p in permutazioni: prodotti = [2, 3, 4, 5, 6] if all( controlla(p,X) for X in prodotti ): for X in prodotti: controlla(p,X,True) # stampo il prodotto # la soluzione è unica! # %% [markdown] # ## In realtà potremmo dedurre alcune cose e restringere l'esplorazione # Le cifre disponibili sono [1, 2, 4, 5, 7, 8] # - ABCDEF * 5 = numero che finisce per 5 o per 0 # - 0 non è presente, il risultato finisce per forza con 5 # - F = deve essere dispari (1,5,7) # - ABCDEF * 2 = numero pari (2,4,8) # - ABCDEF * 4 = numero pari (2,4,8) # - ABCDEF * 6 = numero pari (2,4,8) non ammette F=1 o F=5 che darebbero 0 oppure 6 che non sono presenti # - quindi **F=7** # - ABCDEF * 3 = numero che finisce per 1 OK # # Inoltre **A** deve essere **1** altrimenti **A*6** dà riporto # %% [markdown] # Quindi sappiamo che **F=7 e A=1** # per cui possiamo rendere la ricerca più veloce # esaminando solo le 24 permutazioni delle altre 4 cifre [4,2,8,5] # %% # %%time for p in permutations([2,4,8,5]): cifre = [1, *p, 7] if all( controlla(cifre,X) for X in [2,3,4,5,6] ): for X in prodotti: controlla(cifre,X,True)