11import os
22import numpy as np
3+ import re
4+ import matplotlib .colors as mcolors
35
6+ import matplotlib .pyplot as plt
47from qtpy import QtGui , QtCore , QtWidgets
58import pyqtgraph as pg
6-
9+ from pyqtgraph .exporters import ImageExporter
10+ from matplotlib .colors import to_rgb
711from mapmanagercore .imageImporter import acceptedExtensions
812
913import pymapmanager
@@ -252,7 +256,7 @@ def contextMenuEvent(self, event : QtGui.QContextMenuEvent):
252256
253257 elif action == copyImageAction :
254258 # pass
255- exporter = pg . exporters . ImageExporter (self .getPlotWidget ().plotItem )
259+ exporter = ImageExporter (self .getPlotWidget ().plotItem )
256260 qimage = exporter .export (toBytes = True )
257261
258262 app = self ._stackWidget .getPyMapManagerApp ()
@@ -262,7 +266,7 @@ def contextMenuEvent(self, event : QtGui.QContextMenuEvent):
262266
263267 elif action == exportImageAction :
264268 # Prompt for file location
265- exporter = pg . exporters . ImageExporter (self .getPlotWidget ().plotItem )
269+ exporter = ImageExporter (self .getPlotWidget ().plotItem )
266270 _path = self .getPath ()
267271 filters = '(*.png)'
268272 savePath , _ = QtWidgets .QFileDialog .getSaveFileName (self ,
@@ -500,6 +504,12 @@ def setRadiusEvent(self, event):
500504 sliceNumber = self ._currentSlice
501505 self ._aLinePlot .slot_setSlice (sliceNumber )
502506
507+ def updateChannelMetadataEvent (self , event ):
508+ """
509+ """
510+ # refresh image with new image color
511+ self .refreshSlice ()
512+
503513 def slot_setSlice (self , sliceNumber , doEmit = True ):
504514 if self .slotsBlocked ():
505515 return
@@ -546,20 +556,82 @@ def _setColorLut(self, update=False):
546556 if not self ._channelIsRGB ():
547557 logger .warning ("TODO: add color str like ('red', 'green' 'blue')" )
548558 colorStr = self ._myStack .getChannelColor (self ._displayThisChannelIdx ) # like 'r',
549-
559+ logger . info ( f"colorStr is { colorStr } " )
550560 if colorStr == 'red' :
551561 cm = pg .colormap .get ('Reds_r' , source = 'matplotlib' )
552562 elif colorStr == 'green' :
553563 cm = pg .colormap .get ('Greens_r' , source = 'matplotlib' )
554564 elif colorStr == 'blue' :
555565 cm = pg .colormap .get ('Blues_r' , source = 'matplotlib' )
556566 else :
557- logger .warning (f'did not understand color { colorStr } -->> defaulting to Greens_r' )
558- # cm = pg.colormap.get('Greys_r', source='matplotlib')
559- cm = pg .colormap .get ('Greens_r' , source = 'matplotlib' )
567+
568+ # logger.warning(f'did not understand color {colorStr} -->> defaulting to Greens_r')
569+ # # cm = pg.colormap.get('Greens_r', source='matplotlib')
570+
571+ # here colorStr is a hehex code
572+ colorRGB = to_rgb (colorStr )
573+
574+ # Create a list of colors transitioning from dark to bright
575+ cm_qcolors = []
576+ num_steps = 256 # More steps for smoother gradient
577+
578+ import numpy as np
579+
580+ for i in range (num_steps ):
581+ value_factor = i / (num_steps - 1 )
582+ # Adjust logarithmic scaling to match matplotlib's dynamic range
583+ log_factor = np .log1p (value_factor * 5 ) / np .log1p (5 )
584+ log_factor = log_factor ** 0.7
585+
586+ # Start from a dark version of the color and interpolate to bright
587+ dark_brightness = 0.3
588+
589+ # Find the dominant channel (should be the one with highest value)
590+ max_channel = max (colorRGB )
591+ is_dominant = [c == max_channel for c in colorRGB ]
592+
593+ # Calculate base factors for each channel
594+ r_factor = log_factor * 1.2 if is_dominant [0 ] else log_factor * 0.3
595+ g_factor = log_factor * 1.2 if is_dominant [1 ] else log_factor * 0.3
596+ b_factor = log_factor * 1.2 if is_dominant [2 ] else log_factor * 0.3
597+
598+ # Allow transition to white at highest intensities
599+ white_threshold = 0.7 # When to start transitioning to white
600+ if log_factor > white_threshold :
601+ # Calculate how far we are into the white transition
602+ white_amount = (log_factor - white_threshold ) / (1 - white_threshold )
603+ # Smoothly interpolate to white
604+ r_factor = r_factor * (1 - white_amount ) + white_amount
605+ g_factor = g_factor * (1 - white_amount ) + white_amount
606+ b_factor = b_factor * (1 - white_amount ) + white_amount
607+
608+ # Ensure we don't exceed valid values
609+ r_factor = min (1.0 , r_factor )
610+ g_factor = min (1.0 , g_factor )
611+ b_factor = min (1.0 , b_factor )
612+
613+ r = int ((colorRGB [0 ] * dark_brightness * 255 ) + (255 - colorRGB [0 ] * dark_brightness * 255 ) * r_factor )
614+ g = int ((colorRGB [1 ] * dark_brightness * 255 ) + (255 - colorRGB [1 ] * dark_brightness * 255 ) * g_factor )
615+ b = int ((colorRGB [2 ] * dark_brightness * 255 ) + (255 - colorRGB [2 ] * dark_brightness * 255 ) * b_factor )
616+
617+ # Keep alpha at full opacity
618+ a = 255
619+
620+ cm_qcolors .append (QtGui .QColor (r , g , b , a ))
621+
622+ # Create matching normalized positions
623+ positions = [i / (len (cm_qcolors ) - 1 ) for i in range (len (cm_qcolors ))]
624+
625+ # Create pyqtgraph ColorMap
626+ cm = pg .ColorMap (positions , cm_qcolors )
627+
628+ # # Apply to image
629+ # self._myImage.setLookupTable(lut)
630+
560631
561632 self ._myImage .setColorMap (cm )
562633
634+
563635 def _setContrast (self ):
564636 if self ._channelIsRGB ():
565637 tmpLevelList = [] # list of [min,max]
@@ -649,6 +721,8 @@ def _setSlice(self, sliceNumber : int, doEmit = True):
649721 upSlices = upDownSlices , downSlices = upDownSlices ,
650722 func = np .max )
651723
724+ # logger.info(f"check ch0_image min {ch1_image.min()} max {ch1_image.max()}")
725+
652726 # rgb requires 8-bit images
653727 ch0_image = ch0_image / ch0_image .max () * 2 ** 8
654728 ch1_image = ch1_image / ch1_image .max () * 2 ** 8
@@ -662,10 +736,30 @@ def _setSlice(self, sliceNumber : int, doEmit = True):
662736 _yShape = ch0_image .shape [1 ]
663737 sliceImage = np .ndarray ((_xShape ,_yShape ,3 ))
664738
665- # magenta is blue + red
666- sliceImage [:,:,0 ] = ch1_image # red
667- sliceImage [:,:,1 ] = ch0_image # green
668- sliceImage [:,:,2 ] = ch1_image # blue
739+ # # magenta is blue + red
740+ # sliceImage[:,:,0] = ch1_image # red
741+ # sliceImage[:,:,1] = ch0_image # green
742+ # sliceImage[:,:,2] = ch1_image # blue
743+
744+ # Get first two channel colors
745+ color0 = self ._myStack .getChannelColor (1 )
746+ color1 = self ._myStack .getChannelColor (2 )
747+
748+ color0 = self .colorToHex (color0 )
749+ color1 = self .colorToHex (color1 )
750+ logger .info (f"color0 { color0 } color1 { color1 } " )
751+
752+ # Convert hex color to RGB arrays
753+ rgb0 = to_rgb (color0 ) # for ch0_image
754+ rgb1 = to_rgb (color1 ) # for ch1_image
755+
756+ brightness_factor = 2
757+ for i in range (3 ): # R, G, B
758+ sliceImage [:,:,i ] = (
759+ brightness_factor *
760+ (ch0_image * (rgb0 [i ])) +
761+ (ch1_image * (rgb1 [i ])) * i
762+ ).clip (0 , 255 ).astype (np .uint8 )
669763
670764 else :
671765 sliceImage = self ._myStack .getMaxProjectSlice (sliceNumber ,
@@ -694,6 +788,23 @@ def _setSlice(self, sliceNumber : int, doEmit = True):
694788 logger .info (f' -->> emitEvent signalUpdateSlice() _currentSlice:{ self ._currentSlice } ' )
695789 self .emitEvent (_pmmEvent , blockSlots = True )
696790
791+
792+ def colorToHex (self , color_str ):
793+ # Normalize input
794+ color_str = color_str .strip ().lower ()
795+
796+ # Regex for valid hex color: #RGB, #RRGGBB, RGB, or RRGGBB
797+ hex_pattern = r'^#?([0-9a-f]{3}|[0-9a-f]{6})$'
798+
799+ if re .fullmatch (hex_pattern , color_str ):
800+ # Add '#' if missing
801+ return '#' + color_str .lstrip ('#' )
802+
803+ try :
804+ return mcolors .to_hex (color_str )
805+ except ValueError :
806+ raise ValueError (f"Unknown color name or invalid hex: '{ color_str } '" )
807+
697808 def _emitSetSlice (self , newSlice ):
698809 _pmmEvent = pmmEvent (pmmEventType .setSlice , self )
699810 # _pmmEvent.setSliceNumber(self._currentSlice)
@@ -724,11 +835,13 @@ def togglePlot(self, plotName):
724835 if plotName == "Annotations" :
725836 self ._toggleAllAnnotations = not self ._toggleAllAnnotations
726837 toggle = self ._toggleAllAnnotations
838+ logger .info (f"toggle { toggle } " )
727839 visible = self ._aPointPlot .toggleScatterPlot (toggle ) # spines
728840 self ._aPointPlot .toggleSpineLines (toggle ) # spine (lines)
729- visible2 = self ._aLinePlot .toggleScatterPlot (toggle ) # center line
841+ visible2 = self ._aLinePlot .toggleSegmentPlot (toggle ) # center line
730842 visible3 = self ._aLinePlot .toggleRadiusLines (toggle ) # radius lines
731843 visible4 = self ._aPointPlot .toggleLabels (toggle ) # labels
844+ visible5 = self ._aLinePlot .togglePivotPoints (toggle )
732845 # pass
733846 elif plotName == "Spines" :
734847 visible = self ._aPointPlot .toggleScatterPlot ()
0 commit comments