1515import pandas as pd
1616import panel as pn
1717import param
18+
19+ import logging
20+ from bokeh .util .logconfig import basicConfig
21+ basicConfig (level = logging .ERROR ) # no warnings
1822from bokeh import events
1923from bokeh .models import (
2024 BoxSelectTool ,
2933)
3034from bokeh .plotting import figure
3135
36+ from channelmap_generator import __version__
3237from channelmap_generator .constants import PROBE_N , PROBE_TYPE_MAP , SUPPORTED_1shank_PRESETS , SUPPORTED_4shanks_PRESETS , WIRING_FILE_MAP
3338from channelmap_generator .utils import imro
3439from channelmap_generator .types import Electrode
@@ -98,6 +103,8 @@ class ChannelmapGUI(param.Parameterized):
98103
99104 # Parameters - will take their value as attributes after class initialization
100105 default_type = "2.0-4shanks"
106+ download_button_color = param .String (default = "default" )
107+ download_button_label = param .String (default = "Select electrodes..." )
101108
102109 probe_type = param .Selector (default = default_type , objects = list (PROBE_TYPE_MAP .keys ()), doc = "Neuropixels probe type" )
103110
@@ -902,25 +909,12 @@ def create_widgets(self):
902909 name = "IMRO or PDF filename (omit extension)" ,
903910 value = f"manual_selection_{ self .probe_type } " , # default filename defined here
904911 width = 300 ,
912+ margin = (0 , 0 , 0 , 30 )
905913 )
906914
907- self .download_imro_button = pn .widgets .FileDownload (
908- callback = self .generate_imro_content ,
909- filename = f"{ self .filename_input .value } .imro" ,
910- button_type = "success" ,
911- width = 140 ,
912- icon = "file-text" ,
913- label = "Download IMRO ⬇" ,
914- )
915-
916- self .download_pdf_button = pn .widgets .FileDownload (
917- callback = self .generate_pdf_content ,
918- filename = f"{ self .filename_input .value } .pdf" ,
919- button_type = "success" ,
920- width = 140 ,
921- icon = "file-type-pdf" ,
922- label = "Download PDF ⬇" ,
923- )
915+ # Download buttons creation
916+ # (in their own function to handle their reactive appearance)
917+ self .create_download_buttons ()
924918
925919 # Prominent electrode counter (moved to top)
926920 self .electrode_counter = pn .pane .HTML (
@@ -932,7 +926,7 @@ def create_widgets(self):
932926 </div>
933927 """ ,
934928 width = 300 ,
935- margin = (10 , 10 ),
929+ margin = (30 , 10 , 10 , 10 ),
936930 align = "center" ,
937931 )
938932
@@ -973,22 +967,65 @@ def create_widgets(self):
973967 )
974968 self .apply_uploaded_imro_button .on_click (lambda event : self .apply_uploaded_imro ())
975969
970+
971+ def create_download_buttons (self ):
972+ """Create download buttons as a reactive pane"""
973+ # This method recreates the buttons when color changes
974+ self .download_imro_button = pn .widgets .FileDownload (
975+ callback = self .generate_imro_content ,
976+ filename = f"{ self .filename_input .value } .imro" ,
977+ button_type = self .download_button_color ,
978+ width = 140 ,
979+ icon = "file-text" ,
980+ label = self .download_button_label ,
981+ )
982+
983+ self .download_pdf_button = pn .widgets .FileDownload (
984+ callback = self .generate_pdf_content ,
985+ filename = f"{ self .filename_input .value } .pdf" ,
986+ button_type = self .download_button_color ,
987+ width = 140 ,
988+ icon = "file-type-pdf" ,
989+ label = self .download_button_label .replace ("IMRO" , "PDF" ),
990+ )
991+
992+ return pn .Row (
993+ self .download_imro_button ,
994+ self .download_pdf_button ,
995+ sizing_mode = "stretch_width" ,
996+ margin = (0 , 0 , 0 , 20 ),
997+ )
998+
999+ @pn .depends ('download_button_color' , 'download_button_label' )
1000+ def get_download_buttons (self ):
1001+ """Reactive method that recreates buttons when color changes"""
1002+ return self .create_download_buttons ()
1003+
9761004 def create_layout (self ):
9771005 """Create the main Panel layout"""
9781006
1007+ # Counter and Downloader (fixed on the right)
1008+ downloader = pn .Column (
1009+ pn .Column (
1010+ self .electrode_counter ,
1011+ self .clear_button ,
1012+ margin = (0 , 0 , - 10 , 20 ),
1013+ ),
1014+ pn .pane .Markdown ("## Export Channelmap" , margin = (10 , 0 , - 5 , 30 )),
1015+ self .filename_input ,
1016+ self .get_download_buttons ,
1017+ )
1018+
9791019 # Controls panel (fixed on left)
9801020 controls = pn .Column (
9811021 pn .pane .Markdown (
9821022 (
983- "<div style='text-align: center; padding: 12px;'><strong>See project at:"
1023+ f "<div style='text-align: center; padding: 12px;'><strong>See project (v { __version__ } ) at:"
9841024 "<br><a href='https://github.com/m-beau/channelmap_generator' "
9851025 "target='_blank'>github.com/m-beau/channelmap_generator</a></strong></div>"
9861026 ),
987- margin = (0 , 0 , - 10 , 40 ),
1027+ margin = (0 , 0 , 0 , 40 ),
9881028 ),
989- # Prominent electrode counter at top
990- self .electrode_counter ,
991- self .clear_button ,
9921029 pn .Column (
9931030 pn .pane .Markdown ("## Probe and recording metadata" , margin = (- 5 , 0 , 0 , 10 )),
9941031 pn .pane .Markdown (
@@ -1022,13 +1059,7 @@ def create_layout(self):
10221059 self .imro_file_loader ,
10231060 # pn.Spacer(height=30),
10241061 self .apply_uploaded_imro_button ,
1025- pn .pane .Markdown ("## Export Channelmap" , margin = (10 , 0 , - 5 , 10 )),
1026- self .filename_input ,
1027- pn .Row (
1028- self .download_imro_button ,
1029- self .download_pdf_button ,
1030- sizing_mode = "stretch_width" ,
1031- ),
1062+
10321063 pn .pane .Markdown ("## Instructions" , margin = (10 , 0 , - 5 , 10 )),
10331064 pn .pane .HTML ("""
10341065 <div style="font-size: 13px; line-height: 1.4; text-align: justify;">
@@ -1071,7 +1102,8 @@ def create_layout(self):
10711102 controls ,
10721103 pn .Spacer (width = 370 ), # Slightly wider than controls (350 + margin)
10731104 plot_container ,
1074- sizing_mode = "stretch_width" ,
1105+ downloader ,
1106+ sizing_mode = "fixed" ,
10751107 )
10761108
10771109 return layout
@@ -1104,30 +1136,32 @@ def update_electrode_counter(self):
11041136 # n_remaining = max_allowed - n_selected
11051137
11061138 # Update electrode counter
1107- if hasattr (self , "electrode_counter" ):
1108- if n_selected < max_allowed :
1109- counter_html = f"""
1110- <div style="background: #f0f8ff; border: 2px solid #4a90e2; border-radius: 8px;
1111- padding: 12px; text-align: center; font-size: 16px; font-weight: bold;
1112- color: #AA4A44;">
1113- Selected Electrodes: { n_selected } /{ max_allowed }
1114- </div>
1115- """
1116- else :
1117- counter_html = f"""
1118- <div style="background: #f0f8ff; border: 2px solid #4a90e2; border-radius: 8px;
1119- padding: 12px; text-align: center; font-size: 16px; font-weight: bold;
1120- color: #008000;">
1121- Selected Electrodes: { n_selected } /{ max_allowed } <br>
1122- Ready for IMRO file generation!
1123- </div>
1124- """
1125- self .electrode_counter .object = counter_html
1126-
1139+ if n_selected < max_allowed :
1140+ counter_html = f"""
1141+ <div style="background: #f0f8ff; border: 2px solid #4a90e2; border-radius: 8px;
1142+ padding: 12px; text-align: center; font-size: 16px; font-weight: bold;
1143+ color: #AA4A44;">
1144+ Selected Electrodes: { n_selected } /{ max_allowed }
1145+ </div>
1146+ """
1147+ self .download_button_color = 'default'
1148+ self .download_button_label = "Select electrodes..."
1149+ else :
1150+ counter_html = f"""
1151+ <div style="background: #f0f8ff; border: 2px solid #4a90e2; border-radius: 8px;
1152+ padding: 12px; text-align: center; font-size: 16px; font-weight: bold;
1153+ color: #008000;">
1154+ Selected Electrodes: { n_selected } /{ max_allowed } <br>
1155+ Ready for IMRO file generation!
1156+ </div>
1157+ """
1158+ self .download_button_color = 'success'
1159+ self .download_button_label = "Download IMRO ⬇"
11271160
1128- ## App creation utilities
1161+ self . electrode_counter . object = counter_html
11291162
11301163
1164+ ## App creation utilities
11311165def find_free_port (start_port = 5007 ):
11321166 """Find next available port starting from start_port"""
11331167 for port in range (start_port , start_port + 100 ):
0 commit comments