From 32083386491004959839196d8f1b5f68d061b387 Mon Sep 17 00:00:00 2001 From: gabrielfruet Date: Mon, 3 Feb 2025 11:11:56 -0300 Subject: [PATCH 01/29] feat: pytest adicionado --- Makefile | 4 ++ pytest.ini | 2 + requirements.txt | 1 + tests/segmentacao/test_classificacao.py | 55 +++++++++++++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 Makefile create mode 100644 pytest.ini create mode 100644 tests/segmentacao/test_classificacao.py diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..93570a9 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +.PHONY: test +test: + pytest ./tests --rootdir=. + diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..a635c5c --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +pythonpath = . diff --git a/requirements.txt b/requirements.txt index 518c822..ac9d7e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ scipy==1.15.1 numba==0.61.0 pydicom==3.0.1 ruff==0.9.4 +pytest==8.3.4 diff --git a/tests/segmentacao/test_classificacao.py b/tests/segmentacao/test_classificacao.py new file mode 100644 index 0000000..9bb945f --- /dev/null +++ b/tests/segmentacao/test_classificacao.py @@ -0,0 +1,55 @@ +import pytest +import numpy as np +from segmentacao.classificacao import ( + calcula_ocorrencias_classes, + probabilidade_classes, +) + + +@pytest.fixture +def amostra_imagem(): + return np.array( + [[50, 700, -600], [-200, 1500, -900], [0, -750, 300]], dtype=np.int16 + ) + + +@pytest.fixture +def ocorrencias_amostra(): + return np.array( + [ + [[0, 0, 0], [0, 0, 0], [0, 0, 0]], # Classe 0 + [[0, 0, 1], [0, 0, 0], [0, 1, 0]], # Classe 1 + [[0, 0, 0], [1, 0, 0], [0, 0, 0]], # Classe 2 + [[1, 0, 0], [0, 0, 0], [1, 0, 1]], # Classe 3 + [[0, 1, 0], [0, 1, 0], [0, 0, 0]], # Classe 4 + ], + dtype=np.uint8, + ) + + +def teste_calcula_ocorrencias_classes(amostra_imagem): + ocorrencias = calcula_ocorrencias_classes(amostra_imagem) + assert ocorrencias.shape == (5, 3, 3), "A forma da saída está incorreta." + assert ocorrencias.dtype == np.uint8, "O tipo de dados da saída está incorreto." + assert np.all(ocorrencias >= 0), "Ocorrências não podem ser negativas." + + +def teste_probabilidade_classes(ocorrencias_amostra): + probabilidades = probabilidade_classes(ocorrencias_amostra) + assert probabilidades.shape == (5, 3, 3), "A forma da saída está incorreta." + assert probabilidades.dtype == np.float32, ( + "O tipo de dados da saída está incorreto." + ) + + soma_probabilidades = probabilidades.sum(axis=0) + np.testing.assert_allclose( + soma_probabilidades, + 1.0, + atol=1e-6, + err_msg="A soma das probabilidades por pixel deve ser 1.", + ) + + zero_mask = ocorrencias_amostra.sum(axis=0) == 0 + assert np.all(probabilidades[:, zero_mask] == 0.2), ( + "Para pixels sem ocorrências, a probabilidade deve ser 0.2 (1/5)." + ) From d9463fefffdc1bde2695d8f7db2227bd1803fc12 Mon Sep 17 00:00:00 2001 From: gabrielfruet Date: Wed, 5 Feb 2025 13:11:31 -0300 Subject: [PATCH 02/29] =?UTF-8?q?feat:=20inicializa=C3=A7=C3=A3o=20da=20cu?= =?UTF-8?q?rva?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- segmentacao/curva.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 segmentacao/curva.py diff --git a/segmentacao/curva.py b/segmentacao/curva.py new file mode 100644 index 0000000..9144af0 --- /dev/null +++ b/segmentacao/curva.py @@ -0,0 +1,27 @@ +import numpy as np + + +def inicializa_curva( + ponto: np.ndarray, + raio: int = 30, + quantidade_pixels: int = 30, +) -> np.ndarray: + """ + Recebe o ponto inicial do eixo x e y, calcula a curva e retorna a duas + listas de pontos que representam a curva. + + Args: + ponto: (x,y) que representa o centro da curva + raio: raio da curva + quantidade_pixels: quantidade de pontos que a curva terá + Return: + curva: lista de pontos que representam a curva + """ + curva = [] + angulos = np.linspace(0, 2 * np.pi, quantidade_pixels) + for ang in angulos: + novo_ponto = ponto + np.r_[raio * np.cos(ang), raio * np.sin(ang)] + + curva.append(novo_ponto) + + return np.array(curva).astype(np.int16) From 87b32159d8b5525a9751daadc23aa8d14ad4a201 Mon Sep 17 00:00:00 2001 From: gabrielfruet Date: Wed, 5 Feb 2025 13:16:58 -0300 Subject: [PATCH 03/29] feat: vetorizacao na funcao de inicializacao de curva --- segmentacao/curva.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/segmentacao/curva.py b/segmentacao/curva.py index 9144af0..432c782 100644 --- a/segmentacao/curva.py +++ b/segmentacao/curva.py @@ -17,11 +17,7 @@ def inicializa_curva( Return: curva: lista de pontos que representam a curva """ - curva = [] angulos = np.linspace(0, 2 * np.pi, quantidade_pixels) - for ang in angulos: - novo_ponto = ponto + np.r_[raio * np.cos(ang), raio * np.sin(ang)] + curva = ponto + np.c_[np.cos(angulos), np.sin(angulos)] * raio - curva.append(novo_ponto) - - return np.array(curva).astype(np.int16) + return curva.astype(np.int16) From fb15580f98db81d23138f16f4e9c0100f20b1c48 Mon Sep 17 00:00:00 2001 From: gabrielfruet Date: Wed, 5 Feb 2025 13:30:16 -0300 Subject: [PATCH 04/29] fix: os pontos da curva devem ser sempre maiores que 0 --- segmentacao/curva.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/segmentacao/curva.py b/segmentacao/curva.py index 432c782..e98d1b8 100644 --- a/segmentacao/curva.py +++ b/segmentacao/curva.py @@ -16,8 +16,12 @@ def inicializa_curva( quantidade_pixels: quantidade de pontos que a curva terá Return: curva: lista de pontos que representam a curva + Raises: + AssertionError: caso curva seja menor que 0 em algum ponto """ angulos = np.linspace(0, 2 * np.pi, quantidade_pixels) curva = ponto + np.c_[np.cos(angulos), np.sin(angulos)] * raio + assert np.all(curva > 0), "Os pontos da curva devem sempre ser maior que 0" + return curva.astype(np.int16) From b9cb61ddfa39d41526093b4fc1ce97d483ea5075 Mon Sep 17 00:00:00 2001 From: gabrielfruet Date: Wed, 5 Feb 2025 13:46:54 -0300 Subject: [PATCH 05/29] fix: quantidade de pixels deve ser maior que 2 --- segmentacao/curva.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/segmentacao/curva.py b/segmentacao/curva.py index e98d1b8..7d3f618 100644 --- a/segmentacao/curva.py +++ b/segmentacao/curva.py @@ -18,7 +18,10 @@ def inicializa_curva( curva: lista de pontos que representam a curva Raises: AssertionError: caso curva seja menor que 0 em algum ponto + AssertionError: caso a quantidade_pixels seja menor que 2 """ + assert quantidade_pixels >= 2, "Quantidade de pixels deve ser sempre maior que 2" + angulos = np.linspace(0, 2 * np.pi, quantidade_pixels) curva = ponto + np.c_[np.cos(angulos), np.sin(angulos)] * raio From 2738fb16c436d2169fe90ca3a71f390d5193e664 Mon Sep 17 00:00:00 2001 From: gabrielfruet Date: Wed, 5 Feb 2025 13:47:03 -0300 Subject: [PATCH 06/29] feat: testes para inicializa_curva --- tests/segmentacao/test_curva.py | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/segmentacao/test_curva.py diff --git a/tests/segmentacao/test_curva.py b/tests/segmentacao/test_curva.py new file mode 100644 index 0000000..01cd1b3 --- /dev/null +++ b/tests/segmentacao/test_curva.py @@ -0,0 +1,48 @@ +import pytest +import numpy as np +from numpy.testing import assert_array_equal +from segmentacao.curva import inicializa_curva + + +def test_inicializa_curva_padrao(): + ponto = np.array([50, 50]) + curva = inicializa_curva(ponto) + assert curva.shape == (30, 2) + assert np.all(curva > 0) + + +def test_inicializa_curva_pontos_deslocados(): + ponto = np.array([100, 100]) + curva = inicializa_curva(ponto, raio=20, quantidade_pixels=50) + assert curva.shape == (50, 2) + assert np.all(curva > 0) + + +def test_inicializa_curva_pontos_negativos(): + ponto = np.array([5, 5]) + with pytest.raises( + AssertionError, match="Os pontos da curva devem sempre ser maior que 0" + ): + inicializa_curva(ponto, raio=30) + + +def test_inicializa_curva_qtd_pontos_negativo(): + ponto = np.array([5, 5]) + with pytest.raises( + AssertionError, match="Quantidade de pixels deve ser sempre maior que 2" + ): + inicializa_curva(ponto, raio=30, quantidade_pixels=-3) + + +def test_inicializa_curva_quantidade_minima_pixels(): + ponto = np.array([50, 50]) + curva = inicializa_curva(ponto, quantidade_pixels=2) + assert curva.shape == (2, 2) + assert np.all(curva > 0) + + +def test_inicializa_curva_raio_zero(): + ponto = np.array([30, 30]) + curva = inicializa_curva(ponto, raio=0) + expected_output = np.tile(ponto, (30, 1)) + assert_array_equal(curva, expected_output) From 9ba0bf945fe814ba5a8a947645ec2a8c62d4539c Mon Sep 17 00:00:00 2001 From: Paulo Ricardo Date: Wed, 5 Feb 2025 14:01:11 -0300 Subject: [PATCH 07/29] chore: testando a branch #6 --- curva.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 curva.py diff --git a/curva.py b/curva.py new file mode 100644 index 0000000..34f4d4d --- /dev/null +++ b/curva.py @@ -0,0 +1,5 @@ +import numpy as np + +def crisp_inicial(imagem: np.ndarray): + ... + From 59ca8c235f6fb25e18ed4b0c6141b33daf0aaeda Mon Sep 17 00:00:00 2001 From: Paulo Ricardo Date: Wed, 5 Feb 2025 17:57:31 -0300 Subject: [PATCH 08/29] feat: adiciona crisp inicial #6 --- curva.py | 5 ----- segmentacao/curva.py | 50 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) delete mode 100644 curva.py create mode 100644 segmentacao/curva.py diff --git a/curva.py b/curva.py deleted file mode 100644 index 34f4d4d..0000000 --- a/curva.py +++ /dev/null @@ -1,5 +0,0 @@ -import numpy as np - -def crisp_inicial(imagem: np.ndarray): - ... - diff --git a/segmentacao/curva.py b/segmentacao/curva.py new file mode 100644 index 0000000..133ee8a --- /dev/null +++ b/segmentacao/curva.py @@ -0,0 +1,50 @@ +import numpy as np + +def crisp_inicial( + imagem: np.ndarray, + lim_infY: int, + lim_supY: int, + lim_infX: int, + lim_supX: int +) -> np.ndarray: + """ + Recebe uma imagem em formato de array NumPy e recorta a região definida pelos + limites superiores e inferiores (tanto em X quanto em Y). A função identifica + a posição onde há maior concentração de pixels dentro da faixa de intensidade + [-1000, -500] e retorna as coordenadas correspondentes. + + Args: + imagem (np.ndarray): Matriz da imagem de entrada. + lim_infY (int): Limite inferior do recorte na direção vertical (eixo Y). + lim_supY (int): Limite superior do recorte na direção vertical (eixo Y). + lim_infX (int): Limite inferior do recorte na direção horizontal (eixo X). + lim_supX (int): Limite superior do recorte na direção horizontal (eixo X). + + Returns: + np.ndarray: Coordenadas (x, y) do ponto com maior concentração de pixels + dentro da faixa de interesse. + + Raises: + ValueError: Se os limites fornecidos forem inválidos, ou seja, se + lim_supY < lim_infY ou lim_supX < lim_infX. + + Example: + >>> from carregar import carregar_imagem + >>> imagem = carregar_imagem("../data/pulmao2/60.dcm") + >>> centro_Esq = crisp_inicial(imagem=imagem, lim_infY=180, lim_supY=360, lim_infX=0, lim_supX=255) + array([172, 266]) + >>> centro_dir = crisp_inicial(imagem=imagem, lim_infY=180, lim_supY=360, lim_infX=256, lim_supX=512) + array([335, 289]) + """ + if lim_supY < lim_infY or lim_supX < lim_infX: + raise ValueError("Os limites fornecidos para X e Y são inválidos.") + + #Recorta a Imagem nos Limites Fornecidos + imagem = imagem[lim_infY:lim_supY+1, lim_infX:lim_supX+1] + P = np.where((imagem > -1000) & (imagem < -500), 1, 0) + + # Soma os pixels ao longo dos eixos + X = np.sum(P, axis=1) # Soma na direção das colunas + Y = np.sum(P, axis=0) # Soma na direção das linhas + + return np.array([np.argmax(Y) + lim_infX, np.argmax(X) + lim_infY]) From 273eb3df9173942bc1fb572872d31d7ab18c87be Mon Sep 17 00:00:00 2001 From: Paulo Ricardo Date: Wed, 5 Feb 2025 18:01:49 -0300 Subject: [PATCH 09/29] feat: formatacao com ruff #6 --- segmentacao/curva.py | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/segmentacao/curva.py b/segmentacao/curva.py index 133ee8a..aa20389 100644 --- a/segmentacao/curva.py +++ b/segmentacao/curva.py @@ -1,16 +1,13 @@ import numpy as np + def crisp_inicial( - imagem: np.ndarray, - lim_infY: int, - lim_supY: int, - lim_infX: int, - lim_supX: int + imagem: np.ndarray, lim_infY: int, lim_supY: int, lim_infX: int, lim_supX: int ) -> np.ndarray: """ - Recebe uma imagem em formato de array NumPy e recorta a região definida pelos - limites superiores e inferiores (tanto em X quanto em Y). A função identifica - a posição onde há maior concentração de pixels dentro da faixa de intensidade + Recebe uma imagem em formato de array NumPy e recorta a região definida pelos + limites superiores e inferiores (tanto em X quanto em Y). A função identifica + a posição onde há maior concentração de pixels dentro da faixa de intensidade [-1000, -500] e retorna as coordenadas correspondentes. Args: @@ -21,30 +18,38 @@ def crisp_inicial( lim_supX (int): Limite superior do recorte na direção horizontal (eixo X). Returns: - np.ndarray: Coordenadas (x, y) do ponto com maior concentração de pixels + np.ndarray: Coordenadas (x, y) do ponto com maior concentração de pixels dentro da faixa de interesse. Raises: - ValueError: Se os limites fornecidos forem inválidos, ou seja, se + ValueError: Se os limites fornecidos forem inválidos, ou seja, se lim_supY < lim_infY ou lim_supX < lim_infX. Example: >>> from carregar import carregar_imagem >>> imagem = carregar_imagem("../data/pulmao2/60.dcm") - >>> centro_Esq = crisp_inicial(imagem=imagem, lim_infY=180, lim_supY=360, lim_infX=0, lim_supX=255) + >>> centro_Esq = crisp_inicial( + ... imagem=imagem, lim_infY=180, lim_supY=360, lim_infX=0, lim_supX=255 + ... ) array([172, 266]) - >>> centro_dir = crisp_inicial(imagem=imagem, lim_infY=180, lim_supY=360, lim_infX=256, lim_supX=512) + >>> centro_dir = crisp_inicial( + ... imagem=imagem, + ... lim_infY=180, + ... lim_supY=360, + ... lim_infX=256, + ... lim_supX=512, + ... ) array([335, 289]) """ if lim_supY < lim_infY or lim_supX < lim_infX: raise ValueError("Os limites fornecidos para X e Y são inválidos.") - - #Recorta a Imagem nos Limites Fornecidos - imagem = imagem[lim_infY:lim_supY+1, lim_infX:lim_supX+1] + + # Recorta a Imagem nos Limites Fornecidos + imagem = imagem[lim_infY : lim_supY + 1, lim_infX : lim_supX + 1] P = np.where((imagem > -1000) & (imagem < -500), 1, 0) - + # Soma os pixels ao longo dos eixos X = np.sum(P, axis=1) # Soma na direção das colunas Y = np.sum(P, axis=0) # Soma na direção das linhas - + return np.array([np.argmax(Y) + lim_infX, np.argmax(X) + lim_infY]) From f3655d562bfb0299db96f0f5876e5dcddff150ec Mon Sep 17 00:00:00 2001 From: Mateus Date: Wed, 5 Feb 2025 19:55:35 -0300 Subject: [PATCH 10/29] =?UTF-8?q?feat#3.3/Implementada=20a=20l=C3=B3gica?= =?UTF-8?q?=20sem=20a=20fun=C3=A7=C3=A3o=20do=20Sobel=20e=20ambiente=20de?= =?UTF-8?q?=20testes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- segmentacao/energia.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 segmentacao/energia.py diff --git a/segmentacao/energia.py b/segmentacao/energia.py new file mode 100644 index 0000000..04f751c --- /dev/null +++ b/segmentacao/energia.py @@ -0,0 +1,24 @@ +import numpy as np +from carregar import carregar_imagem +from classificacao import calcula_ocorrencias_classes + +def energia_externa(imagem: np.ndarray, probabilidade: np.ndarray, probablidade3: float = 0.2, probablidade4: float = 0.15) -> np.ndarray: + energia = np.zeros(imagem.shape) + for (x,y), value in np.ndenumerate(energia): + if(not (probabilidade[2][x][y] float: + return 1 + +if "__main__": + img = carregar_imagem('data/pulmao2/30.dcm') + print(type(img)) + func = calcula_ocorrencias_classes(img) + print(type(func)) + print(type(np.zeros(img.shape))) + print(func.shape) + print(energia_externa(img,calcula_ocorrencias_classes(img))) From a0db6c4e7537e9a5dfdf78f583d951ffde2c9666 Mon Sep 17 00:00:00 2001 From: Mateus Date: Wed, 5 Feb 2025 20:05:48 -0300 Subject: [PATCH 11/29] feat#3.3/Formatado ruff e documentado --- segmentacao/energia.py | 43 +++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/segmentacao/energia.py b/segmentacao/energia.py index 04f751c..872daaa 100644 --- a/segmentacao/energia.py +++ b/segmentacao/energia.py @@ -2,23 +2,48 @@ from carregar import carregar_imagem from classificacao import calcula_ocorrencias_classes -def energia_externa(imagem: np.ndarray, probabilidade: np.ndarray, probablidade3: float = 0.2, probablidade4: float = 0.15) -> np.ndarray: + +def energia_externa( + imagem: np.ndarray, + probabilidade: np.ndarray, + probablidade3: float = 0.2, + probablidade4: float = 0.15, +) -> np.ndarray: + """ + Recebe a imagem e as probabilidades de ocorrência de classe da imagem e retorna + a matriz com energia externa crisp de cada ponto imagem. Pode receber os limiares + das probabilidade P(3) e P(4) para experimentação posterior. + + Args: + imagem: Imagem para calcular a energia. + probabilidade: Probabilidade de ocorrência de classe de todos os pontos. + probablidade3: Limiar da probabilidade 3 para definir o valor da energia crispy + como 0. + probablidade4: Limiar da probabilidade 4 para definir o valor da energia crispy + como 0. + Return: + energia: Matriz com as energias crispy de todos os pontos da imagem. + """ energia = np.zeros(imagem.shape) - for (x,y), value in np.ndenumerate(energia): - if(not (probabilidade[2][x][y] float: - return 1 +def S(imagem: np.ndarray, x: int, y: int) -> float: + return 1 + if "__main__": - img = carregar_imagem('data/pulmao2/30.dcm') + img = carregar_imagem("data/pulmao2/30.dcm") print(type(img)) func = calcula_ocorrencias_classes(img) print(type(func)) print(type(np.zeros(img.shape))) print(func.shape) - print(energia_externa(img,calcula_ocorrencias_classes(img))) + print(energia_externa(img, calcula_ocorrencias_classes(img))) From 82a700bc01dd91c17380e8dc92644eca478daf99 Mon Sep 17 00:00:00 2001 From: Mateus Date: Wed, 5 Feb 2025 20:14:05 -0300 Subject: [PATCH 12/29] feat#3.4/ Adicionado operador Sobel --- segmentacao/energia.py | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/segmentacao/energia.py b/segmentacao/energia.py index 872daaa..4b89c05 100644 --- a/segmentacao/energia.py +++ b/segmentacao/energia.py @@ -1,6 +1,7 @@ import numpy as np from carregar import carregar_imagem from classificacao import calcula_ocorrencias_classes +import cv2 def energia_externa( @@ -15,14 +16,17 @@ def energia_externa( das probabilidade P(3) e P(4) para experimentação posterior. Args: - imagem: Imagem para calcular a energia. - probabilidade: Probabilidade de ocorrência de classe de todos os pontos. - probablidade3: Limiar da probabilidade 3 para definir o valor da energia crispy + imagem (np.ndarray): Imagem para calcular a energia. + probabilidade (np.ndarray): Probabilidade de ocorrência de classe de todos os + pontos. + probablidade3 (int): Limiar da probabilidade 3 para definir o valor da energia + crispy como 0. - probablidade4: Limiar da probabilidade 4 para definir o valor da energia crispy + probablidade4 (int): Limiar da probabilidade 4 para definir o valor da energia + crispy como 0. Return: - energia: Matriz com as energias crispy de todos os pontos da imagem. + energia (float): Matriz com as energias crispy de todos os pontos da imagem. """ energia = np.zeros(imagem.shape) for (x, y), value in np.ndenumerate(energia): @@ -36,7 +40,27 @@ def energia_externa( def S(imagem: np.ndarray, x: int, y: int) -> float: - return 1 + """ + Calcula a magnitude do gradiente de Sobel no ponto (x, y) da imagem. + + Parâmetros: + imagem (np.ndarray): A imagem em escala de cinza. + x (int): Coordenada x do ponto. + y (int): Coordenada y do ponto. + + Retorna: + magnitude (float): A magnitude do gradiente de Sobel no ponto (x, y). + """ + + if x < 0 or y < 0 or x >= imagem.shape[1] or y >= imagem.shape[0]: + raise ValueError("Coordenadas (x, y) devem estar nos limites da imagem.") + sobel_x = cv2.Sobel(imagem, cv2.CV_64F, 1, 0, ksize=3) + sobel_y = cv2.Sobel(imagem, cv2.CV_64F, 0, 1, ksize=3) + gradient_x = sobel_x[y, x] + gradient_y = sobel_y[y, x] + magnitude = np.sqrt(gradient_x**2 + gradient_y**2) + + return magnitude if "__main__": From 64dedc9c36fe4394d958939683b98b1142b1dc24 Mon Sep 17 00:00:00 2001 From: Mateus Date: Wed, 5 Feb 2025 20:30:26 -0300 Subject: [PATCH 13/29] feat#3.4/Feito --- segmentacao/energia.py | 43 +++++++----------------------------------- 1 file changed, 7 insertions(+), 36 deletions(-) diff --git a/segmentacao/energia.py b/segmentacao/energia.py index 4b89c05..3300e5f 100644 --- a/segmentacao/energia.py +++ b/segmentacao/energia.py @@ -1,6 +1,4 @@ import numpy as np -from carregar import carregar_imagem -from classificacao import calcula_ocorrencias_classes import cv2 @@ -28,46 +26,19 @@ def energia_externa( Return: energia (float): Matriz com as energias crispy de todos os pontos da imagem. """ + #Calculo do Sobel + sobel_x = cv2.Sobel(imagem, cv2.CV_64F, 1, 0, ksize=3) + sobel_y = cv2.Sobel(imagem, cv2.CV_64F, 0, 1, ksize=3) + #Calculo da energia energia = np.zeros(imagem.shape) for (x, y), value in np.ndenumerate(energia): if not ( probabilidade[2][x][y] < probablidade3 and probabilidade[3][x][y] <= probablidade4 ): - energia[x][y] = S(imagem, x, y) + gradient_x = sobel_x[y, x] + gradient_y = sobel_y[y, x] + energia[x][y] = np.sqrt(gradient_x**2 + gradient_y**2) return energia - -def S(imagem: np.ndarray, x: int, y: int) -> float: - """ - Calcula a magnitude do gradiente de Sobel no ponto (x, y) da imagem. - - Parâmetros: - imagem (np.ndarray): A imagem em escala de cinza. - x (int): Coordenada x do ponto. - y (int): Coordenada y do ponto. - - Retorna: - magnitude (float): A magnitude do gradiente de Sobel no ponto (x, y). - """ - - if x < 0 or y < 0 or x >= imagem.shape[1] or y >= imagem.shape[0]: - raise ValueError("Coordenadas (x, y) devem estar nos limites da imagem.") - sobel_x = cv2.Sobel(imagem, cv2.CV_64F, 1, 0, ksize=3) - sobel_y = cv2.Sobel(imagem, cv2.CV_64F, 0, 1, ksize=3) - gradient_x = sobel_x[y, x] - gradient_y = sobel_y[y, x] - magnitude = np.sqrt(gradient_x**2 + gradient_y**2) - - return magnitude - - -if "__main__": - img = carregar_imagem("data/pulmao2/30.dcm") - print(type(img)) - func = calcula_ocorrencias_classes(img) - print(type(func)) - print(type(np.zeros(img.shape))) - print(func.shape) - print(energia_externa(img, calcula_ocorrencias_classes(img))) From 6f2b11788eb5b5e01b1bd4e7c2698c9e0a5c4f89 Mon Sep 17 00:00:00 2001 From: Mateus Date: Wed, 5 Feb 2025 21:17:22 -0300 Subject: [PATCH 14/29] =?UTF-8?q?Implementa=C3=A7=C3=A3o=20vetorizada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- segmentacao/energia.py | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/segmentacao/energia.py b/segmentacao/energia.py index 3300e5f..d563004 100644 --- a/segmentacao/energia.py +++ b/segmentacao/energia.py @@ -1,8 +1,7 @@ import numpy as np import cv2 - -def energia_externa( +def energia_externa2( imagem: np.ndarray, probabilidade: np.ndarray, probablidade3: float = 0.2, @@ -17,28 +16,22 @@ def energia_externa( imagem (np.ndarray): Imagem para calcular a energia. probabilidade (np.ndarray): Probabilidade de ocorrência de classe de todos os pontos. - probablidade3 (int): Limiar da probabilidade 3 para definir o valor da energia - crispy - como 0. - probablidade4 (int): Limiar da probabilidade 4 para definir o valor da energia - crispy - como 0. + probablidade3 (float): Limiar da probabilidade 3 para definir o valor da energia + crispy como 0. + probablidade4 (float): Limiar da probabilidade 4 para definir o valor da energia + crispy como 0. Return: - energia (float): Matriz com as energias crispy de todos os pontos da imagem. + energia (np.ndarray): Matriz com as energias crispy de todos os pontos da imagem. """ - #Calculo do Sobel + # Cálculo do Sobel sobel_x = cv2.Sobel(imagem, cv2.CV_64F, 1, 0, ksize=3) sobel_y = cv2.Sobel(imagem, cv2.CV_64F, 0, 1, ksize=3) - #Calculo da energia - energia = np.zeros(imagem.shape) - for (x, y), value in np.ndenumerate(energia): - if not ( - probabilidade[2][x][y] < probablidade3 - and probabilidade[3][x][y] <= probablidade4 - ): - gradient_x = sobel_x[y, x] - gradient_y = sobel_y[y, x] - energia[x][y] = np.sqrt(gradient_x**2 + gradient_y**2) + energia = np.sqrt(sobel_x**2 + sobel_y**2) + + # Mascara da probabilidade + mask = (probabilidade[2] >= probablidade3) | (probabilidade[3] > probablidade4) + energia[~mask] = 0 return energia + From a947fe7ab223a4deb1996393432aa0eabb45bc1f Mon Sep 17 00:00:00 2001 From: Mateus Date: Wed, 5 Feb 2025 21:17:27 -0300 Subject: [PATCH 15/29] =?UTF-8?q?Implementa=C3=A7=C3=A3o=20vetorizada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- segmentacao/energia.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/segmentacao/energia.py b/segmentacao/energia.py index d563004..74c0b37 100644 --- a/segmentacao/energia.py +++ b/segmentacao/energia.py @@ -21,7 +21,7 @@ def energia_externa2( probablidade4 (float): Limiar da probabilidade 4 para definir o valor da energia crispy como 0. Return: - energia (np.ndarray): Matriz com as energias crispy de todos os pontos da imagem. + energia (np.ndarray): Matriz das energias crispy de todos os pontos da imagem. """ # Cálculo do Sobel sobel_x = cv2.Sobel(imagem, cv2.CV_64F, 1, 0, ksize=3) From ed3615f3980740746e9dd3a14f2bd834bd4995bd Mon Sep 17 00:00:00 2001 From: Mateus Date: Wed, 5 Feb 2025 21:22:00 -0300 Subject: [PATCH 16/29] =?UTF-8?q?feat#3.4/Implementa=C3=A7=C3=A3o=20vetori?= =?UTF-8?q?zada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- segmentacao/energia.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/segmentacao/energia.py b/segmentacao/energia.py index 74c0b37..a8537e4 100644 --- a/segmentacao/energia.py +++ b/segmentacao/energia.py @@ -1,7 +1,7 @@ import numpy as np import cv2 -def energia_externa2( +def energia_externa( imagem: np.ndarray, probabilidade: np.ndarray, probablidade3: float = 0.2, @@ -28,7 +28,7 @@ def energia_externa2( sobel_y = cv2.Sobel(imagem, cv2.CV_64F, 0, 1, ksize=3) energia = np.sqrt(sobel_x**2 + sobel_y**2) - # Mascara da probabilidade + # Mascara de probabilidade mask = (probabilidade[2] >= probablidade3) | (probabilidade[3] > probablidade4) energia[~mask] = 0 From 54ff3f955b289a3cf168477927d4fbe58cda3c03 Mon Sep 17 00:00:00 2001 From: Davi0Cruz Date: Thu, 6 Feb 2025 07:41:52 -0300 Subject: [PATCH 17/29] =?UTF-8?q?feat:=20for=C3=A7a=20de=20continuidade?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- segmentacao/forca.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 segmentacao/forca.py diff --git a/segmentacao/forca.py b/segmentacao/forca.py new file mode 100644 index 0000000..46a372d --- /dev/null +++ b/segmentacao/forca.py @@ -0,0 +1,22 @@ +import numpy as np + + +def forca_continuidade(pontos: np.array, indice: int) -> np.float64: + """ + Calcula a força de continuidade da curva em um ponto. + + Args: + pontos: np.array - Pontos da curva. + indice: int - Índice do ponto. + Return: + float - Força de continuidade. + """ + + # Calcular a distância média entre os pontos + DM = np.linalg.norm(pontos - np.roll(pontos, 1, axis=0), axis=1).mean() + + # Calcular a derivada discreta do ponto + dc = np.linalg.norm(pontos[indice] - pontos[indice - 1]) + + return np.abs(DM - dc) + From d83fc2693b31b837f248bf5c845cc6ec29e7ecc9 Mon Sep 17 00:00:00 2001 From: Davi0Cruz Date: Thu, 6 Feb 2025 09:59:55 -0300 Subject: [PATCH 18/29] fix: snake case --- segmentacao/forca.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/segmentacao/forca.py b/segmentacao/forca.py index 46a372d..f29c0fe 100644 --- a/segmentacao/forca.py +++ b/segmentacao/forca.py @@ -13,10 +13,10 @@ def forca_continuidade(pontos: np.array, indice: int) -> np.float64: """ # Calcular a distância média entre os pontos - DM = np.linalg.norm(pontos - np.roll(pontos, 1, axis=0), axis=1).mean() + dm = np.linalg.norm(pontos - np.roll(pontos, 1, axis=0), axis=1).mean() # Calcular a derivada discreta do ponto dc = np.linalg.norm(pontos[indice] - pontos[indice - 1]) - return np.abs(DM - dc) + return np.abs(dm - dc) From a4544cac6dbb663c75ccb97235717f9c574cd7e8 Mon Sep 17 00:00:00 2001 From: Davi0Cruz Date: Thu, 6 Feb 2025 10:24:00 -0300 Subject: [PATCH 19/29] =?UTF-8?q?feat=207:=20testes=20for=C3=A7a=20de=20co?= =?UTF-8?q?ntinuidade?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_forca.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/test_forca.py diff --git a/tests/test_forca.py b/tests/test_forca.py new file mode 100644 index 0000000..fd3e110 --- /dev/null +++ b/tests/test_forca.py @@ -0,0 +1,43 @@ +import unittest +import numpy as np +from segmentacao.forca import forca_continuidade +class TestForcaContinuidade(unittest.TestCase): + def test_zero_distance(self): + pontos = np.array([[0, 0], [0, 0], [0, 0]]) + indice = 1 + result = forca_continuidade(pontos, indice) + self.assertAlmostEqual(result, 0.0) + + def test_linear_points(self): + pontos = np.array([[0, 0], [1, 0], [2, 0], [3, 0]]) + indice = 2 + result = forca_continuidade(pontos, indice) + self.assertTrue(result >= 0) + + def test_random_points(self): + np.random.seed(42) + pontos = np.random.rand(5, 2) + indice = 3 + result = forca_continuidade(pontos, indice) + self.assertIsInstance(result, np.float64) + + def test_last_point(self): + pontos = np.array([[0, 0], [1, 1], [2, 2]]) + indice = 2 + result = forca_continuidade(pontos, indice) + self.assertTrue(result >= 0) + + def test_triangulo(self): + pontos = np.array([[0, 0], [4,0],[4, 3]]) + indice = 0 + result = forca_continuidade(pontos, indice) + self.assertTrue(result == 1.0) + + def test_quadrado(self): + pontos = np.array([[0, 0], [4,0],[4, 4], [0, 4]]) + indice = 0 + result = forca_continuidade(pontos, indice) + self.assertTrue(result == 0.0) + +if __name__ == '__main__': + unittest.main() From 4b551a45aa6f93eb84efc27fcd43ec6b88b7c836 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 6 Feb 2025 14:14:48 -0300 Subject: [PATCH 20/29] Adicionada a funcionalidade de forca adaptativa (issue #12) --- segmentacao/forca.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 segmentacao/forca.py diff --git a/segmentacao/forca.py b/segmentacao/forca.py new file mode 100644 index 0000000..8cdf048 --- /dev/null +++ b/segmentacao/forca.py @@ -0,0 +1,21 @@ +import numpy as np + +def forca_adaptativa(pontos: np.ndarray, indice: int) -> float: + """ + Recebe os pontos da curva e calcula a força adaptativa no ponto de índice 'indice'. + + Args: + pontos (np.ndarray): Pontos da curva. + indice (int): Índice do ponto. + Returns: + float: Força adaptativa no ponto de índice 'indice'. + """ + xm = (pontos[(indice - 1%len(pontos)), 0] + pontos[(indice + 1)%len(pontos), 0])/2 + ym = (pontos[(indice - 1%len(pontos)), 1] + pontos[(indice + 1)%len(pontos), 1])/2 + v1 = pontos[(indice + 1)%len(pontos)] - pontos[indice] + v2 = pontos[(indice - 1)%len(pontos)] - pontos[indice] + vet = v1[0]*v2[1] - v1[1]*v2[0] + sig = 1 + if vet < 0: + sig = -1 + return (abs(pontos[indice, 0] + sig*xm)**2 + abs(pontos[indice, 1] + sig*ym)**2)**0.5 \ No newline at end of file From 5a1921499d091537cf012dd5e2d828e6302a824b Mon Sep 17 00:00:00 2001 From: Rycerax Date: Thu, 6 Feb 2025 19:26:31 -0300 Subject: [PATCH 21/29] Code fixes --- segmentacao/forca.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/segmentacao/forca.py b/segmentacao/forca.py index 8cdf048..1634ab7 100644 --- a/segmentacao/forca.py +++ b/segmentacao/forca.py @@ -1,8 +1,7 @@ -import numpy as np - def forca_adaptativa(pontos: np.ndarray, indice: int) -> float: """ - Recebe os pontos da curva e calcula a força adaptativa no ponto de índice 'indice'. + Recebe os pontos em ordem anti-horária da curva e calcula a força adaptativa + no ponto de índice 'indice'. Args: pontos (np.ndarray): Pontos da curva. @@ -10,12 +9,14 @@ def forca_adaptativa(pontos: np.ndarray, indice: int) -> float: Returns: float: Força adaptativa no ponto de índice 'indice'. """ - xm = (pontos[(indice - 1%len(pontos)), 0] + pontos[(indice + 1)%len(pontos), 0])/2 - ym = (pontos[(indice - 1%len(pontos)), 1] + pontos[(indice + 1)%len(pontos), 1])/2 + xm = (pontos[(indice - 1)%len(pontos), 0] + pontos[(indice + 1)%len(pontos), 0])/2 + ym = (pontos[(indice - 1)%len(pontos), 1] + pontos[(indice + 1)%len(pontos), 1])/2 v1 = pontos[(indice + 1)%len(pontos)] - pontos[indice] v2 = pontos[(indice - 1)%len(pontos)] - pontos[indice] vet = v1[0]*v2[1] - v1[1]*v2[0] sig = 1 + if vet == 0: + return 0 if vet < 0: sig = -1 return (abs(pontos[indice, 0] + sig*xm)**2 + abs(pontos[indice, 1] + sig*ym)**2)**0.5 \ No newline at end of file From 1cbf982d57784e594c6aa47ac25e161346482a1c Mon Sep 17 00:00:00 2001 From: Davi0Cruz Date: Thu, 6 Feb 2025 20:18:15 -0300 Subject: [PATCH 22/29] add .vscode to git ignore --- .gitignore | 1 + tests/test_forca.py | 39 ++++++++++++++++++++++----------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 2b236c9..15e58e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .venv/ __pycache__/ .ruff_cache/ +.vscode/ diff --git a/tests/test_forca.py b/tests/test_forca.py index fd3e110..ebd2b64 100644 --- a/tests/test_forca.py +++ b/tests/test_forca.py @@ -1,43 +1,48 @@ import unittest import numpy as np from segmentacao.forca import forca_continuidade + + class TestForcaContinuidade(unittest.TestCase): - def test_zero_distance(self): + def test_zero_distancia(self): pontos = np.array([[0, 0], [0, 0], [0, 0]]) indice = 1 - result = forca_continuidade(pontos, indice) - self.assertAlmostEqual(result, 0.0) + resultado = forca_continuidade(pontos, indice) + self.assertAlmostEqual(resultado, 0.0) - def test_linear_points(self): + def test_pontos_linear(self): pontos = np.array([[0, 0], [1, 0], [2, 0], [3, 0]]) indice = 2 - result = forca_continuidade(pontos, indice) - self.assertTrue(result >= 0) + resultado = forca_continuidade(pontos, indice) + self.assertTrue(resultado >= 0) - def test_random_points(self): + def test_tipo_saida(self): np.random.seed(42) pontos = np.random.rand(5, 2) indice = 3 - result = forca_continuidade(pontos, indice) - self.assertIsInstance(result, np.float64) + resultado = forca_continuidade(pontos, indice) + self.assertIsInstance(resultado, np.float64) - def test_last_point(self): + def test_ultimo_ponto(self): pontos = np.array([[0, 0], [1, 1], [2, 2]]) indice = 2 - result = forca_continuidade(pontos, indice) - self.assertTrue(result >= 0) - + resultado = forca_continuidade(pontos, indice) + self.assertTrue(resultado >= 0) + def test_triangulo(self): - pontos = np.array([[0, 0], [4,0],[4, 3]]) + pontos = np.array([[0, 0], [4, 0], [4, 3]]) indice = 0 result = forca_continuidade(pontos, indice) + # distancia de 5 é 1 maior que a média self.assertTrue(result == 1.0) - + def test_quadrado(self): - pontos = np.array([[0, 0], [4,0],[4, 4], [0, 4]]) + pontos = np.array([[0, 0], [4, 0], [4, 4], [0, 4]]) indice = 0 + # quadrado tem os lados iguais, então a distancia cancela result = forca_continuidade(pontos, indice) self.assertTrue(result == 0.0) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() From 284db7b53930fce54d26c48cd11d12b702a06424 Mon Sep 17 00:00:00 2001 From: Davi0Cruz Date: Thu, 6 Feb 2025 20:30:06 -0300 Subject: [PATCH 23/29] feat 7: testes --- tests/segmentacao/test_forca.py | 29 ++++++++++++++++++++ tests/test_forca.py | 48 --------------------------------- 2 files changed, 29 insertions(+), 48 deletions(-) create mode 100644 tests/segmentacao/test_forca.py delete mode 100644 tests/test_forca.py diff --git a/tests/segmentacao/test_forca.py b/tests/segmentacao/test_forca.py new file mode 100644 index 0000000..de5eb0f --- /dev/null +++ b/tests/segmentacao/test_forca.py @@ -0,0 +1,29 @@ +import pytest +import numpy as np +from segmentacao.forca import forca_continuidade + +def test_zero_distancia(): + pontos = np.array([[0, 0], [0, 0], [0, 0]]) + assert forca_continuidade(pontos, 1) == pytest.approx(0.0) + +def test_pontos_linear(): + pontos = np.array([[0, 0], [1, 0], [2, 0], [3, 0]]) + assert forca_continuidade(pontos, 2) >= 0 + +def test_tipo_saida(): + np.random.seed(42) + pontos = np.random.rand(5, 2) + resultado = forca_continuidade(pontos, 3) + assert isinstance(resultado, np.float64) + +def test_ultimo_ponto(): + pontos = np.array([[0, 0], [1, 1], [2, 2]]) + assert forca_continuidade(pontos, 2) >= 0 + +def test_triangulo(): + pontos = np.array([[0, 0], [4, 0], [4, 3]]) + assert forca_continuidade(pontos, 0) == 1.0 + +def test_quadrado(): + pontos = np.array([[0, 0], [4, 0], [4, 4], [0, 4]]) + assert forca_continuidade(pontos, 0) == 0.0 diff --git a/tests/test_forca.py b/tests/test_forca.py deleted file mode 100644 index ebd2b64..0000000 --- a/tests/test_forca.py +++ /dev/null @@ -1,48 +0,0 @@ -import unittest -import numpy as np -from segmentacao.forca import forca_continuidade - - -class TestForcaContinuidade(unittest.TestCase): - def test_zero_distancia(self): - pontos = np.array([[0, 0], [0, 0], [0, 0]]) - indice = 1 - resultado = forca_continuidade(pontos, indice) - self.assertAlmostEqual(resultado, 0.0) - - def test_pontos_linear(self): - pontos = np.array([[0, 0], [1, 0], [2, 0], [3, 0]]) - indice = 2 - resultado = forca_continuidade(pontos, indice) - self.assertTrue(resultado >= 0) - - def test_tipo_saida(self): - np.random.seed(42) - pontos = np.random.rand(5, 2) - indice = 3 - resultado = forca_continuidade(pontos, indice) - self.assertIsInstance(resultado, np.float64) - - def test_ultimo_ponto(self): - pontos = np.array([[0, 0], [1, 1], [2, 2]]) - indice = 2 - resultado = forca_continuidade(pontos, indice) - self.assertTrue(resultado >= 0) - - def test_triangulo(self): - pontos = np.array([[0, 0], [4, 0], [4, 3]]) - indice = 0 - result = forca_continuidade(pontos, indice) - # distancia de 5 é 1 maior que a média - self.assertTrue(result == 1.0) - - def test_quadrado(self): - pontos = np.array([[0, 0], [4, 0], [4, 4], [0, 4]]) - indice = 0 - # quadrado tem os lados iguais, então a distancia cancela - result = forca_continuidade(pontos, indice) - self.assertTrue(result == 0.0) - - -if __name__ == "__main__": - unittest.main() From 7c5407a7b6a426a40f329e59475f1d73a7127d33 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 7 Feb 2025 10:58:40 -0300 Subject: [PATCH 24/29] =?UTF-8?q?Aplica=C3=A7=C3=A3o=20da=20vetoriza=C3=A7?= =?UTF-8?q?=C3=A3o.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- segmentacao/forca.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/segmentacao/forca.py b/segmentacao/forca.py index 1634ab7..5c7c043 100644 --- a/segmentacao/forca.py +++ b/segmentacao/forca.py @@ -9,14 +9,10 @@ def forca_adaptativa(pontos: np.ndarray, indice: int) -> float: Returns: float: Força adaptativa no ponto de índice 'indice'. """ - xm = (pontos[(indice - 1)%len(pontos), 0] + pontos[(indice + 1)%len(pontos), 0])/2 - ym = (pontos[(indice - 1)%len(pontos), 1] + pontos[(indice + 1)%len(pontos), 1])/2 + pm = (pontos[(indice - 1)%len(pontos)] + pontos[(indice + 1)%len(pontos)])/2 v1 = pontos[(indice + 1)%len(pontos)] - pontos[indice] v2 = pontos[(indice - 1)%len(pontos)] - pontos[indice] - vet = v1[0]*v2[1] - v1[1]*v2[0] - sig = 1 + vet = np.sign(np.linalg.det([v1, v2])) if vet == 0: return 0 - if vet < 0: - sig = -1 - return (abs(pontos[indice, 0] + sig*xm)**2 + abs(pontos[indice, 1] + sig*ym)**2)**0.5 \ No newline at end of file + return np.linalg.norm(pm + vet*pontos[indice]) \ No newline at end of file From 6d3d871da5f8c66f1a80e140d4715b1b89f08e32 Mon Sep 17 00:00:00 2001 From: David Lima Date: Sun, 9 Feb 2025 20:12:33 -0300 Subject: [PATCH 25/29] =?UTF-8?q?Fun=C3=A7=C3=A3o=20para=20converter=20HU?= =?UTF-8?q?=20para=20escala=20de=20cinza?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- alternativas/hu_para_cinza.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 alternativas/hu_para_cinza.py diff --git a/alternativas/hu_para_cinza.py b/alternativas/hu_para_cinza.py new file mode 100644 index 0000000..30e8bb7 --- /dev/null +++ b/alternativas/hu_para_cinza.py @@ -0,0 +1,18 @@ +import numpy as np + +def converter_hu_para_cinza(imagem_hu, hu_min=-1000, hu_max=2000) -> np.ndarray: + """ + Converte Hounsfield Units (HU) para escala de cinza. + + args: + input_path: np.ndarray - Imagem em Hounsfield Units (HU) + return: + np.ndarray - Imagem em escala de cinza + """ + + largura_hu = hu_max - hu_min + + imagem_hu = imagem_hu.astype(np.float32) + imagem_escala_cinza = np.clip((255 * (imagem_hu - hu_min)) / largura_hu, 0, 255) + + return imagem_escala_cinza.astype(np.uint8) From 8235a742e327bfa32dc61ffe22836a54ab247cae Mon Sep 17 00:00:00 2001 From: Paulo Ricardo Date: Tue, 18 Feb 2025 17:36:16 -0300 Subject: [PATCH 26/29] feat: adiciona remocao de pontos #27 --- segmentacao/curva.py | 113 +++++++++++++++++++++++++++++++------------ 1 file changed, 83 insertions(+), 30 deletions(-) diff --git a/segmentacao/curva.py b/segmentacao/curva.py index f1d08c2..1218ad1 100644 --- a/segmentacao/curva.py +++ b/segmentacao/curva.py @@ -1,37 +1,12 @@ import numpy as np -def inicializa_curva( - ponto: np.ndarray, - raio: int = 30, - quantidade_pixels: int = 30, -) -> np.ndarray: - """ - Recebe o ponto inicial do eixo x e y, calcula a curva e retorna a duas - listas de pontos que representam a curva. - - Args: - ponto: (x,y) que representa o centro da curva - raio: raio da curva - quantidade_pixels: quantidade de pontos que a curva terá - Return: - curva: lista de pontos que representam a curva - Raises: - AssertionError: caso curva seja menor que 0 em algum ponto - AssertionError: caso a quantidade_pixels seja menor que 2 - """ - assert quantidade_pixels >= 2, "Quantidade de pixels deve ser sempre maior que 2" - - angulos = np.linspace(0, 2 * np.pi, quantidade_pixels) - curva = ponto + np.c_[np.cos(angulos), np.sin(angulos)] * raio - - assert np.all(curva > 0), "Os pontos da curva devem sempre ser maior que 0" - - return curva.astype(np.int16) - - def crisp_inicial( - imagem: np.ndarray, lim_infY: int, lim_supY: int, lim_infX: int, lim_supX: int + imagem: np.ndarray, + lim_infY: int, + lim_supY: int, + lim_infX: int, + lim_supX: int ) -> np.ndarray: """ Recebe uma imagem em formato de array NumPy e recorta a região definida pelos @@ -82,3 +57,81 @@ def crisp_inicial( Y = np.sum(P, axis=0) # Soma na direção das linhas return np.array([np.argmax(Y) + lim_infX, np.argmax(X) + lim_infY]) + + +def inicializa_curva( + ponto: np.ndarray, + raio: int = 30, + quantidade_pixels: int = 30, +) -> np.ndarray: + """ + Recebe o ponto inicial do eixo x e y, calcula a curva e retorna a duas + listas de pontos que representam a curva. + + Args: + ponto: (x,y) que representa o centro da curva + raio: raio da curva + quantidade_pixels: quantidade de pontos que a curva terá + Return: + curva: lista de pontos que representam a curva + Raises: + AssertionError: caso curva seja menor que 0 em algum ponto + AssertionError: caso a quantidade_pixels seja menor que 2 + """ + assert quantidade_pixels >= 2, "Quantidade de pixels deve ser sempre maior que 2" + + angulos = np.linspace(0, 2 * np.pi, quantidade_pixels+1) + curva = ponto + np.c_[np.cos(angulos), np.sin(angulos)] * raio + + assert np.all(curva > 0), "Os pontos da curva devem sempre ser maior que 0" + + return curva[:-1].astype(np.int16) + +def calcular_angulo(p1:np.ndarray, + p2:np.ndarray, + p3:np.ndarray +) -> float: + """ + Calcula o ângulo formado pelos três pontos (em graus). + Args: + pontos p1(i-1), p2(i) e p3(i+1) + Return: + angulo em graus entre os 3 pontos + + """ + v1 = p1 - p2 + v2 = p3 - p2 + + cos_theta = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2)) + cos_theta = np.clip(cos_theta, -1.0, 1.0) #Manter o cos entre -1 e 1 + + return np.degrees(np.arccos(cos_theta)) + + +def remover_pontos( + curva: np.ndarray, + alpha: float = 20 +) -> np.ndarray: + """ + Recebe pontos da curva e um ângulo mínimo para remover pontos da curva. + + Args: + curva (np.darray): Pontos da curva. + alpha (float): Ângulo mínimo entre dois pontos para remover da curva. + Returns: + np.darray: Curva com os pontos removidos. + """ + + assert curva.shape[-1] == 2, "A curva deve ser um array de shape [n,2]" + + curva = curva[np.insert(np.any(np.diff(curva, axis=0), axis=1), 0, True)] + + curva_filtrada = [curva[0]] + + for i in range(1, len(curva) - 1): + angulo = calcular_angulo(curva[i-1], curva[i], curva[i+1]) + if angulo > alpha: + curva_filtrada.append(curva[i]) + + curva_filtrada.append(curva[-1]) + return np.array(curva_filtrada, dtype=np.int16) From bfd9f7beb72e819ee999c36decbe59b5064654f3 Mon Sep 17 00:00:00 2001 From: gabrielfruet Date: Wed, 19 Feb 2025 08:43:38 -0300 Subject: [PATCH 27/29] feat: energia interna adaptativa --- segmentacao/energia.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/segmentacao/energia.py b/segmentacao/energia.py index a8537e4..7c547b0 100644 --- a/segmentacao/energia.py +++ b/segmentacao/energia.py @@ -1,6 +1,9 @@ import numpy as np import cv2 +from segmentacao.forca import forca_adaptativa, forca_continuidade + + def energia_externa( imagem: np.ndarray, probabilidade: np.ndarray, @@ -35,3 +38,9 @@ def energia_externa( return energia +def energia_interna_adaptativa( + curva: np.ndarray, indice: int, w_adapt: float = 0.1, w_cont: float = 0.6 +): + adaptativa = w_adapt * forca_adaptativa(curva, indice) + continuidade = w_cont * forca_continuidade(curva, indice) + return adaptativa + continuidade From 78960180f9ba105e13c9fc8bbaa5d1cb834ffd8c Mon Sep 17 00:00:00 2001 From: gabrielfruet Date: Wed, 19 Feb 2025 08:43:53 -0300 Subject: [PATCH 28/29] =?UTF-8?q?fix:=20corre=C3=A7=C3=B5es=20no=20fluxo?= =?UTF-8?q?=20de=20trabalho=20da=20main.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 65a9789..11e5258 100644 --- a/main.py +++ b/main.py @@ -29,6 +29,7 @@ def visualize(images: list[cv2.typing.MatLike]): cv2.imshow(f"image_{i}", image) key = cv2.waitKey(0) if key == ord("q"): + cv2.destroyAllWindows() return @@ -39,7 +40,7 @@ def visualize(images: list[cv2.typing.MatLike]): ) args = parser.parse_args() - image = carregar_imagem(args.input_image) + image = carregar_imagem(args.image_de_entrada) print(image.max()) visualize([apply_window(image, -300, 700)]) cv2.destroyAllWindows() From d0b6cdaf1c13a348f5a0711c4b93803eabf63a73 Mon Sep 17 00:00:00 2001 From: Wanderson Batista <75703010+wandersonbatista@users.noreply.github.com> Date: Tue, 25 Feb 2025 10:34:08 -0300 Subject: [PATCH 29/29] Add files via upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit verificação se o ponto ta na curva com erros --- segmentacao/add.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 segmentacao/add.py diff --git a/segmentacao/add.py b/segmentacao/add.py new file mode 100644 index 0000000..b03dab8 --- /dev/null +++ b/segmentacao/add.py @@ -0,0 +1,63 @@ +import numpy as np +from matplotlib.path import Path +from energia import energia_interna_adaptativa +from carregar import carregar_imagem +from classificacao import calcula_ocorrencias_classes, probabilidade_classes + + +def adicionar_pontos( + curva: np.ndarray, imagem: np.ndarray, d: float, w_adapt=0.1, w_cont=0.6 +) -> np.ndarray: + """ + Adiciona pontos à curva minimizando a energia e garantindo que pertencem ao pulmão. + + Args: + curva (np.ndarray): Pontos da curva inicial. + imagem (np.ndarray): Imagem DICOM carregada em Unidades Hounsfield. + d (float): Distância mínima entre pontos. + w_adapt (float): Peso da energia adaptativa. + w_cont (float): Peso da energia de continuidade. + + Returns: + np.ndarray: Curva refinada com pontos adicionados. + """ + nova_curva = [curva[0]] + poligono = Path(curva) # Criar polígono da curva para verificação de inclusão + + # Calcular probabilidades das classes pulmonares + ocorrencias = calcula_ocorrencias_classes(imagem) + probabilidades = probabilidade_classes(ocorrencias) + + for i in range(len(curva) - 1): + p1, p2 = curva[i], curva[i + 1] + dist = np.linalg.norm(p2 - p1) + + if dist > d: + num_pontos = int(dist // d) + melhor_pontos = [] + + for j in range(1, num_pontos + 1): + candidato = p1 + (p2 - p1) * (j / (num_pontos + 1)) + + # Verificar se o ponto está dentro da curva + if poligono.contains_point(candidato): + x, y = int(candidato[0]), int(candidato[1]) + + # Verificar se o ponto pertence ao pulmão (hiperaerado e normalmente aerado) + if -1000 <= imagem[y, x] <= -500: + energia = energia_interna_adaptativa(curva, i, w_adapt, w_cont) + prob_pulmao = ( + probabilidades[0, y, x] + probabilidades[1, y, x] + ) # Soma das classes pulmonares + + # Só adiciona o ponto se a probabilidade de pulmão for alta + if prob_pulmao > 0.5: + melhor_pontos.append((energia, candidato)) + + # Ordenar os pontos pela menor energia + melhor_pontos.sort(key=lambda x: x[0]) + nova_curva.extend([p[1] for p in melhor_pontos]) + + nova_curva.append(p2) + + return np.array(nova_curva)