report_generator.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. """
  2. Report Generator
  3. Orchestrates report generation by combining section builders and visualizations.
  4. """
  5. from typing import Dict, Optional
  6. from datetime import datetime
  7. from PyQt5.QtWidgets import QVBoxLayout
  8. from PyQt5.QtGui import QImage
  9. from ui.components.report_sections import (
  10. create_report_info_section,
  11. create_empty_results_section,
  12. create_model_results_section,
  13. create_analysis_results_section,
  14. create_input_data_section,
  15. create_input_data_with_gradcam,
  16. create_visualizations_section,
  17. get_local_datetime_string
  18. )
  19. def generate_basic_report(
  20. layout: QVBoxLayout,
  21. input_data: Dict[str, str]
  22. ) -> Dict:
  23. """
  24. Generate a basic analysis report without model results.
  25. Args:
  26. layout: QVBoxLayout to add sections to
  27. input_data: Dictionary with input file paths
  28. Returns:
  29. Dictionary with current grade and description
  30. """
  31. # Report Info Section
  32. info_group = create_report_info_section()
  33. layout.addWidget(info_group)
  34. # Analysis Results Section (Empty)
  35. results_group = create_empty_results_section()
  36. layout.addWidget(results_group)
  37. # Input Data Section
  38. data_group = create_input_data_section(input_data)
  39. layout.addWidget(data_group)
  40. return {
  41. 'grade': 'B',
  42. 'description': 'Basic report generated without model analysis'
  43. }
  44. def generate_model_report(
  45. layout: QVBoxLayout,
  46. input_data: Dict[str, str],
  47. gradcam_image: QImage,
  48. predicted_class: str,
  49. confidence: float,
  50. probabilities: Dict[str, float]
  51. ) -> Dict:
  52. """
  53. Generate analysis report with actual multispectral model results.
  54. Args:
  55. layout: QVBoxLayout to add sections to
  56. input_data: Dictionary with input file paths
  57. gradcam_image: QImage of the Grad-CAM visualization
  58. predicted_class: Predicted maturity class from model
  59. confidence: Model confidence (0-1 scale)
  60. probabilities: Dictionary of class probabilities
  61. Returns:
  62. Dictionary with current grade and description
  63. """
  64. # Report Info Section
  65. info_group = create_report_info_section()
  66. layout.addWidget(info_group)
  67. # Analysis Results Section (WITH REAL MODEL DATA)
  68. results_group = create_model_results_section(
  69. predicted_class,
  70. confidence,
  71. probabilities
  72. )
  73. layout.addWidget(results_group)
  74. # Input Data Section (WITH GRADCAM VISUALIZATION)
  75. data_group = create_input_data_with_gradcam(input_data, gradcam_image)
  76. layout.addWidget(data_group)
  77. # Determine grade
  78. grade_map = {
  79. 'Immature': 'C',
  80. 'Mature': 'A',
  81. 'Overmature': 'B'
  82. }
  83. grade = grade_map.get(predicted_class, 'B')
  84. return {
  85. 'grade': grade,
  86. 'description': f'Model prediction: {predicted_class} ({confidence*100:.1f}% confidence)'
  87. }
  88. def generate_comprehensive_report(
  89. layout: QVBoxLayout,
  90. input_data: Dict[str, str],
  91. results: Dict,
  92. report_id: Optional[str] = None
  93. ) -> Dict:
  94. """
  95. Generate comprehensive report with RGB models and multispectral analysis.
  96. Args:
  97. layout: QVBoxLayout to add sections to
  98. input_data: Dictionary with input file paths
  99. results: Dictionary with processing results from all models
  100. Keys: 'defect', 'locule', 'maturity', 'shape', 'audio'
  101. report_id: Optional report ID to display
  102. Returns:
  103. Dictionary with current grade and description
  104. """
  105. # Report Info Section
  106. info_group = create_report_info_section(report_id)
  107. layout.addWidget(info_group)
  108. # Combined Analysis Results Section
  109. results_group = create_analysis_results_section(results)
  110. layout.addWidget(results_group)
  111. # Extract grade information from results group
  112. # (Grade is calculated within create_analysis_results_section)
  113. locule_count = 0
  114. has_defects = False
  115. shape_class = None
  116. maturity_class = None
  117. if 'locule' in results and not results['locule'].get('error'):
  118. locule_count = results['locule'].get('locule_count', 0)
  119. if 'defect' in results and not results['defect'].get('error'):
  120. primary_class = results['defect'].get('primary_class', 'Unknown')
  121. has_defects = (primary_class != "No Defects")
  122. if 'shape' in results and not results['shape'].get('error'):
  123. shape_class = results['shape'].get('shape_class', 'Unknown')
  124. if 'maturity' in results and not results['maturity'].get('error'):
  125. maturity_class = results['maturity'].get('class_name', 'Unknown')
  126. # Import here to avoid circular import
  127. from utils.grade_calculator import calculate_durian_grade
  128. grade, grade_description = calculate_durian_grade(locule_count, has_defects, shape_class, maturity_class)
  129. # Input Data with Visualizations Section
  130. data_group = create_visualizations_section(input_data, results)
  131. layout.addWidget(data_group)
  132. return {
  133. 'grade': grade,
  134. 'description': grade_description
  135. }
  136. def extract_report_content(
  137. report_id: str,
  138. grade: str,
  139. grade_description: str,
  140. results: Optional[Dict] = None
  141. ) -> Dict:
  142. """
  143. Extract report content for export/print.
  144. Args:
  145. report_id: Report ID
  146. grade: Overall grade letter (A, B, or C)
  147. grade_description: Grade description text
  148. results: Optional results dictionary from models
  149. Returns:
  150. Dictionary with all report text content
  151. """
  152. content = {
  153. 'report_id': report_id or f"DUR-{datetime.now().strftime('%Y%m%d-%H%M%S')}",
  154. 'generated': get_local_datetime_string(),
  155. 'grade': grade,
  156. 'grade_description': grade_description,
  157. 'results': {}
  158. }
  159. if results:
  160. # Locule analysis
  161. if 'locule' in results and not results['locule'].get('error'):
  162. content['results']['locule_count'] = results['locule'].get('locule_count', 0)
  163. # Defect analysis
  164. if 'defect' in results and not results['defect'].get('error'):
  165. content['results']['defect_status'] = results['defect'].get('primary_class', 'Unknown')
  166. content['results']['total_detections'] = results['defect'].get('total_detections', 0)
  167. # Shape analysis
  168. if 'shape' in results and not results['shape'].get('error'):
  169. content['results']['shape_class'] = results['shape'].get('shape_class', 'Unknown')
  170. content['results']['shape_confidence'] = results['shape'].get('confidence', 0)
  171. # Maturity analysis
  172. if 'maturity' in results and not results['maturity'].get('error'):
  173. content['results']['maturity_class'] = results['maturity'].get('class_name', 'Unknown')
  174. content['results']['maturity_confidence'] = results['maturity'].get('confidence', 0)
  175. # Audio ripeness analysis
  176. if 'audio' in results and not results['audio'].get('error'):
  177. content['results']['ripeness_class'] = results['audio'].get('ripeness_class', 'Unknown')
  178. content['results']['ripeness_confidence'] = results['audio'].get('confidence', 0)
  179. content['results']['knock_count'] = results['audio'].get('knock_count', 0)
  180. return content