Skip to content
Snippets Groups Projects
Commit c4deda24 authored by Laurent Guerard's avatar Laurent Guerard
Browse files

Refactor script 2c to use `imcflibs`

parent 02a7a4ea
No related branches found
No related tags found
No related merge requests found
# this is a python rewrite of the original ijm published at # @ String (visibility=MESSAGE, value="<html><b> Welcome to Myosoft! </b></html>") msg1
# https://github.com/Hyojung-Choo/Myosoft/blob/Myosoft-hub/Scripts/central%20nuclei%20counter.ijm # @ File (label="Select fiber-ROIs zip-file", style="file") roi_zip
# @ File (label="Select image file", description="select your image") path_to_image
# IJ imports
# TODO: are the imports RoiManager and ResultsTable needed when using the services?
from ij import IJ, WindowManager as wm
from ij.plugin import Duplicator, RoiEnlarger, RoiScaler
from trainableSegmentation import WekaSegmentation
from de.biovoxxel.toolbox import Extended_Particle_Analyzer
# Bio-formats imports
from loci.plugins import BF
from loci.plugins.in import ImporterOptions
# python imports
import time
import os
#@ String (visibility=MESSAGE, value="<html><b> Welcome to Myosoft! </b></html>") msg1
#@ File (label="Select fiber-ROIs zip-file", style="file") roi_zip
#@ File (label="Select image file", description="select your image") path_to_image
#@ File (label="Select directory for output", style="directory") output_dir #@ File (label="Select directory for output", style="directory") output_dir
#@ Boolean (label="close image after processing", description="tick this box when using batch mode", value=False) close_raw #@ Boolean (label="close image after processing", description="tick this box when using batch mode", value=False) close_raw
#@ String (visibility=MESSAGE, value="<html><b> channel positions in the hyperstack </b></html>") msg5 #@ String (visibility=MESSAGE, value="<html><b> channel positions in the hyperstack </b></html>") msg5
...@@ -28,134 +10,26 @@ import os ...@@ -28,134 +10,26 @@ import os
#@ Integer (label="minimum fiber intensity (0=auto)", description="0 = automatic threshold detection", value=0) min_fiber_intensity_1 #@ Integer (label="minimum fiber intensity (0=auto)", description="0 = automatic threshold detection", value=0) min_fiber_intensity_1
#@ Integer (label="minimum fiber intensity (0=auto)", description="0 = automatic threshold detection", value=0) min_fiber_intensity_2 #@ Integer (label="minimum fiber intensity (0=auto)", description="0 = automatic threshold detection", value=0) min_fiber_intensity_2
#@ Integer (label="minimum fiber intensity (0=auto)", description="0 = automatic threshold detection", value=0) min_fiber_intensity_3 #@ Integer (label="minimum fiber intensity (0=auto)", description="0 = automatic threshold detection", value=0) min_fiber_intensity_3
#@ ResultsTable rt # @ ResultsTable rt
#@ RoiManager rm # @ RoiManager rm
def fix_ij_options():
"""Configure ImageJ (IJ) to a predefined state.
This function sets various options in ImageJ to ensure a consistent
environment for image processing tasks. It includes settings for appearance,
color management, binary image options, and file output format.
"""
# Disable inverting LUT (Look-Up Table) for images
IJ.run("Appearance...", " menu=0 16-bit=Automatic")
# Set foreground color to be white and background color to be black, with red for selections
IJ.run("Colors...", "foreground=white background=black selection=red")
# Enable black background for binary images and pad edges when eroding
IJ.run("Options...", "black pad")
# Set saving format to .txt files, saving both columns and rows
IJ.run("Input/Output...", "file=.txt save_column save_row")
# Set "Black Background" option for binary operations # ─── Requirements ─────────────────────────────────────────────────────────────
IJ.run("Options...", "black")
# Enable scaling when converting images between types
IJ.run("Conversions...", "scale")
def fix_ij_dirs(path):
"""Replace backslashes with forward slashes in directory paths.
This function takes a directory path obtained from a dialogue or script
parameter and returns a more robust path with forward slashes as separators.
Parameters
----------
path : str
A directory path
Returns
-------
str
A directory path with forward slashes as separators
"""
fixed_path = str(path).replace("\\", "/")
# fixed_path = fixed_path + "/"
return fixed_path # List of update sites needed for the code
# * IMCF
# ─── Imports ──────────────────────────────────────────────────────────────────
def open_image_with_BF(path_to_file):
"""Open the first image from a file using Bio-Formats.
This function utilizes Bio-Formats to open an image file and returns import time
the first image contained within the file. The image is opened in
grayscale mode with autoscaling enabled.
Parameters
----------
path_to_file : str
The file path to the image file to be opened.
Returns
-------
ImagePlus
The first image contained in the specified file.
"""
# Create an ImporterOptions object for configuring the import process
options = ImporterOptions()
# Set the color mode to grayscale
options.setColorMode(ImporterOptions.COLOR_MODE_COMPOSITE )
# Enable autoscaling for the image
options.setAutoscale(True)
# Set the file path for the image to be opened
options.setId(path_to_file)
# Open the image(s) using Bio-Formats and store them in an array
imps = BF.openImagePlus(options)
# Return the first image in the array
return imps[0]
def fix_BF_czi_imagetitle(imp):
"""
Fix the title of an image read using the bio-formats importer.
The title is modified to remove the ".czi" extension and replace
spaces with underscores.
Parameters
----------
imp : ij.ImagePlus
The image to be processed.
Returns from ij import IJ
------- from ij import WindowManager as wm
str from imcflibs import pathtools
The modified title of the image. from imcflibs.imagej import bioformats as bf
""" from imcflibs.imagej import misc, prefs, resultstable, roimanager
# Get the short title of the image (without path)
image_title = os.path.basename(imp.getShortTitle())
# Remove the ".czi" extension
image_title = image_title.replace(".czi", "")
# Replace spaces with underscores
image_title = image_title.replace(" ", "_")
# Remove any double underscores
image_title = image_title.replace("_-_", "")
# Remove any double underscores
image_title = image_title.replace("__", "_")
# Remove any "#" characters
image_title = image_title.replace("#", "Series")
return image_title
def clear_ij_roi_manager(rm):
"""
Clear all ROIs from the RoiManager.
Parameters # ─── Functions ────────────────────────────────────────────────────────────────
----------
rm : ij.plugin.frame.RoiManager
a reference of the IJ-RoiManager
"""
# Run the "reset" command in the RoiManager to clear all ROIs
rm.runCommand('reset')
def get_threshold_from_method(imp, channel, method): def get_threshold_from_method(imp, channel, method):
...@@ -190,136 +64,47 @@ def get_threshold_from_method(imp, channel, method): ...@@ -190,136 +64,47 @@ def get_threshold_from_method(imp, channel, method):
return [lower_thr, upper_thr] return [lower_thr, upper_thr]
def measure_in_all_rois( imp, channel, rm ): def enhance_contrast(imp):
"""Measures in all ROIs on a given channel of imp all parameters that are set in IJ "Set Measurements". """Use "Auto" Contrast & Brightness settings in each channel of imp.
This function takes an ImagePlus (imp), a channel (integer, starts at 1), and a RoiManager (rm) as
parameters. It then sets the channel of the imp to the desired channel, deselects all ROIs in the
RoiManager, and measures in all ROIs of the RoiManager on the channel of the imp. The parameters
that are measured are the ones that are set in IJ "Set Measurements".
Parameters Parameters
---------- ----------
imp : ij.ImagePlus imp : ij.ImagePlus
the imp to measure on The imp on which to change C&B.
channel : int
the channel to measure in. starts at 1.
rm : ij.plugin.frame.RoiManager
a reference of the IJ-RoiManager
"""
# Set the channel of the imp to the desired channel
imp.setC(channel)
# Deselect all ROIs in the RoiManager
rm.runCommand(imp,"Deselect")
# Measure in all ROIs of the RoiManager on the channel of the imp
rm.runCommand(imp,"Measure")
def change_all_roi_color(rm, color):
"""
Change the color of all ROIs in the RoiManager.
Parameters
----------
rm : ij.plugin.frame.RoiManager
A reference to the IJ-RoiManager.
color : str
The desired color, e.g., "green", "red", "yellow", "magenta".
Returns
-------
None
"""
# Select all ROIs in the RoiManager
#rm.setSelectedIndexes(range(rm.getCount()))
rm.runCommand("Deselect")
# Change the color of all ROIs to the desired color
rm.runCommand("Set Color", color)
# # Deselect all ROIs again to finalize changes
# rm.runCommand("Deselect")
def change_subset_roi_color(rm, selected_rois, color):
"""
Change the color of selected ROIs in the RoiManager.
Parameters
----------
rm : ij.plugin.frame.RoiManager
A reference to the IJ-RoiManager.
selected_rois : list of int
Indices of ROIs in the RoiManager to change.
color : str
The desired color, e.g., "green", "red", "yellow", "magenta", etc.
"""
# Deselect all currently selected ROIs in the RoiManager
rm.runCommand("Deselect")
# Select the specified ROIs by their indices
rm.setSelectedIndexes(selected_rois)
# Change the color of the selected ROIs to the specified color
rm.runCommand("Set Color", color)
# Deselect all ROIs again to finalize changes
rm.runCommand("Deselect")
def show_all_rois_on_image(rm, imp):
""" """
Display all ROIs on the given image using the ROI Manager. for channel in range(imp.getDimensions()[2]):
imp.setC(channel + 1) # IJ channels start at 1
IJ.run(imp, "Enhance Contrast", "saturated=0.35")
Parameters
----------
rm : ij.plugin.frame.RoiManager
A reference to the IJ-RoiManager.
imp : ij.ImagePlus
The image on which to display the ROIs.
"""
# Show the image in the ImageJ window
imp.show()
# Use the ROI Manager to show all ROIs on the image
rm.runCommand(imp, "Show All")
def setup_defined_ij(rm, rt):
"""Set up a clean and defined Fiji user environment.
def save_all_rois(rm, target): This function configures the ImageJ environment to a predefined state by
"""Save all ROIs in the RoiManager as a zip file to the given target path. resetting the RoiManager and ResultsTable, and clearing the IJ log.
Parameters Parameters
---------- ----------
rm : ij.plugin.frame.RoiManager rm : ij.plugin.frame.RoiManager
A reference to the IJ-RoiManager. A reference to the IJ-RoiManager.
target : str rt : ij.measure.ResultsTable
The file path in which to save the ROIs, e.g., /my-images/resulting_rois.zip A reference to the IJ-ResultsTable.
""" """
# Save all ROIs in the RoiManager to the given target path # Configure IJ options to a predefined state
rm.runCommand("Save", target) prefs.set_default_ij_options()
# Reset the RoiManager to remove all existing ROIs
rm.runCommand("reset")
def save_selected_rois(rm, selected_rois, target): # Reset the ResultsTable to clear all previous results
""" rt.reset()
Save selected ROIs in the RoiManager as a zip file to the given target path.
Parameters # Clear the IJ log to ensure a fresh output window
---------- IJ.log("\\Clear")
rm : ij.plugin.frame.RoiManager
A reference to the IJ-RoiManager.
selected_rois : list of int
Indices of ROIs in the RoiManager to save.
target : str
The file path in which to save the ROIs, e.g., /my-images/resulting_rois_subset.zip
"""
# Deselect all currently selected ROIs in the RoiManager to ensure a clean start
rm.runCommand("Deselect")
# Select the specified ROIs by their indices
rm.setSelectedIndexes(selected_rois)
# Save the selected ROIs to the given target path as a zip file
rm.runCommand("save selected", target)
# Deselect all ROIs again to finalize changes and maintain a clean state
rm.runCommand("Deselect")
def select_positive_fibers( imp, channel, rm, min_intensity ): def measure_mean_of_rois(imp, channel, rm):
"""For all ROIs in the RoiManager, select ROIs based on intensity measurement in given channel of imp. """Measure the mean intensity of all ROIs in the given channel of imp.
See https://imagej.nih.gov/ij/developer/api/ij/process/ImageStatistics.html
Parameters Parameters
---------- ----------
...@@ -339,149 +124,35 @@ def select_positive_fibers( imp, channel, rm, min_intensity ): ...@@ -339,149 +124,35 @@ def select_positive_fibers( imp, channel, rm, min_intensity ):
""" """
imp.setC(channel) imp.setC(channel)
all_rois = rm.getRoisAsArray() all_rois = rm.getRoisAsArray()
selected_rois = []
for i, roi in enumerate(all_rois): for i, roi in enumerate(all_rois):
imp.setRoi(roi) imp.setRoi(roi)
stats = imp.getStatistics() stats = imp.getStatistics()
if stats.mean > min_intensity:
selected_rois.append(i)
return selected_rois
def open_rois_from_zip(rm, path):
"""
Open ROIs from a zip file and add them to the RoiManager.
Parameters
----------
rm : ij.plugin.frame.RoiManager
A reference to the IJ-RoiManager.
path : str
The file path to the ROI zip file.
"""
rm.runCommand("Open", path)
def preset_results_column( rt, column, value):
"""Pre-set all rows in given column of the IJ-ResultsTable with desired value.
Parameters
----------
rt : ij.measure.ResultsTable
A reference of the IJ-ResultsTable
column : str
The desired column. Will be created if it does not yet exist
value : str or float or int
The value to be set
"""
for i in range( rt.size() ):
rt.setValue(column, i, value)
rt.show("Results")
def add_results(rt, column, row, value):
"""Add a value in specified rows of a given column.
Parameters
----------
rt : ij.measure.ResultsTable
A reference to the IJ-ResultsTable.
column : str
The column in which to add the values.
row : list of int
The row numbers in which to add the values.
value : str, float, or int
The value to be set.
"""
# Iterate over each row index in the row list
for i in range(len(row)):
# Set the specified value in the given column and row
rt.setValue(column, row[i], value)
# Display the updated ResultsTable
rt.show("Results")
def enhance_contrast(imp):
"""Use "Auto" Contrast & Brightness settings in each channel of imp.
Parameters
----------
imp : ij.ImagePlus
The imp on which to change C&B.
"""
for channel in range(imp.getDimensions()[2]):
imp.setC(channel + 1) # IJ channels start at 1
IJ.run(imp, "Enhance Contrast", "saturated=0.35")
def renumber_rois(rm):
"""Rename all ROIs in the RoiManager according to their number.
The RoiManager uses 0-based indexing, so the first ROI is at index 0.
This function renames each ROI with its index (starting from 1).
Parameters
----------
rm : ij.plugin.frame.RoiManager
A reference to the IJ-RoiManager.
"""
number_of_rois = rm.getCount()
for roi in range( number_of_rois ):
rm.rename( roi, str(roi + 1) )
def setup_defined_ij(rm, rt):
"""Set up a clean and defined Fiji user environment.
This function configures the ImageJ environment to a predefined state by
resetting the RoiManager and ResultsTable, and clearing the IJ log.
Parameters
----------
rm : ij.plugin.frame.RoiManager
A reference to the IJ-RoiManager.
rt : ij.measure.ResultsTable
A reference to the IJ-ResultsTable.
"""
# Configure IJ options to a predefined state
fix_ij_options()
# Reset the RoiManager to remove all existing ROIs
rm.runCommand('reset')
# Reset the ResultsTable to clear all previous results
rt.reset()
# Clear the IJ log to ensure a fresh output window
IJ.log("\\Clear")
execution_start_time = time.time() execution_start_time = time.time()
setup_defined_ij(rm, rt) misc.timed_log("Script starting")
setup_defined_ij(rm, rt)
image_file_info = pathtools.parse_path(path_to_image)
# open image using Bio-Formats # open image using Bio-Formats
path_to_image = fix_ij_dirs(path_to_image) raw = bf.import_image(image_file_info["full"])
raw = open_image_with_BF(path_to_image) raw.hide()
raw.hide()
# get image info # get image info
raw_image_calibration = raw.getCalibration() raw_image_calibration = raw.getCalibration()
raw_image_title = fix_BF_czi_imagetitle(raw) raw_image_title = misc.sanitize_image_title(raw)
# take care of paths and directories roi_file_info = pathtools.parse_path(roi_zip)
input_rois_path = fix_ij_dirs( roi_zip )
output_dir = fix_ij_dirs(output_dir) + "/2c_fibertyping"
if not os.path.exists( str(output_dir) ): # take care of paths and directories
os.makedirs( str(output_dir) ) input_rois_path = roi_file_info["full"]
output_dir = pathtools.join2(output_dir, "2c_fibertyping")
pathtools.create_directory(output_dir)
# open ROIS and show on image roimanager.load_rois_from_zip(rm, input_rois_path)
open_rois_from_zip( rm, str(input_rois_path) ) roimanager.change_roi_color(rm, "blue")
change_all_roi_color(rm, "blue")
# show_all_rois_on_image( rm, raw )
# update the log for the user # update the log for the user
IJ.log( "Now working on " + str(raw_image_title) ) IJ.log( "Now working on " + str(raw_image_title) )
...@@ -498,6 +169,7 @@ IJ.log( " -- settings used -- ") ...@@ -498,6 +169,7 @@ IJ.log( " -- settings used -- ")
IJ.run("Set Measurements...", "area perimeter shape feret's redirect=None decimal=4") IJ.run("Set Measurements...", "area perimeter shape feret's redirect=None decimal=4")
IJ.run("Clear Results", "") IJ.run("Clear Results", "")
measure_in_all_rois( raw, fiber_channel_1, rm ) measure_in_all_rois( raw, fiber_channel_1, rm )
roimanager.measure_in_all_rois(raw, fiber_channel_1, rm)
# loop through the fiber channels, check if positive, add info to results table # loop through the fiber channels, check if positive, add info to results table
all_fiber_channels = [fiber_channel_1, fiber_channel_2, fiber_channel_3] all_fiber_channels = [fiber_channel_1, fiber_channel_2, fiber_channel_3]
...@@ -505,18 +177,65 @@ all_min_fiber_intensities = [min_fiber_intensity_1, min_fiber_intensity_2, min_f ...@@ -505,18 +177,65 @@ all_min_fiber_intensities = [min_fiber_intensity_1, min_fiber_intensity_2, min_f
roi_colors = ["green", "orange", "red"] roi_colors = ["green", "orange", "red"]
all_fiber_subsets =[ [], [], [] ] all_fiber_subsets =[ [], [], [] ]
for index, fiber_channel in enumerate(all_fiber_channels): for index, fiber_channel in enumerate(all_fiber_channels):
if fiber_channel > 0: if fiber_channel > 0:
preset_results_column( rt, "channel " + str(fiber_channel) + " positive (" + roi_colors[index] + ")", "NO" ) resultstable.preset_results_column(
if all_min_fiber_intensities[index] == 0: rt, "channel %s mean intensity" % fiber_channel, 0
all_min_fiber_intensities[index] = get_threshold_from_method(raw, fiber_channel, "Mean")[0] )
IJ.log( "fiber channel " + str(fiber_channel) + " intensity threshold: " + str(all_min_fiber_intensities[index]) ) mean_intensities = measure_mean_of_rois(raw, fiber_channel, rm)
positive_fibers = select_positive_fibers( raw, fiber_channel, rm, all_min_fiber_intensities[index] ) resultstable.add_results_to_resultstable(
all_fiber_subsets[index] = positive_fibers rt,
if len(positive_fibers) > 0: "channel %s mean intensity" % fiber_channel,
change_subset_roi_color(rm, positive_fibers, roi_colors[index]) range(rm.getCount()),
save_selected_rois( rm, positive_fibers, output_dir + "/" + raw_image_title + "_positive_fiber_rois_c" + str( fiber_channel ) + ".zip") mean_intensities,
add_results( rt, "channel " + str(fiber_channel) + " positive (" + roi_colors[index] + ")", positive_fibers, "YES") )
resultstable.preset_results_column(
rt,
"channel "
+ str(fiber_channel)
+ " positive ("
+ roi_colors[index]
+ ")",
"NO",
)
if all_min_fiber_intensities[index] == 0:
all_min_fiber_intensities[index] = get_threshold_from_method(
raw, fiber_channel, "Mean"
)[0]
IJ.log(
"fiber channel "
+ str(fiber_channel)
+ " intensity threshold: "
+ str(all_min_fiber_intensities[index])
)
positive_fibers = roimanager.select_rois_above_min_intensity(
raw, fiber_channel, rm, all_min_fiber_intensities[index]
)
all_fiber_subsets[index] = positive_fibers
if len(positive_fibers) > 0:
roimanager.change_roi_color(rm, roi_colors[index], positive_fibers)
roimanager.save_rois_to_zip(
rm,
pathtools.join2(
output_dir,
raw_image_title
+ "_positive_fiber_rois_c"
+ str(fiber_channel)
+ ".zip",
),
positive_fibers,
)
resultstable.add_results_to_resultstable(
rt,
"channel "
+ str(fiber_channel)
+ " positive ("
+ roi_colors[index]
+ ")",
positive_fibers,
"YES",
)
# single positive # single positive
positive_c1 = all_fiber_subsets[0] positive_c1 = all_fiber_subsets[0]
...@@ -537,22 +256,34 @@ channels = [ ...@@ -537,22 +256,34 @@ channels = [
(positive_c1_c2_c3, [fiber_channel_1, fiber_channel_2, fiber_channel_3], "white") (positive_c1_c2_c3, [fiber_channel_1, fiber_channel_2, fiber_channel_3], "white")
] ]
for positives, ch_nums, color in channels: for positives, ch_nums, color in channels:
if positives: if positives:
ch_str = ",".join(map(str, ch_nums)) ch_str = ",".join(map(str, ch_nums))
color_label = "channel %s positive (%s)" % (ch_str, color) color_label = "channel %s positive (%s)" % (ch_str, color)
preset_results_column(rt, color_label.replace(',', '-'), "NO") resultstable.preset_results_column(rt, color_label.replace(",", "-"), "NO")
change_subset_roi_color(rm, positives, color) roimanager.change_roi_color(rm, color, positives)
save_selected_rois(rm, positives, "%s/%s_positive_fiber_rois_c%s.zip" % (output_dir, raw_image_title, '_c'.join(map(str, ch_nums)))) roimanager.save_rois_to_zip(
add_results(rt, color_label.replace(',', '-'), positives, "YES") rm,
"%s/%s_positive_fiber_rois_c%s.zip"
# save all results together % (output_dir, raw_image_title, "_c".join(map(str, ch_nums))),
save_all_rois( rm, output_dir + "/" + raw_image_title + "_all_fiber_type_rois_color-coded.zip" ) positives,
rt.save(output_dir + "/" + raw_image_title + "_fibertyping_results.csv") )
resultstable.add_results_to_resultstable(
# dress up the original image, save a overlay-png, present original to the user rt, color_label.replace(",", "-"), positives, "YES"
raw.show() )
show_all_rois_on_image( rm, raw )
# save all results together
roimanager.save_rois_to_zip(
rm,
pathtools.join2(
output_dir, raw_image_title + "_all_fiber_type_rois_color-coded.zip"
),
)
rt.save(pathtools.join2(output_dir, raw_image_title + "_fibertyping_results.csv"))
# dress up the original image, save a overlay-png, present original to the user
raw.show()
roimanager.show_all_rois_on_image(rm, raw)
# raw.setDisplayMode(IJ.COMPOSITE) # raw.setDisplayMode(IJ.COMPOSITE)
enhance_contrast( raw ) enhance_contrast( raw )
IJ.run("From ROI Manager", "") # ROIs -> overlays so they show up in the saved png IJ.run("From ROI Manager", "") # ROIs -> overlays so they show up in the saved png
...@@ -560,13 +291,13 @@ qc_duplicate = raw.duplicate() ...@@ -560,13 +291,13 @@ qc_duplicate = raw.duplicate()
IJ.saveAs(qc_duplicate, "PNG", output_dir + "/" + raw_image_title + "_fibertyping") IJ.saveAs(qc_duplicate, "PNG", output_dir + "/" + raw_image_title + "_fibertyping")
qc_duplicate.close() qc_duplicate.close()
wm.toFront( raw.getWindow() ) wm.toFront( raw.getWindow() )
# IJ.run("Remove Overlay", "")
# raw.setDisplayMode(IJ.GRAYSCALE) IJ.log(
# show_all_rois_on_image( rm, raw ) "Script finished. Total runtime [HH:MM:SS.ss]: "
total_execution_time_min = (time.time() - execution_start_time) / 60.0 + misc.elapsed_time_since(execution_start_time)
IJ.log("total time in minutes: " + str(total_execution_time_min)) )
IJ.log( "~~ all done ~~" ) IJ.log("~~ all done ~~")
IJ.selectWindow("Log") IJ.selectWindow("Log")
IJ.saveAs("Text", str(output_dir + "/" + raw_image_title + "_fibertyping_Log")) IJ.saveAs("Text", str(output_dir + "/" + raw_image_title + "_fibertyping_Log"))
if close_raw == True: if close_raw:
raw.close() raw.close()
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment