Commit bf78e965 authored by Keith Schulze's avatar Keith Schulze
Browse files

Initial commit of working prototype for lung scoring

parents
# Created by https://www.gitignore.io
### Jython ###
*.pyc
*.class
### SublimeText ###
# cache files for sublime text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# workspace files are user-specific
*.sublime-workspace
# project files should be checked into the repository, unless a significant
# proportion of contributors will probably not be using SublimeText
*.sublime-project
# sftp configuration file
sftp-config.json
### SBT ###
# Simple Build Tool
# http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control
target/
lib_managed/
src_managed/
project/boot/
.history
.cache
name := "GridJ"
organization := "edu.monash.gridj"
version := "0.1-SNAPSHOT"
javacOptions ++= Seq("-source", "1.6", "-target", "1.6")
resolvers ++= Seq(
"imagej.releases" at "http://maven.imagej.net/content/repositories/releases",
"imagej.snapshots" at "http://maven.imagej.net/content/repositories/snapshots",
"imagej.public" at "http://maven.imagej.net/content/groups/public"
)
libraryDependencies ++= Seq (
"net.imagej" % "imagej" % "latest.integration",
"sc.fiji" % "Jython_Interpreter" % "2.0.0-SNAPSHOT"
)
enablePlugins(SbtImageJ)
\ No newline at end of file
addSbtPlugin("net.sf.ij-plugins" % "sbt-imagej" % "2.0.0")
\ No newline at end of file
package edu.monash.gridj;
import ij.plugin.PlugIn;
import Jython.Refresh_Jython_Scripts;
public class Jython_Launcher implements PlugIn {
public void run(String arg) {
new Refresh_Jython_Scripts().runScript(getClass().getResourceAsStream(arg));
}
}
\ No newline at end of file
Plugins>GridJ, "GridJ", edu.monash.gridj.Jython_Launcher("/scripts/gridj.py")
\ No newline at end of file
# Copyright (c) 2015 Keith Schulze, Monash Micro Imaging, Monash University.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# Changelog:
# v1.1 - Added code to allow grid to automatically id tissue in H&E stain.
import math
from java.awt import Color, Font
from javax.swing import (ButtonGroup, BoxLayout, JButton, JFrame, JRadioButton,
JLabel, JPanel, JSeparator, SwingConstants,
WindowConstants)
from java.awt.event import KeyAdapter, KeyEvent
from ij import IJ, WindowManager
from ij.gui import OvalRoi, GenericDialog
from ij.measure import Measurements, ResultsTable
from ij.plugin.frame import RoiManager
from ij.util import Tools
HARTS = "Hart's Stain"
HE = "H & E Stain"
class CatOvalRoi(OvalRoi):
'''
Categorised Oval Roi. Adds a classification parameter to the OvalRoi class
to allow categorisation of each Roi.
'''
AIRSPACE = 1
TISSUE = 2
SEPTAL_CREST = 3
OPACITY = 0.5
def __init__(self, x, y, width=20, height=20):
'''Constructor for Categorised Oval ROI - classification defaults to
airspace'''
OvalRoi.__init__(self, x, y, width, height)
self.classification = self.AIRSPACE
# self.setStrokeColor()
self.setFillColor(Color(0.0, 0.0, 0.0, self.OPACITY))
def get_classification():
return self.classification
def set_classification(classification):
self.classification = classification
class CatRMKeyAdapter(KeyAdapter):
def keyPressed(self, event):
kc = event.getKeyCode()
rm = CatRoiManager.getInstance()
roi = rm.getSelectedRoisAsArray()[0]
if kc == KeyEvent.VK_1:
roi.classification = CatOvalRoi.AIRSPACE
rm.runCommand(
"Set Fill Color", "#80" + Tools.c2hex(Color(0.0, 0.0, 0.0))[1:])
rm.as_button.setSelected(True)
elif kc == KeyEvent.VK_2:
roi.classification = CatOvalRoi.TISSUE
rm.runCommand(
"Set Fill Color", "#80" + Tools.c2hex(Color(0.0, 0.6, 0.0))[1:])
rm.t_button.setSelected(True)
elif kc == KeyEvent.VK_3:
roi.classification = CatOvalRoi.SEPTAL_CREST
rm.runCommand("Set Fill Color", Tools.c2hex(Color(1.0, 0.0, 0.0)))
rm.sc_button.setSelected(True)
event.consume()
class CatRoiManager(RoiManager):
'''
Subclass of RoiManager that supports categorised ROIs
'''
RESULT_TITLE = "Septal Crest Count Summary"
def __init__(self, image, result_table):
super(CatRoiManager, self).__init__()
self.image = image
self.load_image_key_handlers()
self.result_table = result_table
self.frame = JFrame('Classify ROIs', defaultCloseOperation=WindowConstants.DISPOSE_ON_CLOSE,
size=(200, 300))
self.frame.windowClosing = self.wclose
panel = JPanel()
panel.layout = BoxLayout(panel, BoxLayout.Y_AXIS)
class_label = JLabel("ROI Classification")
font = Font(class_label.font.fontName, Font.BOLD, 12)
class_label.font = font
rb_group = ButtonGroup()
self.as_button = JRadioButton(
'Airspace', actionPerformed=self.classify_airspace)
self.t_button = JRadioButton(
'Tissue', actionPerformed=self.classify_tissue)
self.sc_button = JRadioButton(
'Septal Crest', actionPerformed=self.classify_septal_crest)
rb_group.add(self.as_button)
rb_group.add(self.t_button)
rb_group.add(self.sc_button)
panel.add(class_label)
panel.add(self.as_button)
panel.add(self.t_button)
panel.add(self.sc_button)
sep = JSeparator(SwingConstants.HORIZONTAL)
panel.add(sep)
measure_label = JLabel("Measurements")
measure_label.font = font
self.measure_btn = JButton("Measure", actionPerformed=self.measure)
panel.add(measure_label)
panel.add(self.measure_btn)
self.frame.add(panel)
self.frame.pack()
self.frame.visible = True
def load_image_key_handlers(self):
canvas = self.image.getWindow().getCanvas()
kls = canvas.getKeyListeners()
map(canvas.removeKeyListener, kls)
canvas.addKeyListener(CatRMKeyAdapter())
def wclose(self, event):
self.close()
def close(self):
self.frame.dispose()
self.super__close()
def valueChanged(self, event):
roi = self.getSelectedRoisAsArray()[0]
if roi.classification == CatOvalRoi.AIRSPACE:
self.as_button.setSelected(True)
elif roi.classification == CatOvalRoi.TISSUE:
self.t_button.setSelected(True)
elif roi.classification == CatOvalRoi.SEPTAL_CREST:
self.sc_button.setSelected(True)
return self.super__valueChanged(event)
def classify_airspace(self, event):
roi = self.getSelectedRoisAsArray()[0]
roi.classification = CatOvalRoi.AIRSPACE
self.runCommand(
"Set Fill Color", "#80" + Tools.c2hex(Color(0.0, 0.0, 0.0))[1:])
def classify_tissue(self, event):
roi = self.getSelectedRoisAsArray()[0]
roi.classification = CatOvalRoi.TISSUE
self.runCommand(
"Set Fill Color", "#80" + Tools.c2hex(Color(0.0, 0.6, 0.0))[1:])
def classify_septal_crest(self, event):
roi = self.getSelectedRoisAsArray()[0]
roi.classification = CatOvalRoi.SEPTAL_CREST
self.runCommand("Set Fill Color", Tools.c2hex(Color(1.0, 0.0, 0.0)))
def move_rois_to_overlay(self, image):
self.moveRoisToOverlay(image)
def tabulateResults(self, image, rt, airspace_count, tissue_count, septal_crest_count):
rt.incrementCounter()
rt.addValue("Title", image.getTitle())
rt.addValue("Airspace", airspace_count)
rt.addValue("Tissue", tissue_count)
rt.addValue("Septal Crest", septal_crest_count)
rt.addValue(
"Total", airspace_count + tissue_count + septal_crest_count)
def measure(self, event):
airspace_count = 0
tissue_count = 0
septal_crest_count = 0
rois = self.getRoisAsArray()
for roi in rois:
if roi.classification == CatOvalRoi.AIRSPACE:
airspace_count += 1
elif roi.classification == CatOvalRoi.TISSUE:
tissue_count += 1
elif roi.classification == CatOvalRoi.SEPTAL_CREST:
septal_crest_count += 1
else:
print "Whoops, a ROI has an unknown classification"
self.tabulateResults(
self.image, self.result_table, airspace_count, tissue_count, septal_crest_count)
print "Successfully tabulated"
self.result_table.show(self.RESULT_TITLE)
def calculate_row_number(image, row_step_size, scaled_units):
row_number = 0
if scaled_units:
cal = image.getCalibration()
step = cal.getRawY(row_step_size)
row_number = math.ceil((image.getHeight() - step) / step)
else:
row_number = math.ceil(
(image.getHeight() - row_step_size) / row_step_size)
return row_number
def calculate_column_number(image, column_step_size, alternate_rows_offset, scaled_units):
col_number = 0
offset_constant = 1
if alternate_rows_offset:
offset_constant = 2
if scaled_units:
cal = image.getCalibration()
step = cal.getRawX(column_step_size)
col_number = math.ceil(
(image.getWidth() - offset_constant * step) / step)
else:
col_number = math.ceil(
(image.getWidth() - offset_constant * column_step_size) / column_step_size)
return col_number
def is_odd(x):
return x & 1
def create_grid(image, spot_diameter, row_step_size, column_step_size,
alternate_rows_offset, scaled_units,):
row_number = calculate_row_number(image, row_step_size, scaled_units)
column_number = calculate_column_number(
image, column_step_size, alternate_rows_offset, scaled_units)
row_offset = row_step_size / 2
col_offset = column_step_size / 2
if scaled_units:
cal = image.getCalibration()
row_step_size = cal.getRawY(row_step_size)
column_step_size = cal.getRawX(column_step_size)
row_offset = cal.getRawY(row_offset)
col_offset = cal.getRawX(col_offset)
rois = []
if alternate_rows_offset:
offset_constant = 1
for row in xrange(int(row_number)):
if row == 0 or not is_odd(row):
offset_constant = 1
else:
offset_constant = 2
y = row_offset + (row * row_step_size)
for col in xrange(int(column_number)):
x = (col_offset * offset_constant) + (col * column_step_size)
roi = CatOvalRoi(x, y, spot_diameter, spot_diameter)
rois.append(roi)
else:
for row in xrange(int(row_number)):
y = row_offset + (row * row_step_size)
for col in xrange(int(column_number)):
x = col_offset + (col * column_step_size)
roi = CatOvalRoi(x, y, spot_diameter, spot_diameter)
rois.append(roi)
return rois
def populate_roi_manager(image, rois, result_table):
crm = CatRoiManager(image, result_table)
for roi in rois:
crm.addRoi(roi)
crm.runCommand("Show All")
return crm
def process_image(orig_image, tissue_type):
image = orig_image.duplicate()
IJ.run(image, "8-bit", "")
IJ.run(image, "Subtract Background...", "rolling=50 light")
IJ.run(image, "Gaussian Blur...", "sigma=2")
if tissue_type == HARTS:
IJ.setAutoThreshold(image, "Mean")
elif tissue_type == HE:
IJ.setAutoThreshold(image, "Triangle")
else:
raise Exception("Oops this shouldn't happen, "
"please contact the developer.")
IJ.run(image, "Convert to Mask", "")
return image
def score_roi(mask, roi, overlapLimit=0):
roi.setImage(None)
mask.setRoi(roi)
stats = mask.getStatistics(
Measurements.AREA_FRACTION | Measurements.LIMIT, 2)
return stats.areaFraction > overlapLimit
def mark_tissue(image, tissue_type, rois, overlapLimit=0):
bin_mask = process_image(image, tissue_type)
for roi in rois:
if score_roi(bin_mask, roi, overlapLimit):
roi.classification = CatOvalRoi.TISSUE
roi.setFillColor(Color(0.0, 0.6, 0.0, 0.5))
bin_mask.flush()
def run():
image = IJ.getImage()
result_table = None
win = WindowManager.getFrame(CatRoiManager.RESULT_TITLE)
if win is not None:
result_table = win.getTextPanel().getResultsTable()
else:
result_table = ResultsTable()
# Grid Parameters
spot_diameter = 12
row_step_size = 50
column_step_size = 50
alternate_rows_offset = False
scaled_units = False
# Analysis Parameters
classify_tissue = True
roi_overlap = 50
tissue_types = [HARTS, HE]
gd = GenericDialog("Analysis Parameters")
gd.addMessage("Grid Paramaters")
gd.addNumericField("Spot diameter", spot_diameter, 1)
gd.addNumericField("Row step size", row_step_size, 1)
gd.addNumericField("Column step size", column_step_size, 1)
gd.addCheckbox("Offset rows", alternate_rows_offset)
gd.addCheckbox("Use scaled units", scaled_units)
gd.addMessage("Analysis Parameters")
gd.addCheckbox("Classify tissue", classify_tissue)
gd.addNumericField("ROI overlap limit (%)", roi_overlap, 0)
gd.addRadioButtonGroup("Tissue type", tissue_types, len(tissue_types), 1,
HARTS)
gd.showDialog()
if gd.wasCanceled():
return
spot_diameter = gd.getNextNumber()
row_step_size = gd.getNextNumber()
column_step_size = gd.getNextNumber()
alternate_rows_offset = gd.getNextBoolean()
scaled_units = gd.getNextBoolean()
classify_tissue = gd.getNextBoolean()
roi_overlap = gd.getNextNumber()/100
tissue_type = gd.getNextRadioButton()
rois = create_grid(image, spot_diameter, row_step_size,
column_step_size, alternate_rows_offset, scaled_units)
if classify_tissue:
mark_tissue(image, tissue_type, rois, roi_overlap)
rm = populate_roi_manager(image, rois, result_table)
run()
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment