Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #include <mpi.h> // Biblioteca central de OpenMPI para paralelismo
- #include <iostream> // Para usar std::cout y mostrar texto en consola
- #include <vector> // Para usar vectores dinámicos (búferes de memoria contigua)
- #include <fstream> // Para abrir y leer/escribir archivos binarios (ifstream/ofstream)
- #include <string> // Para manipular cadenas de texto (nombres de archivos)
- #include <algorithm> // Para operaciones de copiado rápido (std::copy)
- #include <cstdlib> // Para ejecutar comandos de Linux (std::system)
- // Estructura para almacenar los metadatos de la mamografía
- struct PGMHeader {
- int width = 0; // Ancho de la imagen (ej. 1024)
- int height = 0; // Alto de la imagen (ej. 1024)
- int max_val = 0; // Valor máximo de brillo (típicamente 255)
- };
- // Función para cargar la imagen PGM desde el disco a la memoria RAM
- bool readPGM(const std::string& filename, PGMHeader& header, std::vector<unsigned char>& data) {
- std::ifstream file(filename, std::ios::binary);
- if (!file.is_open()) return false;
- std::string type; file >> type;
- if (type != "P5") return false; // Valida que sea PGM Binario (P5)
- char ch; file >> ch;
- // Este bucle saltea los comentarios que suelen tener las imágenes médicas (#...)
- while (ch == '#') { std::string comment; std::getline(file, comment); file >> ch; }
- file.putback(ch);
- file >> header.width >> header.height >> header.max_val;
- file.ignore(); // Saltea el espacio en blanco antes de los píxeles puros
- // Redimensiona el vector al tamaño justo (1024 * 1024 = 1.048.576 bytes)
- data.resize(header.width * header.height);
- file.read(reinterpret_cast<char*>(data.data()), data.size()); // Lee los píxeles de un solo golpe
- return file.good();
- }
- // Función para guardar el resultado en PPM (P6) que admite canales RGB (Color)
- bool writePPM(const std::string& filename, const PGMHeader& header, const std::vector<unsigned char>& data_rgb) {
- std::ofstream file(filename, std::ios::binary);
- if (!file.is_open()) return false;
- file << "P6\n" << header.width << " " << header.height << "\n255\n"; // Cabecera PPM
- file.write(reinterpret_cast<const char*>(data_rgb.data()), data_rgb.size()); // Graba los píxeles a color
- return file.good();
- }
- int main(int argc, char** argv) {
- int prop;
- // Inicializa el entorno MPI con soporte para hilos simples
- MPI_Init_thread(&argc, &argv, MPI_THREAD_SINGLE, &prop);
- int rank, size;
- MPI_Comm_rank(MPI_COMM_WORLD, &rank); // Obtiene el ID del proceso (0 para Maestro, 1 para Esclavo)
- MPI_Comm_size(MPI_COMM_WORLD, &size); // Obtiene el total de procesos del clúster (2 en nuestro caso)
- std::string archivo_objetivo = "mamografia.pgm"; // Nombre interno temporal
- if (rank == 0) { // === ROL DEL PROCESO MAESTRO ===
- int opcion_principal = 0;
- do {
- std::cout << "\n=======================================================" << std::endl;
- std::cout << " SISTEMA DE PROCESAMIENTO PARALELO DE IMAGENES MEDICAS " << std::endl;
- std::cout << "=======================================================" << std::endl;
- std::cout << "1. Cargar/Subir imagen desde carpeta 'Downloads'" << std::endl;
- std::cout << "2. Salir" << std::endl;
- std::cout << "Seleccione una opcion: "; std::cin >> opcion_principal;
- // Sincronización por Difusión: El Maestro le avisa al Esclavo qué opción se eligió
- MPI_Bcast(&opcion_principal, 1, MPI_INT, 0, MPI_COMM_WORLD);
- if (opcion_principal == 1) {
- std::string nombre_archivo;
- std::cout << "Ingrese el nombre del archivo en Downloads: "; std::cin >> nombre_archivo;
- // Comando de Linux que copia la imagen real del usuario a la carpeta del programa
- std::string comando_mover = "cp ~/Downloads/" + nombre_archivo + " ./" + archivo_objetivo + " 2>/dev/null";
- std::system(comando_mover.c_str());
- std::ifstream test(archivo_objetivo);
- if (!test.good()) {
- std::cout << "Error: No se encontro el archivo." << std::endl;
- int flag_error = -1;
- MPI_Bcast(&flag_error, 1, MPI_INT, 0, MPI_COMM_WORLD); // Cancela al Esclavo si hay error
- continue;
- }
- test.close();
- std::cout << "¡Imagen cargada con exito al sistema!" << std::endl;
- int opcion_filtro = 0;
- do {
- std::cout << "\n--- SUBMENU: FILTROS MORFOLOGICOS DISPONIBLES ---" << std::endl;
- std::cout << "1. Aplicar Erosion Morfologica" << std::endl;
- std::cout << "2. Aplicar Dilatacion Morfologica" << std::endl;
- std::cout << "3. Detección de Nódulos (Marcado Color Rojo)" << std::endl;
- std::cout << "4. Volver al menu principal" << std::endl;
- std::cout << "Seleccione un filtro: "; std::cin >> opcion_filtro;
- // El Maestro le avisa al Esclavo qué filtro va a correr para que salten juntos
- MPI_Bcast(&opcion_filtro, 1, MPI_INT, 0, MPI_COMM_WORLD);
- if (opcion_filtro >= 1 && opcion_filtro <= 3) {
- procesarFiltro(opcion_filtro, rank, size, archivo_objetivo);
- }
- } while (opcion_filtro != 4);
- }
- } while (opcion_principal != 2);
- }
- else { // === ROL DEL PROCESO ESCLAVO (RANK 1) ===
- int opcion_principal = 0;
- do {
- // El Esclavo se bloquea esperando recibir la orden del Maestro vía Bcast
- MPI_Bcast(&opcion_principal, 1, MPI_INT, 0, MPI_COMM_WORLD);
- if (opcion_principal == 1) {
- int opcion_filtro = 0;
- do {
- MPI_Bcast(&opcion_filtro, 1, MPI_INT, 0, MPI_COMM_WORLD);
- if (opcion_filtro == -1) break; // Si hubo error de archivo, vuelve a esperar
- if (opcion_filtro >= 1 && opcion_filtro <= 3) {
- procesarFiltro(opcion_filtro, rank, size, archivo_objetivo);
- }
- } while (opcion_filtro != 4);
- }
- } while (opcion_principal != 2);
- }
- MPI_Finalize(); // Apaga el entorno MPI de forma limpia
- return 0;
- }
- void procesarFiltro(int filtro, int rank, int size, const std::string& nombre_archivo) {
- PGMHeader header;
- std::vector<unsigned char> full_image;
- std::vector<int> send_counts(size, 0), displacements(size, 0);
- // 1. Solo el Maestro lee físicamente el archivo del disco
- if (rank == 0) {
- readPGM(nombre_archivo, header, full_image);
- int base_rows = header.height / size;
- int remainder = header.height % size;
- int current_disp = 0;
- // Calcula matemáticamente cuántos píxeles le tocan a cada proceso (Balance de carga)
- for (int i = 0; i < size; ++i) {
- int rows_for_proc = base_rows + (i < remainder ? 1 : 0);
- send_counts[i] = rows_for_proc * header.width;
- displacements[i] = current_disp;
- current_disp += send_counts[i];
- }
- }
- // 2. Transmisión de metadatos de cabecera a todos los procesos
- MPI_Bcast(&header.width, 1, MPI_INT, 0, MPI_COMM_WORLD);
- MPI_Bcast(&header.height, 1, MPI_INT, 0, MPI_COMM_WORLD);
- MPI_Bcast(&header.max_val, 1, MPI_INT, 0, MPI_COMM_WORLD);
- // Cada proceso calcula cuántas filas y píxeles locales va a recibir
- int base_rows = header.height / size;
- int remainder = header.height % size;
- int my_rows = base_rows + (rank < remainder ? 1 : 0); // Ej: 512 filas
- int my_pixels_count = my_rows * header.width;
- std::vector<unsigned char> local_data(my_pixels_count);
- // Dispersión: Corta la imagen original en dos franjas horizontales y las inyecta en cada proceso
- MPI_Scatterv(full_image.data(), send_counts.data(), displacements.data(), MPI_UNSIGNED_CHAR,
- local_data.data(), my_pixels_count, MPI_UNSIGNED_CHAR, 0, MPI_COMM_WORLD);
- // 3. CREACIÓN DE LAS FILAS FANTASMA (GHOST ZONES)
- // Reservamos espacio local expandido: agregamos +2 filas (el ancho por 2)
- std::vector<unsigned char> local_buffer((my_rows + 2) * header.width, 0);
- // Copiamos nuestros datos reales justo en el medio, salteando la primera fila extra
- std::copy(local_data.begin(), local_data.end(), local_buffer.begin() + header.width);
- // Identificamos quiénes son nuestros vecinos en la topología lógica por filas
- int vec_sup = (rank == 0) ? MPI_PROC_NULL : rank - 1;
- int vec_inf = (rank == size - 1) ? MPI_PROC_NULL : rank + 1;
- // Intercambio de Fronteras: Comunicación síncrona punto a punto bidireccional
- // Envía la primera fila real al vecino superior y recibe la fila fantasma inferior del vecino superior
- MPI_Sendrecv(&local_buffer[header.width], header.width, MPI_UNSIGNED_CHAR, vec_sup, 0,
- &local_buffer[(my_rows + 1) * header.width], header.width, MPI_UNSIGNED_CHAR, vec_inf, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
- // Envía la última fila real al vecino inferior y recibe la fila fantasma superior del vecino inferior
- MPI_Sendrecv(&local_buffer[my_rows * header.width], header.width, MPI_UNSIGNED_CHAR, vec_inf, 1,
- &local_buffer[0], header.width, MPI_UNSIGNED_CHAR, vec_sup, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
- // Si somos bordes absolutos de la imagen física, duplicamos nuestra propia frontera para el filtro 3x3
- if (rank == 0) std::copy(local_buffer.begin() + header.width, local_buffer.begin() + 2 * header.width, local_buffer.begin());
- if (rank == size - 1) std::copy(local_buffer.begin() + my_rows * header.width, local_buffer.begin() + (my_rows + 1) * header.width, local_buffer.begin() + (my_rows + 1) * header.width);
- std::vector<unsigned char> local_result(my_pixels_count, 0);
- // ========================================================
- // CRONÓMETRO DE ALTA PRECISION MPI
- // ========================================================
- double t_inicio = MPI_Wtime();
- if (filtro == 1) { // === ALGORITMO DE EROSIÓN PARALELA ===
- for (int i = 1; i <= my_rows; ++i) { // Recorre nuestras filas válidas
- for (int j = 0; j < header.width; ++j) { // Recorre las columnas
- unsigned char min_val = 255;
- // Ventana de vecindad 3x3 (kx, ky toman valores -1, 0, 1)
- for (int ky = -1; ky <= 1; ++ky) {
- for (int kx = -1; kx <= 1; ++kx) {
- int n_col = j + kx;
- if (n_col >= 0 && n_col < header.width) {
- // Mapeo bidimensional indexado en vector unidimensional
- unsigned char p = local_buffer[(i + ky) * header.width + n_col];
- if (p < min_val) min_val = p; // Guarda el mínimo local
- }
- }
- }
- local_result[(i - 1) * header.width + j] = min_val; // Guarda en la matriz de salida limpia
- }
- }
- }
- else if (filtro == 2) { // === ALGORITMO DE DILATACIÓN PARALELA ===
- for (int i = 1; i <= my_rows; ++i) {
- for (int j = 0; j < header.width; ++j) {
- unsigned char max_val = 0;
- for (int ky = -1; ky <= 1; ++ky) {
- for (int kx = -1; kx <= 1; ++kx) {
- int n_col = j + kx;
- if (n_col >= 0 && n_col < header.width) {
- unsigned char p = local_buffer[(i + ky) * header.width + n_col];
- if (p > max_val) max_val = p; // Guarda el máximo local
- }
- }
- }
- local_result[(i - 1) * header.width + j] = max_val;
- }
- }
- }
- else if (filtro == 3) { // === ALGORITMO CAD: DETECCIÓN DE NÓDULOS (TOP-HAT) ===
- std::vector<unsigned char> eroded(my_pixels_count, 255);
- // Fase A: Primero aplicamos Erosión local
- for (int i = 1; i <= my_rows; ++i) {
- for (int j = 0; j < header.width; ++j) {
- unsigned char min_val = 255;
- for (int ky = -1; ky <= 1; ++ky) {
- for (int kx = -1; kx <= 1; ++kx) {
- int n_col = j + kx;
- if (n_col >= 0 && n_col < header.width) {
- unsigned char p = local_buffer[(i + ky) * header.width + n_col];
- if (p < min_val) min_val = p;
- }
- }
- }
- eroded[(i - 1) * header.width + j] = min_val;
- }
- }
- // Re-sincronización intermedia: Prepara el buffer local con la imagen erosionada para dilatarla
- std::copy(eroded.begin(), eroded.end(), local_buffer.begin() + header.width);
- MPI_Sendrecv(&local_buffer[header.width], header.width, MPI_UNSIGNED_CHAR, vec_sup, 0, &local_buffer[(my_rows + 1) * header.width], header.width, MPI_UNSIGNED_CHAR, vec_inf, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
- MPI_Sendrecv(&local_buffer[my_rows * header.width], header.width, MPI_UNSIGNED_CHAR, vec_inf, 1, &local_buffer[0], header.width, MPI_UNSIGNED_CHAR, vec_sup, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
- // Fase B: Dilatación sobre la erosión (Apertura Morfológica) + Resta Top-Hat
- for (int i = 1; i <= my_rows; ++i) {
- for (int j = 0; j < header.width; ++j) {
- unsigned char max_val = 0;
- for (int ky = -1; ky <= 1; ++ky) {
- for (int kx = -1; kx <= 1; ++kx) {
- int n_col = j + kx;
- if (n_col >= 0 && n_col < header.width) {
- unsigned char p = local_buffer[(i + ky) * header.width + n_col];
- if (p > max_val) max_val = p;
- }
- }
- }
- // Algoritmo Top-Hat: Restamos la Apertura a la imagen original para aislar picos de brillo
- int diff = max_val - eroded[(i - 1) * header.width + j];
- // Si la anomalía supera el umbral crítico de contraste (>15), la marcamos con blanco puro (255)
- // Si no, atenuamos el tejido normal multiplicándolo por 0.4 para aumentar el contraste visual
- local_result[(i - 1) * header.width + j] = (diff > 15) ? 255 : (local_data[(i - 1) * header.width + j] * 0.4);
- }
- }
- }
- // ========================================================
- // ¡FIN DEL CÓMPUTO Y EVALUACIÓN REPETITIVA DE RENDIMIENTO!
- // ========================================================
- double t_fin = MPI_Wtime();
- double t_local = t_fin - t_inicio;
- double t_maximo = 0;
- // Operación colectiva de reducción: Busca el proceso que más tardó (Métrica HPC real)
- MPI_Reduce(&t_local, &t_maximo, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);
- std::vector<unsigned char> final_image;
- if (rank == 0) final_image.resize(header.width * header.height); // Búfer de recolección en el Maestro
- // Recolección ordenada: Junta las mitades procesadas eliminando las filas fantasmas
- MPI_Gatherv(local_result.data(), my_pixels_count, MPI_UNSIGNED_CHAR,
- final_image.data(), send_counts.data(), displacements.data(), MPI_UNSIGNED_CHAR, 0, MPI_COMM_WORLD);
- if (rank == 0) { // Solo el Maestro ejecuta la salida y la renderización visual
- std::cout << "\n>>> [HPC INFO] Tiempo puro de computacion: " << t_maximo << " segundos. <<<" << std::endl;
- // Vector para armar la salida RGB (Ancho * Alto * 3 canales: Rojo, Verde, Azul)
- std::vector<unsigned char> rgb_output(header.width * header.height * 3);
- for (int i = 0; i < header.width * header.height; ++i) {
- // Si el filtro es CAD y el píxel fue marcado como anomalía (255), lo pintamos de rojo puro
- if (filtro == 3 && final_image[i] == 255) {
- rgb_output[i * 3] = 255; // Canal R (Rojo al máximo)
- rgb_output[i * 3 + 1] = 0; // Canal G (Verde apagado)
- rgb_output[i * 3 + 2] = 0; // Canal B (Azul apagado)
- } else {
- // Si es tejido normal o filtros estandares, mantenemos la escala de grises (R=G=B)
- unsigned char val = final_image[i];
- rgb_output[i * 3] = val; rgb_output[i * 3 + 1] = val; rgb_output[i * 3 + 2] = val;
- }
- }
- std::string archivo_salida = (filtro == 1) ? "resultado_erosion.ppm" : ((filtro == 2) ? "resultado_dilatacion.ppm" : "resultado_nodulos.ppm");
- writePPM(archivo_salida, header, rgb_output); // Graba la imagen a color en disco
- std::cout << "[HPC] Procesamiento listo. Creando imagen comparativa..." << std::endl;
- // Automatización mediante shell: Usa 'montage' de ImageMagick para pegar las dos imágenes de lado a lado
- // e invoca a 'xdg-open' para desplegarlas automáticamente en el visor del sistema operativo.
- std::string cmd_comparar = "montage -geometry +2+2 -resize x500 " + nombre_archivo + " " + archivo_salida + " comparacion.png && xdg-open comparacion.png &";
- std::system(cmd_comparar.c_str());
- }
- } // Fin de la función procesarFiltro
Advertisement
Add Comment
Please, Sign In to add comment