Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
3208338
feat: pytest adicionado
gabrielfruet Feb 3, 2025
d9463fe
feat: inicialização da curva
gabrielfruet Feb 5, 2025
87b3215
feat: vetorizacao na funcao de inicializacao de curva
gabrielfruet Feb 5, 2025
fb15580
fix: os pontos da curva devem ser sempre maiores que 0
gabrielfruet Feb 5, 2025
b9cb61d
fix: quantidade de pixels deve ser maior que 2
gabrielfruet Feb 5, 2025
2738fb1
feat: testes para inicializa_curva
gabrielfruet Feb 5, 2025
9ba0bf9
chore: testando a branch #6
pauloricms12 Feb 5, 2025
9869005
Merge branch 'dev' into feat-5/inicializacao-curva
gabrielfruet Feb 5, 2025
76a8cca
Merge pull request #8 from ProjetoFinalPDI/feat-5/inicializacao-curva
gabrielfruet Feb 5, 2025
59ca8c2
feat: adiciona crisp inicial #6
pauloricms12 Feb 5, 2025
273eb3d
feat: formatacao com ruff #6
pauloricms12 Feb 5, 2025
f3655d5
feat#3.3/Implementada a lógica sem a função do Sobel e ambiente de te…
MateusSantos14 Feb 5, 2025
a0db6c4
feat#3.3/Formatado ruff e documentado
MateusSantos14 Feb 5, 2025
82a700b
feat#3.4/ Adicionado operador Sobel
MateusSantos14 Feb 5, 2025
64dedc9
feat#3.4/Feito
MateusSantos14 Feb 5, 2025
126ec18
Merge pull request #9 from ProjetoFinalPDI/feat4-energiaexcrisp
gabrielfruet Feb 5, 2025
6f2b117
Implementação vetorizada
MateusSantos14 Feb 6, 2025
a947fe7
Implementação vetorizada
MateusSantos14 Feb 6, 2025
ed3615f
feat#3.4/Implementação vetorizada
MateusSantos14 Feb 6, 2025
54ff3f9
feat: força de continuidade
Davi0Cruz Feb 6, 2025
d83fc26
fix: snake case
Davi0Cruz Feb 6, 2025
a4544ca
feat 7: testes força de continuidade
Davi0Cruz Feb 6, 2025
4b551a4
Adicionada a funcionalidade de forca adaptativa (issue #12)
Feb 6, 2025
dae11e1
Merge pull request #13 from ProjetoFinalPDI/feat4-energiaexcrisp
gabrielfruet Feb 6, 2025
5a19214
Code fixes
rafaelcapeloo Feb 6, 2025
1cbf982
add .vscode to git ignore
Davi0Cruz Feb 6, 2025
284db7b
feat 7: testes
Davi0Cruz Feb 6, 2025
f9589d7
Merge branch 'dev' into feat-7/forca-continuidade
Davi0Cruz Feb 6, 2025
a9ae12e
Merge pull request #18 from ProjetoFinalPDI/feat-12/forca-adaptativa
gabrielfruet Feb 7, 2025
7c5407a
Aplicação da vetorização.
Feb 7, 2025
2ad855f
Merge branch 'dev' into feat-7/forca-continuidade
gabrielfruet Feb 7, 2025
f02b560
Merge pull request #19 from ProjetoFinalPDI/feat-7/forca-continuidade
gabrielfruet Feb 7, 2025
5ee5181
Merge branch 'dev' into feat-12/forca-adaptativa
gabrielfruet Feb 7, 2025
af13d62
Merge pull request #20 from ProjetoFinalPDI/feat-12/forca-adaptativa
gabrielfruet Feb 7, 2025
6d3d871
Função para converter HU para escala de cinza
Davidls10 Feb 9, 2025
4d47455
Merge branch 'feat-6/crisp-inicial' into dev
gabrielfruet Feb 11, 2025
c80d006
Merge pull request #23 from ProjetoFinalPDI/dev
gabrielfruet Feb 11, 2025
92889d0
Merge pull request #25 from ProjetoFinalPDI/feat-24/hu_para_cinza
Davidls10 Feb 11, 2025
8235a74
feat: adiciona remocao de pontos #27
pauloricms12 Feb 18, 2025
739a505
Merge pull request #30 from ProjetoFinalPDI/feat-27/remover-pontos-da…
gabrielfruet Feb 19, 2025
bfd9f7b
feat: energia interna adaptativa
gabrielfruet Feb 19, 2025
7896018
fix: correções no fluxo de trabalho da main.py
gabrielfruet Feb 19, 2025
f515ebe
Merge pull request #33 from ProjetoFinalPDI/feat-31/energia-interna-a…
gabrielfruet Feb 19, 2025
d0b6cda
Add files via upload
wandersonbatista Feb 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.venv/
__pycache__/
.ruff_cache/
.vscode/
4 changes: 4 additions & 0 deletions Makefile

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

acho que não precisa de makefile

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.PHONY: test
test:
pytest ./tests --rootdir=.

18 changes: 18 additions & 0 deletions alternativas/hu_para_cinza.py
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 2 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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()
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
pythonpath = .
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ scipy==1.15.1
numba==0.61.0
pydicom==3.0.1
ruff==0.9.4
pytest==8.3.4
flask==3.1.0
flask-cors==5.0.0
flask-cors==5.0.0
63 changes: 63 additions & 0 deletions segmentacao/add.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A adição de pontos não deveria lidar com a energia. Boa idaide usar a função do matplotlib!

Original file line number Diff line number Diff line change
@@ -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)
137 changes: 137 additions & 0 deletions segmentacao/curva.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
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])


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)
46 changes: 46 additions & 0 deletions segmentacao/energia.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import numpy as np
import cv2

from segmentacao.forca import forca_adaptativa, forca_continuidade


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 (np.ndarray): Imagem para calcular a energia.
probabilidade (np.ndarray): Probabilidade de ocorrência de classe de todos os
pontos.
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 (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)
sobel_y = cv2.Sobel(imagem, cv2.CV_64F, 0, 1, ksize=3)
energia = np.sqrt(sobel_x**2 + sobel_y**2)

# Mascara de probabilidade
mask = (probabilidade[2] >= probablidade3) | (probabilidade[3] > probablidade4)
energia[~mask] = 0

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
41 changes: 41 additions & 0 deletions segmentacao/forca.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import numpy as np


def forca_continuidade(pontos: np.ndarray, 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)


def forca_adaptativa(pontos: np.ndarray, indice: int) -> np.float64:
"""
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.
indice (int): Índice do ponto.
Returns:
float: Força adaptativa no ponto de índice 'indice'.
"""
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 = np.sign(np.linalg.det([v1, v2]))
if vet == 0:
return np.float64(0.0)
return np.linalg.norm(pm + vet * pontos[indice])
Loading