AmbarG

paralelizacion

Jul 7th, 2026
41
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 17.60 KB | None | 0 0
  1. #include <mpi.h>       // Biblioteca central de OpenMPI para paralelismo
  2. #include <iostream>    // Para usar std::cout y mostrar texto en consola
  3. #include <vector>      // Para usar vectores dinámicos (búferes de memoria contigua)
  4. #include <fstream>     // Para abrir y leer/escribir archivos binarios (ifstream/ofstream)
  5. #include <string>      // Para manipular cadenas de texto (nombres de archivos)
  6. #include <algorithm>   // Para operaciones de copiado rápido (std::copy)
  7. #include <cstdlib>     // Para ejecutar comandos de Linux (std::system)
  8.  
  9. // Estructura para almacenar los metadatos de la mamografía
  10. struct PGMHeader {
  11.     int width = 0;      // Ancho de la imagen (ej. 1024)
  12.     int height = 0;     // Alto de la imagen (ej. 1024)
  13.     int max_val = 0;    // Valor máximo de brillo (típicamente 255)
  14. };
  15.  
  16. // Función para cargar la imagen PGM desde el disco a la memoria RAM
  17. bool readPGM(const std::string& filename, PGMHeader& header, std::vector<unsigned char>& data) {
  18.     std::ifstream file(filename, std::ios::binary);
  19.     if (!file.is_open()) return false;
  20.    
  21.     std::string type; file >> type;
  22.     if (type != "P5") return false; // Valida que sea PGM Binario (P5)
  23.    
  24.     char ch; file >> ch;
  25.     // Este bucle saltea los comentarios que suelen tener las imágenes médicas (#...)
  26.     while (ch == '#') { std::string comment; std::getline(file, comment); file >> ch; }
  27.     file.putback(ch);
  28.    
  29.     file >> header.width >> header.height >> header.max_val;
  30.     file.ignore(); // Saltea el espacio en blanco antes de los píxeles puros
  31.    
  32.     // Redimensiona el vector al tamaño justo (1024 * 1024 = 1.048.576 bytes)
  33.     data.resize(header.width * header.height);
  34.     file.read(reinterpret_cast<char*>(data.data()), data.size()); // Lee los píxeles de un solo golpe
  35.     return file.good();
  36. }
  37.  
  38. // Función para guardar el resultado en PPM (P6) que admite canales RGB (Color)
  39. bool writePPM(const std::string& filename, const PGMHeader& header, const std::vector<unsigned char>& data_rgb) {
  40.     std::ofstream file(filename, std::ios::binary);
  41.     if (!file.is_open()) return false;
  42.     file << "P6\n" << header.width << " " << header.height << "\n255\n"; // Cabecera PPM
  43.     file.write(reinterpret_cast<const char*>(data_rgb.data()), data_rgb.size()); // Graba los píxeles a color
  44.     return file.good();
  45. }
  46.  
  47. int main(int argc, char** argv) {
  48.     int prop;
  49.     // Inicializa el entorno MPI con soporte para hilos simples
  50.     MPI_Init_thread(&argc, &argv, MPI_THREAD_SINGLE, &prop);
  51.    
  52.     int rank, size;
  53.     MPI_Comm_rank(MPI_COMM_WORLD, &rank); // Obtiene el ID del proceso (0 para Maestro, 1 para Esclavo)
  54.     MPI_Comm_size(MPI_COMM_WORLD, &size); // Obtiene el total de procesos del clúster (2 en nuestro caso)
  55.  
  56.     std::string archivo_objetivo = "mamografia.pgm"; // Nombre interno temporal
  57.  
  58.     if (rank == 0) { // === ROL DEL PROCESO MAESTRO ===
  59.         int opcion_principal = 0;
  60.         do {
  61.             std::cout << "\n=======================================================" << std::endl;
  62.             std::cout << "  SISTEMA DE PROCESAMIENTO PARALELO DE IMAGENES MEDICAS " << std::endl;
  63.             std::cout << "=======================================================" << std::endl;
  64.             std::cout << "1. Cargar/Subir imagen desde carpeta 'Downloads'" << std::endl;
  65.             std::cout << "2. Salir" << std::endl;
  66.             std::cout << "Seleccione una opcion: "; std::cin >> opcion_principal;
  67.  
  68.             // Sincronización por Difusión: El Maestro le avisa al Esclavo qué opción se eligió
  69.             MPI_Bcast(&opcion_principal, 1, MPI_INT, 0, MPI_COMM_WORLD);
  70.  
  71.             if (opcion_principal == 1) {
  72.                 std::string nombre_archivo;
  73.                 std::cout << "Ingrese el nombre del archivo en Downloads: "; std::cin >> nombre_archivo;
  74.  
  75.                 // Comando de Linux que copia la imagen real del usuario a la carpeta del programa
  76.                 std::string comando_mover = "cp ~/Downloads/" + nombre_archivo + " ./" + archivo_objetivo + " 2>/dev/null";
  77.                 std::system(comando_mover.c_str());
  78.  
  79.                 std::ifstream test(archivo_objetivo);
  80.                 if (!test.good()) {
  81.                     std::cout << "Error: No se encontro el archivo." << std::endl;
  82.                     int flag_error = -1;
  83.                     MPI_Bcast(&flag_error, 1, MPI_INT, 0, MPI_COMM_WORLD); // Cancela al Esclavo si hay error
  84.                     continue;
  85.                 }
  86.                 test.close();
  87.                 std::cout << "¡Imagen cargada con exito al sistema!" << std::endl;
  88.  
  89.                 int opcion_filtro = 0;
  90.                 do {
  91.                     std::cout << "\n--- SUBMENU: FILTROS MORFOLOGICOS DISPONIBLES ---" << std::endl;
  92.                     std::cout << "1. Aplicar Erosion Morfologica" << std::endl;
  93.                     std::cout << "2. Aplicar Dilatacion Morfologica" << std::endl;
  94.                     std::cout << "3. Detección de Nódulos (Marcado Color Rojo)" << std::endl;
  95.                     std::cout << "4. Volver al menu principal" << std::endl;
  96.                     std::cout << "Seleccione un filtro: "; std::cin >> opcion_filtro;
  97.  
  98.                     // El Maestro le avisa al Esclavo qué filtro va a correr para que salten juntos
  99.                     MPI_Bcast(&opcion_filtro, 1, MPI_INT, 0, MPI_COMM_WORLD);
  100.  
  101.                     if (opcion_filtro >= 1 && opcion_filtro <= 3) {
  102.                         procesarFiltro(opcion_filtro, rank, size, archivo_objetivo);
  103.                     }
  104.                 } while (opcion_filtro != 4);
  105.             }
  106.         } while (opcion_principal != 2);
  107.     }
  108.     else { // === ROL DEL PROCESO ESCLAVO (RANK 1) ===
  109.         int opcion_principal = 0;
  110.         do {
  111.             // El Esclavo se bloquea esperando recibir la orden del Maestro vía Bcast
  112.             MPI_Bcast(&opcion_principal, 1, MPI_INT, 0, MPI_COMM_WORLD);
  113.             if (opcion_principal == 1) {
  114.                 int opcion_filtro = 0;
  115.                 do {
  116.                     MPI_Bcast(&opcion_filtro, 1, MPI_INT, 0, MPI_COMM_WORLD);
  117.                     if (opcion_filtro == -1) break; // Si hubo error de archivo, vuelve a esperar
  118.                     if (opcion_filtro >= 1 && opcion_filtro <= 3) {
  119.                         procesarFiltro(opcion_filtro, rank, size, archivo_objetivo);
  120.                     }
  121.                 } while (opcion_filtro != 4);
  122.             }
  123.         } while (opcion_principal != 2);
  124.     }
  125.  
  126.     MPI_Finalize(); // Apaga el entorno MPI de forma limpia
  127.     return 0;
  128. }
  129.  
  130. void procesarFiltro(int filtro, int rank, int size, const std::string& nombre_archivo) {
  131.     PGMHeader header;
  132.     std::vector<unsigned char> full_image;
  133.     std::vector<int> send_counts(size, 0), displacements(size, 0);
  134.  
  135.     // 1. Solo el Maestro lee físicamente el archivo del disco
  136.     if (rank == 0) {
  137.         readPGM(nombre_archivo, header, full_image);
  138.         int base_rows = header.height / size;
  139.         int remainder = header.height % size;
  140.         int current_disp = 0;
  141.         // Calcula matemáticamente cuántos píxeles le tocan a cada proceso (Balance de carga)
  142.         for (int i = 0; i < size; ++i) {
  143.             int rows_for_proc = base_rows + (i < remainder ? 1 : 0);
  144.             send_counts[i] = rows_for_proc * header.width;
  145.             displacements[i] = current_disp;
  146.             current_disp += send_counts[i];
  147.         }
  148.     }
  149.  
  150.     // 2. Transmisión de metadatos de cabecera a todos los procesos
  151.     MPI_Bcast(&header.width, 1, MPI_INT, 0, MPI_COMM_WORLD);
  152.     MPI_Bcast(&header.height, 1, MPI_INT, 0, MPI_COMM_WORLD);
  153.     MPI_Bcast(&header.max_val, 1, MPI_INT, 0, MPI_COMM_WORLD);
  154.  
  155.     // Cada proceso calcula cuántas filas y píxeles locales va a recibir
  156.     int base_rows = header.height / size;
  157.     int remainder = header.height % size;
  158.     int my_rows = base_rows + (rank < remainder ? 1 : 0); // Ej: 512 filas
  159.     int my_pixels_count = my_rows * header.width;
  160.  
  161.     std::vector<unsigned char> local_data(my_pixels_count);
  162.    
  163.     // Dispersión: Corta la imagen original en dos franjas horizontales y las inyecta en cada proceso
  164.     MPI_Scatterv(full_image.data(), send_counts.data(), displacements.data(), MPI_UNSIGNED_CHAR,
  165.                  local_data.data(), my_pixels_count, MPI_UNSIGNED_CHAR, 0, MPI_COMM_WORLD);
  166.  
  167.     // 3. CREACIÓN DE LAS FILAS FANTASMA (GHOST ZONES)
  168.     // Reservamos espacio local expandido: agregamos +2 filas (el ancho por 2)
  169.     std::vector<unsigned char> local_buffer((my_rows + 2) * header.width, 0);
  170.     // Copiamos nuestros datos reales justo en el medio, salteando la primera fila extra
  171.     std::copy(local_data.begin(), local_data.end(), local_buffer.begin() + header.width);
  172.  
  173.     // Identificamos quiénes son nuestros vecinos en la topología lógica por filas
  174.     int vec_sup = (rank == 0) ? MPI_PROC_NULL : rank - 1;
  175.     int vec_inf = (rank == size - 1) ? MPI_PROC_NULL : rank + 1;
  176.  
  177.     // Intercambio de Fronteras: Comunicación síncrona punto a punto bidireccional
  178.     // Envía la primera fila real al vecino superior y recibe la fila fantasma inferior del vecino superior
  179.     MPI_Sendrecv(&local_buffer[header.width], header.width, MPI_UNSIGNED_CHAR, vec_sup, 0,
  180.                  &local_buffer[(my_rows + 1) * header.width], header.width, MPI_UNSIGNED_CHAR, vec_inf, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
  181.     // Envía la última fila real al vecino inferior y recibe la fila fantasma superior del vecino inferior
  182.     MPI_Sendrecv(&local_buffer[my_rows * header.width], header.width, MPI_UNSIGNED_CHAR, vec_inf, 1,
  183.                  &local_buffer[0], header.width, MPI_UNSIGNED_CHAR, vec_sup, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
  184.  
  185.     // Si somos bordes absolutos de la imagen física, duplicamos nuestra propia frontera para el filtro 3x3
  186.     if (rank == 0) std::copy(local_buffer.begin() + header.width, local_buffer.begin() + 2 * header.width, local_buffer.begin());
  187.     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);
  188.  
  189.  
  190. std::vector<unsigned char> local_result(my_pixels_count, 0);
  191.  
  192.     // ========================================================
  193.     // CRONÓMETRO DE ALTA PRECISION MPI
  194.     // ========================================================
  195.     double t_inicio = MPI_Wtime();
  196.  
  197.     if (filtro == 1) { // === ALGORITMO DE EROSIÓN PARALELA ===
  198.         for (int i = 1; i <= my_rows; ++i) { // Recorre nuestras filas válidas
  199.             for (int j = 0; j < header.width; ++j) { // Recorre las columnas
  200.                 unsigned char min_val = 255;
  201.                 // Ventana de vecindad 3x3 (kx, ky toman valores -1, 0, 1)
  202.                 for (int ky = -1; ky <= 1; ++ky) {
  203.                     for (int kx = -1; kx <= 1; ++kx) {
  204.                         int n_col = j + kx;
  205.                         if (n_col >= 0 && n_col < header.width) {
  206.                             // Mapeo bidimensional indexado en vector unidimensional
  207.                             unsigned char p = local_buffer[(i + ky) * header.width + n_col];
  208.                             if (p < min_val) min_val = p; // Guarda el mínimo local
  209.                         }
  210.                     }
  211.                 }
  212.                 local_result[(i - 1) * header.width + j] = min_val; // Guarda en la matriz de salida limpia
  213.             }
  214.         }
  215.     }
  216.     else if (filtro == 2) { // === ALGORITMO DE DILATACIÓN PARALELA ===
  217.         for (int i = 1; i <= my_rows; ++i) {
  218.             for (int j = 0; j < header.width; ++j) {
  219.                 unsigned char max_val = 0;
  220.                 for (int ky = -1; ky <= 1; ++ky) {
  221.                     for (int kx = -1; kx <= 1; ++kx) {
  222.                         int n_col = j + kx;
  223.                         if (n_col >= 0 && n_col < header.width) {
  224.                             unsigned char p = local_buffer[(i + ky) * header.width + n_col];
  225.                             if (p > max_val) max_val = p; // Guarda el máximo local
  226.                         }
  227.                     }
  228.                 }
  229.                 local_result[(i - 1) * header.width + j] = max_val;
  230.             }
  231.         }
  232.     }
  233.     else if (filtro == 3) { // === ALGORITMO CAD: DETECCIÓN DE NÓDULOS (TOP-HAT) ===
  234.         std::vector<unsigned char> eroded(my_pixels_count, 255);
  235.         // Fase A: Primero aplicamos Erosión local
  236.         for (int i = 1; i <= my_rows; ++i) {
  237.             for (int j = 0; j < header.width; ++j) {
  238.                 unsigned char min_val = 255;
  239.                 for (int ky = -1; ky <= 1; ++ky) {
  240.                     for (int kx = -1; kx <= 1; ++kx) {
  241.                         int n_col = j + kx;
  242.                         if (n_col >= 0 && n_col < header.width) {
  243.                             unsigned char p = local_buffer[(i + ky) * header.width + n_col];
  244.                             if (p < min_val) min_val = p;
  245.                         }
  246.                     }
  247.                 }
  248.                 eroded[(i - 1) * header.width + j] = min_val;
  249.             }
  250.         }
  251.  
  252.         // Re-sincronización intermedia: Prepara el buffer local con la imagen erosionada para dilatarla
  253.         std::copy(eroded.begin(), eroded.end(), local_buffer.begin() + header.width);
  254.         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);
  255.         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);
  256.  
  257.         // Fase B: Dilatación sobre la erosión (Apertura Morfológica) + Resta Top-Hat
  258.         for (int i = 1; i <= my_rows; ++i) {
  259.             for (int j = 0; j < header.width; ++j) {
  260.                 unsigned char max_val = 0;
  261.                 for (int ky = -1; ky <= 1; ++ky) {
  262.                     for (int kx = -1; kx <= 1; ++kx) {
  263.                         int n_col = j + kx;
  264.                         if (n_col >= 0 && n_col < header.width) {
  265.                             unsigned char p = local_buffer[(i + ky) * header.width + n_col];
  266.                             if (p > max_val) max_val = p;
  267.                         }
  268.                     }
  269.                 }
  270.                 // Algoritmo Top-Hat: Restamos la Apertura a la imagen original para aislar picos de brillo
  271.                 int diff = max_val - eroded[(i - 1) * header.width + j];
  272.                
  273.                 // Si la anomalía supera el umbral crítico de contraste (>15), la marcamos con blanco puro (255)
  274.                 // Si no, atenuamos el tejido normal multiplicándolo por 0.4 para aumentar el contraste visual
  275.                 local_result[(i - 1) * header.width + j] = (diff > 15) ? 255 : (local_data[(i - 1) * header.width + j] * 0.4);
  276.             }
  277.         }
  278.     }
  279.  
  280.     // ========================================================
  281.     // ¡FIN DEL CÓMPUTO Y EVALUACIÓN REPETITIVA DE RENDIMIENTO!
  282.     // ========================================================
  283.     double t_fin = MPI_Wtime();
  284.     double t_local = t_fin - t_inicio;
  285.     double t_maximo = 0;
  286.  
  287.     // Operación colectiva de reducción: Busca el proceso que más tardó (Métrica HPC real)
  288.     MPI_Reduce(&t_local, &t_maximo, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);
  289.  
  290. std::vector<unsigned char> final_image;
  291.     if (rank == 0) final_image.resize(header.width * header.height); // Búfer de recolección en el Maestro
  292.  
  293.     // Recolección ordenada: Junta las mitades procesadas eliminando las filas fantasmas
  294.     MPI_Gatherv(local_result.data(), my_pixels_count, MPI_UNSIGNED_CHAR,
  295.                 final_image.data(), send_counts.data(), displacements.data(), MPI_UNSIGNED_CHAR, 0, MPI_COMM_WORLD);
  296.  
  297.     if (rank == 0) { // Solo el Maestro ejecuta la salida y la renderización visual
  298.         std::cout << "\n>>> [HPC INFO] Tiempo puro de computacion: " << t_maximo << " segundos. <<<" << std::endl;
  299.  
  300.         // Vector para armar la salida RGB (Ancho * Alto * 3 canales: Rojo, Verde, Azul)
  301.         std::vector<unsigned char> rgb_output(header.width * header.height * 3);
  302.         for (int i = 0; i < header.width * header.height; ++i) {
  303.             // Si el filtro es CAD y el píxel fue marcado como anomalía (255), lo pintamos de rojo puro
  304.             if (filtro == 3 && final_image[i] == 255) {
  305.                 rgb_output[i * 3] = 255;     // Canal R (Rojo al máximo)
  306.                 rgb_output[i * 3 + 1] = 0;   // Canal G (Verde apagado)
  307.                 rgb_output[i * 3 + 2] = 0;   // Canal B (Azul apagado)
  308.             } else {
  309.                 // Si es tejido normal o filtros estandares, mantenemos la escala de grises (R=G=B)
  310.                 unsigned char val = final_image[i];
  311.                 rgb_output[i * 3] = val; rgb_output[i * 3 + 1] = val; rgb_output[i * 3 + 2] = val;
  312.             }
  313.         }
  314.  
  315.         std::string archivo_salida = (filtro == 1) ? "resultado_erosion.ppm" : ((filtro == 2) ? "resultado_dilatacion.ppm" : "resultado_nodulos.ppm");
  316.         writePPM(archivo_salida, header, rgb_output); // Graba la imagen a color en disco
  317.  
  318.         std::cout << "[HPC] Procesamiento listo. Creando imagen comparativa..." << std::endl;
  319.        
  320.         // Automatización mediante shell: Usa 'montage' de ImageMagick para pegar las dos imágenes de lado a lado
  321.         // e invoca a 'xdg-open' para desplegarlas automáticamente en el visor del sistema operativo.
  322.         std::string cmd_comparar = "montage -geometry +2+2 -resize x500 " + nombre_archivo + " " + archivo_salida + " comparacion.png && xdg-open comparacion.png &";
  323.         std::system(cmd_comparar.c_str());
  324.     }
  325. } // Fin de la función procesarFiltro
  326.  
  327.  
  328.  
  329.  
Advertisement
Add Comment
Please, Sign In to add comment