hiyata commited on
Commit
ae32958
·
verified ·
1 Parent(s): 021471b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +180 -227
app.py CHANGED
@@ -645,19 +645,36 @@ def compute_gene_statistics(gene_shap: np.ndarray) -> Dict[str, float]:
645
 
646
  def create_simple_genome_diagram(gene_results: List[Dict[str, Any]], genome_length: int) -> Image.Image:
647
  """Create a simple genome diagram using PIL"""
648
- # Validate inputs
649
  if not gene_results or genome_length <= 0:
650
  img = Image.new('RGB', (800, 100), color='white')
651
  draw = ImageDraw.Draw(img)
652
  draw.text((10, 40), "Error: Invalid input data", fill='black')
653
  return img
654
-
655
- # Ensure all gene coordinates are valid integers
 
656
  for gene in gene_results:
657
- gene['start'] = max(0, int(gene['start']))
658
- gene['end'] = min(genome_length, int(gene['end']))
659
- if gene['start'] >= gene['end']:
660
- print(f"Warning: Invalid coordinates for gene {gene['gene_name']}: {gene['start']}-{gene['end']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
661
  # Image dimensions
662
  width = 1500
663
  height = 600
@@ -668,17 +685,16 @@ def create_simple_genome_diagram(gene_results: List[Dict[str, Any]], genome_leng
668
  img = Image.new('RGB', (width, height), 'white')
669
  draw = ImageDraw.Draw(img)
670
 
671
- # Try to load font, fall back to default if unavailable
672
  try:
673
  font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12)
674
  title_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16)
675
  except:
676
- font = None
677
- title_font = None
678
 
679
  # Draw title
680
- draw.text((margin, margin//2), "Genome SHAP Analysis",
681
- fill='black', font=title_font or font)
682
 
683
  # Draw genome line
684
  line_y = height // 2
@@ -689,57 +705,62 @@ def create_simple_genome_diagram(gene_results: List[Dict[str, Any]], genome_leng
689
 
690
  # Draw scale markers
691
  for i in range(0, genome_length + 1, genome_length // 10):
692
- x = int(margin + i * scale)
693
  draw.line([(x, line_y - 5), (x, line_y + 5)], fill='black', width=1)
694
  draw.text((x - 20, line_y + 10), f"{i:,}", fill='black', font=font)
695
 
696
- # Sort genes by absolute SHAP value for drawing
697
- sorted_genes = sorted(gene_results, key=lambda x: abs(x['avg_shap']))
698
 
699
  # Draw genes
700
- for gene in sorted_genes:
701
- # Calculate position and ensure integers
702
- start_x = int(margin + gene['start'] * scale)
703
- end_x = int(margin + gene['end'] * scale)
704
 
705
  # Calculate color based on SHAP value
706
- if gene['avg_shap'] > 0:
707
- intensity = min(255, int(abs(gene['avg_shap'] * 500)))
 
708
  color = (255, 255 - intensity, 255 - intensity) # Red
709
  else:
710
- intensity = min(255, int(abs(gene['avg_shap'] * 500)))
711
  color = (255 - intensity, 255 - intensity, 255) # Blue
712
 
713
- # Draw gene box with integer coordinates
714
- draw.rectangle([
715
- (start_x, int(line_y - track_height // 2)),
716
- (end_x, int(line_y + track_height // 2))
717
- ], fill=color, outline='black')
718
 
719
  # Draw gene name
720
- label = f"{gene['gene_name']}"
721
- label_bbox = draw.textbbox((0, 0), label, font=font)
722
- label_width = label_bbox[2] - label_bbox[0]
 
 
 
 
723
 
724
- # Try to place label, alternating above and below
725
- if sorted_genes.index(gene) % 2 == 0:
726
- text_y = line_y - track_height - 15
727
  else:
728
- text_y = line_y + track_height + 5
729
 
730
- # Draw label with rotation if space is tight
731
  gene_width = end_x - start_x
732
  if gene_width > label_width:
733
  # Horizontal label
734
- text_x = int(start_x + (gene_width - label_width) // 2)
735
- draw.text((text_x, int(text_y)), label, fill='black', font=font)
736
  elif gene_width > 20:
737
- # Create rotated text image
738
- txt_img = Image.new('RGBA', (label_width, 20), (255, 255, 255, 0))
739
  txt_draw = ImageDraw.Draw(txt_img)
740
  txt_draw.text((0, 0), label, font=font, fill='black')
741
  txt_img = txt_img.rotate(90, expand=True)
742
- img.paste(txt_img, (int(start_x), text_y), txt_img)
743
 
744
  # Draw legend
745
  legend_x = margin
@@ -751,213 +772,145 @@ def create_simple_genome_diagram(gene_results: List[Dict[str, Any]], genome_leng
751
  box_height = 20
752
  spacing = 15
753
 
754
- # Strong human-like
755
- draw.rectangle([(legend_x, legend_y - 45, legend_x + box_width, legend_y - 45 + box_height)],
756
- fill=(255, 0, 0), outline='black')
757
- draw.text((legend_x + box_width + spacing, legend_y - 45),
758
- "Strong human-like signal", fill='black', font=font)
759
-
760
- # Weak human-like
761
- draw.rectangle([(legend_x, legend_y - 20, legend_x + box_width, legend_y - 20 + box_height)],
762
- fill=(255, 200, 200), outline='black')
763
- draw.text((legend_x + box_width + spacing, legend_y - 20),
764
- "Weak human-like signal", fill='black', font=font)
765
 
766
- # Weak non-human-like
767
- draw.rectangle([(legend_x + 250, legend_y - 45, legend_x + 250 + box_width, legend_y - 45 + box_height)],
768
- fill=(200, 200, 255), outline='black')
769
- draw.text((legend_x + 250 + box_width + spacing, legend_y - 45),
770
- "Weak non-human-like signal", fill='black', font=font)
771
-
772
- # Strong non-human-like
773
- draw.rectangle([(legend_x + 250, legend_y - 20, legend_x + 250 + box_width, legend_y - 20 + box_height)],
774
- fill=(0, 0, 255), outline='black')
775
- draw.text((legend_x + 250 + box_width + spacing, legend_y - 20),
776
- "Strong non-human-like signal", fill='black', font=font)
777
 
778
  return img
779
 
780
- def create_simple_genome_diagram(gene_results: List[Dict[str, Any]], genome_length: int) -> Image.Image:
781
- """Create a simple genome diagram using PIL with proper coordinate handling"""
782
- # Validate inputs and ensure genome_length is an integer
783
- if not gene_results or not isinstance(genome_length, int) or genome_length <= 0:
784
- img = Image.new('RGB', (800, 100), color='white')
785
- draw = ImageDraw.Draw(img)
786
- draw.text((10, 40), "Error: Invalid input data", fill='black')
787
- return img
 
 
 
 
788
 
789
- # Pre-process gene coordinates and handle type conversion
790
- processed_genes = []
791
- for gene in gene_results:
792
- try:
793
- # Ensure start and end are integers
794
- start = int(float(gene['start']))
795
- end = int(float(gene['end']))
 
 
796
 
797
- # Validate coordinates
798
- if start < 0 or end > genome_length or start >= end:
799
- print(f"Warning: Skipping gene {gene.get('gene_name', 'unknown')} due to invalid coordinates: {start}-{end}")
 
 
 
800
  continue
801
 
802
- processed_gene = gene.copy()
803
- processed_gene['start'] = start
804
- processed_gene['end'] = end
805
- processed_genes.append(processed_gene)
806
- except (ValueError, TypeError) as e:
807
- print(f"Warning: Error processing gene coordinates: {str(e)}")
808
- continue
809
-
810
- if not processed_genes:
811
- img = Image.new('RGB', (800, 100), color='white')
812
- draw = ImageDraw.Draw(img)
813
- draw.text((10, 40), "Error: No valid genes to display", fill='black')
814
- return img
815
-
816
- # Image dimensions
817
- width = 1500
818
- height = 600
819
- margin = 50
820
- track_height = 40
821
-
822
- # Create image with white background
823
- img = Image.new('RGB', (width, height), 'white')
824
- draw = ImageDraw.Draw(img)
825
-
826
- # Try to load font, fall back to default if unavailable
827
- try:
828
- font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12)
829
- title_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16)
830
- except:
831
- font = None
832
- title_font = None
833
-
834
- # Draw title
835
- draw.text((margin, margin//2), "Genome SHAP Analysis",
836
- fill='black', font=title_font or font)
837
-
838
- # Draw genome line
839
- line_y = height // 2
840
- line_coords = [(margin, line_y), (width - margin, line_y)]
841
- draw.line(line_coords, fill='black', width=2)
842
-
843
- # Calculate scale factor
844
- scale = float(width - 2 * margin) / float(genome_length)
845
-
846
- # Draw scale markers
847
- for i in range(0, genome_length + 1, max(1, genome_length // 10)):
848
- x = int(margin + i * scale)
849
- marker_coords = [(x, line_y - 5), (x, line_y + 5)]
850
- draw.line(marker_coords, fill='black', width=1)
851
- draw.text((x - 20, line_y + 10), f"{i:,}", fill='black', font=font)
852
-
853
- # Sort genes by absolute SHAP value for drawing
854
- sorted_genes = sorted(processed_genes, key=lambda x: abs(float(x.get('avg_shap', 0))))
855
-
856
- # Draw genes
857
- for gene in sorted_genes:
858
- try:
859
- # Calculate position
860
- start_x = int(margin + gene['start'] * scale)
861
- end_x = int(margin + gene['end'] * scale)
862
-
863
- # Ensure minimum visible width
864
- if end_x - start_x < 2:
865
- end_x = start_x + 2
866
-
867
- # Calculate color based on SHAP value
868
- avg_shap = float(gene.get('avg_shap', 0))
869
- if avg_shap > 0:
870
- intensity = min(255, int(abs(avg_shap * 500)))
871
- color = (255, 255 - intensity, 255 - intensity) # Red
872
- else:
873
- intensity = min(255, int(abs(avg_shap * 500)))
874
- color = (255 - intensity, 255 - intensity, 255) # Blue
875
-
876
- # Draw gene box
877
- box_coords = [
878
- start_x,
879
- int(line_y - track_height // 2),
880
- end_x,
881
- int(line_y + track_height // 2)
882
- ]
883
- draw.rectangle(box_coords, fill=color, outline='black')
884
-
885
- # Draw gene name
886
- label = str(gene.get('gene_name', 'Unknown'))
887
- if font:
888
- label_bbox = draw.textbbox((0, 0), label, font=font)
889
- label_width = label_bbox[2] - label_bbox[0]
890
- else:
891
- label_width = len(label) * 6 # Rough estimate if no font
892
 
893
- # Try to place label, alternating above and below
894
- if sorted_genes.index(gene) % 2 == 0:
895
- text_y = line_y - track_height - 15
896
- else:
897
- text_y = line_y + track_height + 5
 
 
 
 
 
 
 
 
 
 
898
 
899
- # Draw label with rotation if space is tight
900
- gene_width = end_x - start_x
901
- if gene_width > label_width:
902
- # Horizontal label
903
- text_x = int(start_x + (gene_width - label_width) // 2)
904
- draw.text((text_x, text_y), label, fill='black', font=font)
905
- elif gene_width > 20:
906
- # Create rotated text image
907
- txt_img = Image.new('RGBA', (label_width, 20), (255, 255, 255, 0))
908
- txt_draw = ImageDraw.Draw(txt_img)
909
- txt_draw.text((0, 0), label, font=font, fill='black')
910
- txt_img = txt_img.rotate(90, expand=True)
911
- img.paste(txt_img, (int(start_x), text_y), txt_img)
912
-
913
  except Exception as e:
914
- print(f"Warning: Error drawing gene {gene.get('gene_name', 'unknown')}: {str(e)}")
915
  continue
916
 
917
- # Draw legend
918
- legend_x = margin
919
- legend_y = height - margin
920
- draw.text((legend_x, legend_y - 60), "SHAP Values:", fill='black', font=font)
 
921
 
922
- # Draw legend boxes
923
- box_width = 20
924
- box_height = 20
925
- spacing = 15
 
926
 
927
- # Strong human-like
928
- draw.rectangle([
929
- (legend_x, legend_y - 45),
930
- (legend_x + box_width, legend_y - 45 + box_height)
931
- ], fill=(255, 0, 0), outline='black')
932
- draw.text((legend_x + box_width + spacing, legend_y - 45),
933
- "Strong human-like signal", fill='black', font=font)
 
 
934
 
935
- # Weak human-like
936
- draw.rectangle([
937
- (legend_x, legend_y - 20),
938
- (legend_x + box_width, legend_y - 20 + box_height)
939
- ], fill=(255, 200, 200), outline='black')
940
- draw.text((legend_x + box_width + spacing, legend_y - 20),
941
- "Weak human-like signal", fill='black', font=font)
942
 
943
- # Weak non-human-like
944
- draw.rectangle([
945
- (legend_x + 250, legend_y - 45),
946
- (legend_x + 250 + box_width, legend_y - 45 + box_height)
947
- ], fill=(200, 200, 255), outline='black')
948
- draw.text((legend_x + 250 + box_width + spacing, legend_y - 45),
949
- "Weak non-human-like signal", fill='black', font=font)
950
 
951
- # Strong non-human-like
952
- draw.rectangle([
953
- (legend_x + 250, legend_y - 20),
954
- (legend_x + 250 + box_width, legend_y - 20 + box_height)
955
- ], fill=(0, 0, 255), outline='black')
956
- draw.text((legend_x + 250 + box_width + spacing, legend_y - 20),
957
- "Strong non-human-like signal", fill='black', font=font)
958
 
959
- return img
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
960
 
 
 
 
 
 
961
  ###############################################################################
962
  # 12. DOWNLOAD FUNCTIONS
963
  ###############################################################################
 
645
 
646
  def create_simple_genome_diagram(gene_results: List[Dict[str, Any]], genome_length: int) -> Image.Image:
647
  """Create a simple genome diagram using PIL"""
648
+ # Validate inputs and convert to proper types
649
  if not gene_results or genome_length <= 0:
650
  img = Image.new('RGB', (800, 100), color='white')
651
  draw = ImageDraw.Draw(img)
652
  draw.text((10, 40), "Error: Invalid input data", fill='black')
653
  return img
654
+
655
+ # Ensure all gene coordinates are valid integers and within bounds
656
+ valid_genes = []
657
  for gene in gene_results:
658
+ try:
659
+ start = max(0, int(float(gene['start'])))
660
+ end = min(genome_length, int(float(gene['end'])))
661
+ if start < end:
662
+ gene_copy = gene.copy()
663
+ gene_copy['start'] = start
664
+ gene_copy['end'] = end
665
+ valid_genes.append(gene_copy)
666
+ else:
667
+ print(f"Warning: Skipping gene {gene.get('gene_name', 'unknown')} due to invalid coordinates: {start}-{end}")
668
+ except (ValueError, TypeError) as e:
669
+ print(f"Warning: Skipping gene due to coordinate conversion error: {str(e)}")
670
+ continue
671
+
672
+ if not valid_genes:
673
+ img = Image.new('RGB', (800, 100), color='white')
674
+ draw = ImageDraw.Draw(img)
675
+ draw.text((10, 40), "Error: No valid genes to display", fill='black')
676
+ return img
677
+
678
  # Image dimensions
679
  width = 1500
680
  height = 600
 
685
  img = Image.new('RGB', (width, height), 'white')
686
  draw = ImageDraw.Draw(img)
687
 
688
+ # Use default font if custom font not available
689
  try:
690
  font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12)
691
  title_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16)
692
  except:
693
+ font = ImageFont.load_default()
694
+ title_font = ImageFont.load_default()
695
 
696
  # Draw title
697
+ draw.text((margin, margin//2), "Genome SHAP Analysis", fill='black', font=title_font)
 
698
 
699
  # Draw genome line
700
  line_y = height // 2
 
705
 
706
  # Draw scale markers
707
  for i in range(0, genome_length + 1, genome_length // 10):
708
+ x = margin + int(i * scale)
709
  draw.line([(x, line_y - 5), (x, line_y + 5)], fill='black', width=1)
710
  draw.text((x - 20, line_y + 10), f"{i:,}", fill='black', font=font)
711
 
712
+ # Sort genes by absolute SHAP value
713
+ sorted_genes = sorted(valid_genes, key=lambda x: abs(float(x['avg_shap'])))
714
 
715
  # Draw genes
716
+ for idx, gene in enumerate(sorted_genes):
717
+ # Calculate position
718
+ start_x = margin + int(float(gene['start']) * scale)
719
+ end_x = margin + int(float(gene['end']) * scale)
720
 
721
  # Calculate color based on SHAP value
722
+ avg_shap = float(gene['avg_shap'])
723
+ if avg_shap > 0:
724
+ intensity = min(255, int(abs(avg_shap * 500)))
725
  color = (255, 255 - intensity, 255 - intensity) # Red
726
  else:
727
+ intensity = min(255, int(abs(avg_shap * 500)))
728
  color = (255 - intensity, 255 - intensity, 255) # Blue
729
 
730
+ # Draw gene box
731
+ y_top = line_y - track_height // 2
732
+ y_bottom = line_y + track_height // 2
733
+ draw.rectangle([(start_x, y_top), (end_x, y_bottom)],
734
+ fill=color, outline='black')
735
 
736
  # Draw gene name
737
+ label = str(gene['gene_name'])
738
+ # Get text size for positioning
739
+ if hasattr(font, 'getsize'):
740
+ label_width, label_height = font.getsize(label)
741
+ else:
742
+ label_width = len(label) * 6 # Approximate width
743
+ label_height = 12
744
 
745
+ # Alternate label position above/below
746
+ if idx % 2 == 0:
747
+ text_y = y_top - label_height - 5
748
  else:
749
+ text_y = y_bottom + 5
750
 
751
+ # Draw label
752
  gene_width = end_x - start_x
753
  if gene_width > label_width:
754
  # Horizontal label
755
+ text_x = start_x + (gene_width - label_width) // 2
756
+ draw.text((text_x, text_y), label, fill='black', font=font)
757
  elif gene_width > 20:
758
+ # Vertical label
759
+ txt_img = Image.new('RGBA', (label_width, label_height), (255, 255, 255, 0))
760
  txt_draw = ImageDraw.Draw(txt_img)
761
  txt_draw.text((0, 0), label, font=font, fill='black')
762
  txt_img = txt_img.rotate(90, expand=True)
763
+ img.paste(txt_img, (start_x, text_y), txt_img)
764
 
765
  # Draw legend
766
  legend_x = margin
 
772
  box_height = 20
773
  spacing = 15
774
 
775
+ legend_items = [
776
+ ((255, 0, 0), "Strong human-like signal", (legend_x, legend_y - 45)),
777
+ ((255, 200, 200), "Weak human-like signal", (legend_x, legend_y - 20)),
778
+ ((200, 200, 255), "Weak non-human-like signal", (legend_x + 250, legend_y - 45)),
779
+ ((0, 0, 255), "Strong non-human-like signal", (legend_x + 250, legend_y - 20))
780
+ ]
 
 
 
 
 
781
 
782
+ for color, label, (x, y) in legend_items:
783
+ draw.rectangle([(x, y, x + box_width, y + box_height)],
784
+ fill=color, outline='black')
785
+ draw.text((x + box_width + spacing, y), label, fill='black', font=font)
 
 
 
 
 
 
 
786
 
787
  return img
788
 
789
+ def analyze_gene_features(sequence_file: str,
790
+ features_file: str,
791
+ fasta_text: str = "",
792
+ features_text: str = "") -> Tuple[str, Optional[str], Optional[Image.Image]]:
793
+ """Analyze SHAP values for each gene feature"""
794
+ # First analyze whole sequence
795
+ sequence_results = analyze_sequence(sequence_file, top_kmers=10, fasta_text=fasta_text)
796
+ if isinstance(sequence_results[0], str) and "Error" in sequence_results[0]:
797
+ return f"Error in sequence analysis: {sequence_results[0]}", None, None
798
+
799
+ # Get SHAP values
800
+ shap_means = sequence_results[3]["shap_means"]
801
 
802
+ # Parse gene features
803
+ try:
804
+ if features_text.strip():
805
+ genes = parse_gene_features(features_text)
806
+ else:
807
+ with open(features_file, 'r') as f:
808
+ genes = parse_gene_features(f.read())
809
+ except Exception as e:
810
+ return f"Error reading features file: {str(e)}", None, None
811
 
812
+ # Analyze each gene
813
+ gene_results = []
814
+ for gene in genes:
815
+ try:
816
+ location = gene['metadata'].get('location', '')
817
+ if not location:
818
  continue
819
 
820
+ start, end = parse_location(location)
821
+ if start is None or end is None:
822
+ continue
823
+
824
+ # Get SHAP values for this region
825
+ gene_shap = shap_means[start:end]
826
+ stats = compute_gene_statistics(gene_shap)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
827
 
828
+ gene_results.append({
829
+ 'gene_name': gene['metadata'].get('gene', 'Unknown'),
830
+ 'location': location,
831
+ 'start': start,
832
+ 'end': end,
833
+ 'locus_tag': gene['metadata'].get('locus_tag', ''),
834
+ 'avg_shap': stats['avg_shap'],
835
+ 'median_shap': stats['median_shap'],
836
+ 'std_shap': stats['std_shap'],
837
+ 'max_shap': stats['max_shap'],
838
+ 'min_shap': stats['min_shap'],
839
+ 'pos_fraction': stats['pos_fraction'],
840
+ 'classification': 'Human' if stats['avg_shap'] > 0 else 'Non-human',
841
+ 'confidence': abs(stats['avg_shap'])
842
+ })
843
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
844
  except Exception as e:
845
+ print(f"Error processing gene {gene['metadata'].get('gene', 'Unknown')}: {str(e)}")
846
  continue
847
 
848
+ if not gene_results:
849
+ return "No valid genes could be processed", None, None
850
+
851
+ # Sort genes by absolute SHAP value
852
+ sorted_genes = sorted(gene_results, key=lambda x: abs(x['avg_shap']), reverse=True)
853
 
854
+ # Create results text
855
+ results_text = "Gene Analysis Results:\n\n"
856
+ results_text += f"Total genes analyzed: {len(gene_results)}\n"
857
+ results_text += f"Human-like genes: {sum(1 for g in gene_results if g['classification'] == 'Human')}\n"
858
+ results_text += f"Non-human-like genes: {sum(1 for g in gene_results if g['classification'] == 'Non-human')}\n\n"
859
 
860
+ results_text += "Top 10 most distinctive genes:\n"
861
+ for gene in sorted_genes[:10]:
862
+ results_text += (
863
+ f"Gene: {gene['gene_name']}\n"
864
+ f"Location: {gene['location']}\n"
865
+ f"Classification: {gene['classification']} "
866
+ f"(confidence: {gene['confidence']:.4f})\n"
867
+ f"Average SHAP: {gene['avg_shap']:.4f}\n\n"
868
+ )
869
 
870
+ # Create CSV content
871
+ csv_content = "gene_name,location,avg_shap,median_shap,std_shap,max_shap,min_shap,"
872
+ csv_content += "pos_fraction,classification,confidence,locus_tag\n"
 
 
 
 
873
 
874
+ for gene in gene_results:
875
+ csv_content += (
876
+ f"{gene['gene_name']},{gene['location']},{gene['avg_shap']:.4f},"
877
+ f"{gene['median_shap']:.4f},{gene['std_shap']:.4f},{gene['max_shap']:.4f},"
878
+ f"{gene['min_shap']:.4f},{gene['pos_fraction']:.4f},{gene['classification']},"
879
+ f"{gene['confidence']:.4f},{gene['locus_tag']}\n"
880
+ )
881
 
882
+ # Save CSV to temp file
883
+ try:
884
+ temp_dir = tempfile.gettempdir()
885
+ temp_path = None
 
 
 
886
 
887
+ # Create visualization with robust error handling
888
+ try:
889
+ # Ensure all gene coordinates are numeric and valid
890
+ for gene in gene_results:
891
+ try:
892
+ gene['start'] = int(float(gene['start']))
893
+ gene['end'] = int(float(gene['end']))
894
+ if gene['start'] >= gene['end']:
895
+ raise ValueError(f"Invalid coordinates for gene {gene['gene_name']}: {gene['start']}-{gene['end']}")
896
+ except (ValueError, TypeError) as e:
897
+ print(f"Warning: Invalid coordinates for gene {gene['gene_name']}: {str(e)}")
898
+ continue
899
+
900
+ diagram_img = create_simple_genome_diagram(gene_results, len(shap_means))
901
+
902
+ except Exception as e:
903
+ print(f"Error creating visualization: {str(e)}")
904
+ # Create error image
905
+ diagram_img = Image.new('RGB', (800, 100), color='white')
906
+ draw = ImageDraw.Draw(diagram_img)
907
+ draw.text((10, 40), f"Error creating visualization: {str(e)}", fill='black')
908
 
909
+ return results_text, temp_path, diagram_img os.path.join(temp_dir, f"gene_analysis_{os.urandom(4).hex()}.csv")
910
+
911
+ with open(temp_path, 'w') as f:
912
+ f.write(csv_content)
913
+
914
  ###############################################################################
915
  # 12. DOWNLOAD FUNCTIONS
916
  ###############################################################################