Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ RUN apt-get update && \
COPY . $mc_path/
WORKDIR $mc_path

RUN pip cache purge
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why we need this?

RUN pip install --no-cache-dir --upgrade --ignore-installed pip setuptools && \
pip install --no-cache-dir . && \
pip install --no-cache-dir tensorboard cmake onnx && \
Expand All @@ -134,6 +135,7 @@ WORKDIR $mc_path/multic/cli
RUN python -m slicer_cli_web.cli_list_entrypoint --list_cli
RUN python -m slicer_cli_web.cli_list_entrypoint MultiCompartmentSegment --help
RUN python -m slicer_cli_web.cli_list_entrypoint FeatureExtraction --help
RUN python -m slicer_cli_web.cli_list_entrypoint FeatureExtraction-HG --help


ENTRYPOINT ["/bin/bash", "docker-entrypoint.sh"]
183 changes: 183 additions & 0 deletions multic/cli/FeatureExtraction-HG/FeatureExtraction-HG.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import sys
import os, girder_client
import numpy as np
import pandas as pd
from tiffslide import TiffSlide
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TiffSlide not used please remove

from ctk_cli import CLIArgumentParser
import cv2
import csv
from skimage import measure
from skimage.feature import graycomatrix, graycoprops
from skimage.segmentation import find_boundaries
from skimage.measure import regionprops
from skimage.color import rgb2gray


sys.path.append("..")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed please remove



def calculate_color_features(image, compartment_mask):

feature_values = {}
compartment_names = ['Luminal space compartment', 'PAS compartment', 'Nuclei compartment']

for compartment in range(3): # As there are 3 compartments

compartment_pixels = image[compartment_mask == (compartment + 1)]

if len(compartment_pixels) > 0:

# Mean Color

mean_color = np.mean(compartment_pixels, axis=0)

for i, channel_value in enumerate(mean_color):

feature_values[f"Mean {['Red', 'Green', 'Blue'][i]} {compartment_names[compartment]}"] = channel_value

# Standard Deviation Color

std_dev_color = np.std(compartment_pixels, axis=0)

for i, channel_value in enumerate(std_dev_color):

feature_values[f"Standard Deviation {['Red', 'Green', 'Blue'][i]} {compartment_names[compartment]}"] = channel_value

else:

# If compartment has no pixels, set values to zero

for i in range(3):

feature_values[f"Mean {['Red', 'Green', 'Blue'][i]} {compartment_names[compartment]}"] = 0.0

feature_values[f"Standard Deviation {['Red', 'Green', 'Blue'][i]} {compartment_names[compartment]}"] = 0.0

return feature_values



# Texture Features

def calculate_texture_features(image, compartment_mask):

feature_values = {}

texture_feature_names = ['Contrast', 'Homogeneity', 'Correlation', 'Energy']

compartment_names = ['Luminal space compartment', 'PAS compartment', 'Nuclei compartment']

for compartment in range(3): # As there are 3 compartments

compartment_pixels = (compartment_mask == compartment).astype(np.uint8)

compartment_image = cv2.bitwise_and(image, image, mask=compartment_pixels)

compartment_image_gray = rgb2gray(compartment_image)

compartment_image_gray_uint = (compartment_image_gray * 255).astype(np.uint8)

texture_matrix = graycomatrix(compartment_image_gray_uint, [1], [0], levels=256, symmetric=True, normed=True)

for i, texture_name in enumerate(texture_feature_names):

texture_feature_value = graycoprops(texture_matrix, texture_name.lower())

feature_values[f"{texture_name} {compartment_names[compartment]}"] = texture_feature_value[0][0]

return feature_values



def calculate_features(image, compartment_mask):

color_features_dict = calculate_color_features(image, compartment_mask)

texture_features_dict = calculate_texture_features(image, compartment_mask)

all_features = {

#"Distance Transform Features": distance_transform_features_list,

"Color Features": color_features_dict,

"Texture Features": texture_features_dict,

#"Morphological Features": morphological_features_list

}

return all_features


def main(args):
# Load the compartment mask and rgb image

compartment_mask = cv2.imread(args.sub_compartment_mask, cv2.IMREAD_GRAYSCALE)

image = cv2.imread(args.input_file, cv2.IMREAD_COLOR)

# Assigning custom labels to compartments. For easier calculations

# 29 - blue = nuclei,

# 76- red = PAS

# 149 - green = Luminal space

compartment_mask[compartment_mask == 29] = 3

compartment_mask[compartment_mask == 76] = 2

compartment_mask[compartment_mask == 149] = 1

compartment_mask[compartment_mask > 3] = 0

all_features = calculate_features(image, compartment_mask)

# Print the color features dictionary

print(all_features)

# Write the color features to a CSV file

csv_filename = args.basedir+"/tmp/features_output.csv"

os.makedirs(args.basedir+"/tmp/")

folder = args.basedir

gc_folder_id = folder.split('/')[-2]

gc = girder_client.GirderClient(apiUrl = args.girderApiUrl)

gc.setToken(args.girderToken)

f = open(csv_filename, 'w')
f.close()

with open(csv_filename, mode='w', newline='') as csv_file:

writer = csv.writer(csv_file)

for feature_name, value in all_features.items():

writer.writerow([feature_name, ""])

if isinstance(value, dict):

for sub_feature_name, sub_value in value.items():

writer.writerow(["", f"{sub_feature_name}", sub_value])

file_name = csv_filename.split('/')[-1]

print("Uploading to DSA")
gc.uploadFileToFolder(gc_folder_id, csv_filename, name = file_name)
print("Done")






Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove extra space end of the file, just leave 1 extra line

94 changes: 94 additions & 0 deletions multic/cli/FeatureExtraction-HG/FeatureExtraction-HG.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<executable>
<category>HistomicsTK</category>
<title>Extract Features</title>
<description>Extracts a variety of features an annotated whole-slide image</description>
<version>0.1.0</version>
<documentation-url>https://github.com/SarderLab/deeplab-WSI</documentation-url>
<license>Apache 2.0</license>
<contributor>Sayat Mimar (UFL)</contributor>
<acknowledgements>This work is part of efforts in digital pathology by the Sarder Lab: University Of Florida.</acknowledgements>
<parameters>
<label>IO</label>
<description>Input/output parameters</description>
<directory>
<name>base_dir</name>
<label>WSI folder</label>
<description>Select the folder containing the annotated slide to be analyzed</description>
<channel>input</channel>
<index>0</index>
</directory>
<image>
<name>input_file</name>
<label>input_file</label>
<description>input file</description>
<channel>input</channel>
<index>1</index>
</image>
<double>
<name>downsample_factor</name>
<label>downsample_factor</label>
<description>downsample_factor</description>
<default>1.0</default>
<channel>input</channel>
<index>2</index>
</double>
<file fileExtensions=".xlsx" reference="output">
<name>output_filename</name>
<label>Output Excel Filename</label>
<description>Select the name and location of the Excel file produced. By default this will be saved in your Private folder.</description>
<channel>output</channel>
<index>3</index>
</file>
<file>
<name>sub_compartment_mask</name>
<label>sub_compartment_mask</label>
<description>sub_compartment mask</description>
<channel>input</channel>
<index>4</index>
</file>
</parameters>
<parameters advanced="true">
<label>Deconvolution Thresholds</label>
<description>Deconvolution Thresholds</description>
<integer>
<name>h_threshold</name>
<label>h-threhsold</label>
<description>h-threhshold for glomeruli and sclerotic glomeruli</description>
<longflag>h_threshold</longflag>
<default>160</default>
</integer>
<double>
<name>saturation_threshold</name>
<label>Saturation Threshold</label>
<description>Satruation Threshold for glomeruli and sclerotic glomeruli</description>
<longflag>saturation_threshold</longflag>
<default>0.3</default>
</double>
<double>
<name>whitespace_threshold</name>
<label>Whitespace Threshold</label>
<description>Whitespace Threshold for tubules</description>
<longflag>whitespace_threshold</longflag>
<default>0.88</default>
</double>
</parameters>
<parameters advanced="true">
<label>Girder API URL and Key</label>
<description>A Girder API URL and token for Girder client</description>
<string>
<name>girderApiUrl</name>
<longflag>api-url</longflag>
<label>Girder API URL</label>
<description>A Girder API URL (e.g., https://girder.example.com:443/api/v1)</description>
<default></default>
</string>
<string>
<name>girderToken</name>
<longflag>token</longflag>
<label>Girder API Token</label>
<description>A Girder token</description>
<default></default>
</string>
</parameters>
</executable>
3 changes: 3 additions & 0 deletions multic/cli/slicer_cli_list.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@
},
"FeatureExtraction": {
"type" : "python"
},
"FeatureExtraction-HG": {
"type" : "python"
}
}