# @int (Label = "Number of categories") N_category
'''
This script can be used to manually classify full images from a stack into N user-defined categories.
A first window pops up to request the number of categories.
A second window follows asking for the name and /character to use as a keyboard shortcut for those categories.

NB : 
- Existing ImageJ shortcuts are deactivated
- The keyboard mapping depends on the keyboard layout (Virtual Keycode VK used for the events), so it might give strange result with keyboard others than german ones (the best would be to catch up keyevent directly when assigning shortcuts)


TO DO : Add mesaurement possibility ? The addValue was not working so well in this case. Duplicate to another code to try with the result table
'''
from ij.gui import GenericDialog
from ij     import WindowManager
from java.awt.event import KeyEvent, KeyAdapter
from ij.measure import ResultsTable
import ij.IJ as IJ

############### GUI : CATEGORY AND SHORTCUT DIALOG #############
Win = GenericDialog("Category name and keyboard shortcut") # Title of the window
Win.enableYesNoCancel('OK','Cancel') # OK, Cancel (chosen) and Cancel button
Win.hideCancelButton()  # keep only one cancel button

for i in range(N_category):
	Win.addStringField("Category: ","Category_"+str(i))
	Win.addStringField("Keyboard Shortcut",str(i))
	Win.addMessage("") # skip one line
	
Win.showDialog()



################# After OK clicking ###########
List_Category = []
List_Shortcut = []
DicoDigit = {i:j for i,j in zip('0123456789',range(96,106))} # create Char:KeyCode mapping for the numpad keystroke (since they do not return ASCII)
DicoCategory = {} # Map KeyCode:Category

# Recover fields from the formular
if (Win.wasOKed()): 
	for i in range(N_category):
		
		Cat = Win.getNextString()
		Shortcut_str = Win.getNextString().upper() # ImageJ is not case sensitive regarding ASCII code, always return the upper case value

		if len(Shortcut_str) != 1 :
			raise Exception('Shortcut should be a 1-character string')
		
		if Shortcut_str in '0123456789' : # for digits there will be 2 KeyCode for 1 category : 1 for the keycode from the numpad, a second for the keycode from the top row of digit on the keyboard
			KeyCode1 = ord(Shortcut_str)        # Keycode for the digit generated from the top row of the keyboard
			KeyCode2 = DicoDigit[Shortcut_str]  # Keycode for the digit generated via the numpad
			
			DicoCategory[KeyCode1] = Cat
			DicoCategory[KeyCode2] = Cat
			
		else :
			KeyCode = ord(Shortcut_str)
			DicoCategory[KeyCode] = Cat 
	
#print DicoCategory

################## KEYEVENTS #########
Table = ResultsTable()
#Table.show("Classification")

def doSomething(imp, keyEvent): # function called at each key pressed on an image imp (imp is an ImagePlus object)
  Key = keyEvent.getKeyCode()
  keyEvent.consume() # Prevent further propagation of the key event
  
  #print Key
  
  if Key in DicoCategory.keys() : # Check that the pressed key is one of the shortcuts
  	  #print Key
  	  Table.incrementCounter() # Add one additional row before filling it

	  # recover the image or slice name (getTitle return the name for single image but not for stacks. Hence generic solution is to use Stack object works in bopth cases)
	  Stack = imp.getStack()
	  SliceName = Stack.getSliceLabel(imp.currentSlice)	  
  
	  if SliceName is None: # the slice label can be empty sometime
	  	SliceName = 'Slice' + str(imp.currentSlice)	  
		
	  else : 
	  	SliceName = SliceName.split('\n',1)[0]
	  
	  Table.addValue("Image",SliceName)	
	  Table.addValue("Category",DicoCategory[Key])
	  Table.show('Classification') # Update table	  
	  #Table.updateResults() # only for result table but then addValue does not work !

	  if imp.getStackSize() != 1 and imp.currentSlice != imp.getStackSize(): # if We have a stack and the current slice is not the last slice
	  	imp.setSlice(imp.currentSlice+1)
	  	imp.updateStatusbarValue() # update Z and pixel value (called by next slice so should do it too)

	  # Bring back the focus to the image (otherwise the table is in the front and one need to reclick on the image)
	  Id = imp.getID()
	  IJ.selectWindow(Id)
	  

## LISTENING CLASS  
class ListenToKey(KeyAdapter): 
  def keyPressed(this, event):
    imp = event.getSource().getImage() # Get image on which we clicked
    doSomething(imp, event)            # Call feedback function with source image and key that was typed 

listener = ListenToKey() # Create an instance of the listening class ie start listening to the event 



# Add our listener to each image
for imp in map(WindowManager.getImage, WindowManager.getIDList()):
  win = imp.getWindow()
  if win is None:
    continue
  canvas = win.getCanvas()
  
  # Remove existing key listeners
  kls = canvas.getKeyListeners()
  map(canvas.removeKeyListener, kls)
 
  # Add our key listener
  canvas.addKeyListener(listener)
  
  # Optionally re-add existing key listeners
  # map(canvas.addKeyListener, kls)