1. load libraries

2. Load Data into Seurat


# Patient IDs and H5 files (replace with your actual file paths)

# Define paths to your H5 files
h5_files <- list(
  "SS1" = "../data/SS/GSM5687767_SZ22raw_feature_bc_matrix.h5", #SS-Patient
  "SS2" = "../data/SS/GSM5687769_SZ16raw_feature_bc_matrix.h5", #SS-Patient
  "SS3" = "../data/SS/GSM5687772_SZ30raw_feature_bc_matrix.h5", #SS-Patient
  "HC1_2" = "../data/Disease_state_Healthy_Control/GSM5538785_SC327raw_feature_bc_matrix.h5", # disease state: Healthy control/tissue: Skin tumor
  "HC3_4" = "../data/Disease_state_Healthy_Control/GSM5538786_SC374raw_feature_bc_matrix.h5" # disease state: Healthy control/tissue: Skin tumor
)

# Read and create individual Seurat objects
seurat_list <- list()

3. Load each patient sample with metadata


seurat_list <- list()

for (patient in names(h5_files)) {
  raw_data <- Read10X_h5(h5_files[[patient]])

  # If raw_data is a list (multi-modal), extract "Gene Expression"
  if (is.list(raw_data)) {
    if (!"Gene Expression" %in% names(raw_data)) {
      stop(paste("Gene Expression modality not found in patient", patient))
    }
    raw_counts <- raw_data[["Gene Expression"]]
  } else {
    # Single modality
    raw_counts <- raw_data
  }

  seu <- CreateSeuratObject(
    counts = raw_counts,
    min.cells = 3,
    min.features = 200,
    project = patient
  )

  seu$patient_id <- patient
  seurat_list[[patient]] <- seu
}
Genome matrix has multiple modalities, returning a list of matrices for this genome
Genome matrix has multiple modalities, returning a list of matrices for this genome
Genome matrix has multiple modalities, returning a list of matrices for this genome
Genome matrix has multiple modalities, returning a list of matrices for this genome

4. Merge All Patients into One Seurat Object


# Step 2: Merge into combined Seurat object
combined_seu <- merge(
  seurat_list[[1]],
  y = seurat_list[-1],
  add.cell.ids = names(seurat_list),
  project = "SS_HC_Merged"
)

# Step 3: Normalization (LogNormalize)
combined_seu <- NormalizeData(combined_seu, normalization.method = "LogNormalize", scale.factor = 10000)
Normalizing layer: counts.SS1
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Normalizing layer: counts.SS2
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Normalizing layer: counts.SS3
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Normalizing layer: counts.HC1_2
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Normalizing layer: counts.HC3_4
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
# Step 4: Variable features selection
combined_seu <- FindVariableFeatures(combined_seu, selection.method = "vst", nfeatures = 2000)
Finding variable features for layer counts.SS1
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Finding variable features for layer counts.SS2
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Finding variable features for layer counts.SS3
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Finding variable features for layer counts.HC1_2
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Finding variable features for layer counts.HC3_4
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
# Step 5: Scaling
combined_seu <- ScaleData(combined_seu)
Centering and scaling data matrix

  |                                                                                                     
  |                                                                                               |   0%
  |                                                                                                     
  |================================================                                               |  50%
  |                                                                                                     
  |===============================================================================================| 100%
# Step 6: PCA
combined_seu <- RunPCA(combined_seu, features = VariableFeatures(combined_seu))
PC_ 1 
Positive:  LYZ, IFI30, FTH1, S100A8, FCER1G, FTL, CTSB, AIF1, TYROBP, FCN1 
       S100A9, CYBB, SOD2, C15orf48, MMP9, CTSS, SPI1, CST3, TYMP, HLA-DRA 
       CD68, PSAP, SMIM25, CD14, SERPINA1, SAT1, GRINA, NPC2, CTSD, CTSZ 
Negative:  IL32, CD3E, IFITM1, RPSA, RPS18, CD7, CD3D, LTB, RPL3, CD52 
       RPS5, TRBC2, RPS6, EEF1A1, RPLP0, GZMA, PPIA, CORO1A, CST7, NPM1 
       RPL4, EEF1B2, TRAC, CTSW, GZMB, NKG7, SH2D2A, STMN1, HSPA8, CRIP1 
PC_ 2 
Positive:  HBB, LTB, MT-CO1, TYROBP, S100A8, CD7, FTH1, FTL, MT-CO2, LYZ 
       TRBC2, IFI30, CD52, FCER1G, SELL, IL32, CD3E, GNLY, TMSB4X, TRAC 
       LCP1, NCF1, S100A9, CORO1A, NKG7, CD3D, GBP5, CST7, RPS4Y1, SOCS1 
Negative:  ADIRF, TIMP3, IGFBP7, CAV1, SPARC, CALD1, MGP, ID1, GSN, CD34 
       MYL9, CAVIN3, SERPINH1, IFI27, RAMP2, TSC22D1, CRIP2, TM4SF1, GNG11, CD9 
       ARHGAP29, RHOB, SELENOP, HSPA1B, HSPA1A, S100A13, PLVAP, PLK2, HES1, LMNA 
PC_ 3 
Positive:  TYMS, GZMA, SRGN, GZMB, PCLAF, PFN1, NKG7, RRM2, ACTB, TYROBP 
       TMSB4X, GNLY, CCL5, LCP1, CTSW, S100A4, GZMH, CST7, STMN1, TPI1 
       TK1, TUBA1B, PRF1, CTSC, MKI67, ZWINT, CLSPN, CCL4, UHRF1, UBE2C 
Negative:  IGHM, MS4A1, LINC00926, CD79A, GAS5, IGKC, CCR7, MEF2C, NIBAN3, CD79B 
       BACH2, SESN3, EEF1A1, CD19, SPIB, BANK1, GNG7, AFF3, ARHGAP24, RPS18 
       RUBCNL, PKIG, SNHG29, SELL, CALD1, ID3, ADIRF, BCL11A, IGLC2, TIMP3 
PC_ 4 
Positive:  HLA-DQB1, EEF1A1, LTB, CD83, COTL1, RPS6, RPL4, RPLP0, RPS18, CD74 
       BIRC3, CD52, HLA-DPB1, RPS5, HLA-DRB1, RPL3, RPSA, HLA-DQA1, EEF1B2, RGS1 
       JUNB, HLA-DPA1, PIM3, GPR183, NR4A2, ZFP36, HSPA8, MS4A1, IGHM, CD79A 
Negative:  GNLY, NKG7, CCL5, KLRD1, GZMB, HBB, GZMH, PRF1, GZMA, CCL4 
       IGFBP7, CTSW, SPARC, KLRC1, CALD1, CST7, KLRK1, MYL9, KLRF1, C1orf21 
       HOPX, TRDC, MGP, XCL1, COL6A2, CMC1, TIMP3, CLIC3, EOMES, XCL2 
PC_ 5 
Positive:  IGHM, MS4A1, LINC00926, CD79A, IGKC, MEF2C, NIBAN3, SPIB, BANK1, CD79B 
       CD19, RUBCNL, HVCN1, GNG7, ARHGAP24, BCL11A, AFF3, CD72, HLA-DQA1, IGLC2 
       HLA-DPB1, FAM30A, BACH2, ADAM28, JCHAIN, RALGPS2, CD83, CD74, LHPP, BLNK 
Negative:  IL32, S100A4, CD3E, CD3D, S100A6, TNFRSF4, AQP3, MAL, S100A10, S100A11 
       VIM, TRAC, LGALS1, IL7R, IL2RA, COTL1, CD7, CORO1B, LAT, LIME1 
       LINC01943, TNFRSF25, LTB, GATA3, LGALS3, FOXP3, LINC02195, TRAV21, TRAT1, SIT1 
# Step 7: Elbow Plot to decide number of PCs
ElbowPlot(combined_seu)



# # Step 8: JackStraw (optional but useful for PC significance)
# combined_seu <- JackStraw(combined_seu, num.replicate = 100)
# combined_seu <- ScoreJackStraw(combined_seu, dims = 1:20)
# JackStrawPlot(combined_seu, dims = 1:20)

5. QC


# Calculate percent.mt for each cell
combined_seu[["percent.mt"]] <- PercentageFeatureSet(combined_seu, pattern = "^MT-")

# Filter cells with too few or too many genes
combined_seu <- subset(combined_seu, 
                       subset = nFeature_RNA > 200 & 
                                nFeature_RNA < 8000 & 
                                percent.mt < 10)


VlnPlot(combined_seu, features = c("nFeature_RNA", 
                                    "nCount_RNA", 
                                    "percent.mt"), 
                                      ncol = 3)
Warning: The `slot` argument of `FetchData()` is deprecated as of SeuratObject 5.0.0.
Please use the `layer` argument instead.Warning: `PackageCheck()` was deprecated in SeuratObject 5.0.0.
Please use `rlang::check_installed()` instead.

VlnPlot(combined_seu, features = c("nFeature_RNA", 
                                         "nCount_RNA", 
                                         "percent.mt",
                                         "percent.rb"), 
                            ncol = 4, pt.size = 0.1) & 
              theme(plot.title = element_text(size=10))
Warning: The following requested variables were not found: percent.rb

FeatureScatter(combined_seu, 
               feature1 = "nCount_RNA", 
               feature2 = "nFeature_RNA") +
  geom_smooth(method = 'lm')

##FeatureScatter is typically used to visualize feature-feature relationships ##for anything calculated by the object, ##i.e. columns in object metadata, PC scores etc.


FeatureScatter(combined_seu, 
               feature1 = "nCount_RNA", 
               feature2 = "percent.mt")+
  geom_smooth(method = 'lm')


FeatureScatter(combined_seu, 
               feature1 = "nCount_RNA", 
               feature2 = "nFeature_RNA")+
  geom_smooth(method = 'lm')

6. PCA Visualization




# TEST-1
# given that the output of RunPCA is "pca"
# replace "so" by the name of your seurat object

pct <- combined_seu[["pca"]]@stdev / sum(combined_seu[["pca"]]@stdev) * 100
cumu <- cumsum(pct) # Calculate cumulative percents for each PC
# Determine the difference between variation of PC and subsequent PC
co2 <- sort(which((pct[-length(pct)] - pct[-1]) > 0.1), decreasing = T)[1] + 1
# last point where change of % of variation is more than 0.1%. -> co2
co2
[1] 26
# TEST-2
# get significant PCs
stdv <- combined_seu[["pca"]]@stdev
sum.stdv <- sum(combined_seu[["pca"]]@stdev)
percent.stdv <- (stdv / sum.stdv) * 100
cumulative <- cumsum(percent.stdv)
co1 <- which(cumulative > 90 & percent.stdv < 5)[1]
co2 <- sort(which((percent.stdv[1:length(percent.stdv) - 1] - 
                       percent.stdv[2:length(percent.stdv)]) > 0.1), 
              decreasing = T)[1] + 1
min.pc <- min(co1, co2)
min.pc
[1] 26
# Create a dataframe with values
plot_df <- data.frame(pct = percent.stdv, 
           cumu = cumulative, 
           rank = 1:length(percent.stdv))

# Elbow plot to visualize 
  ggplot(plot_df, aes(cumulative, percent.stdv, label = rank, color = rank > min.pc)) + 
  geom_text() + 
  geom_vline(xintercept = 90, color = "grey") + 
  geom_hline(yintercept = min(percent.stdv[percent.stdv > 5]), color = "grey") +
  theme_bw()

NA
NA
NA

8. Clustering (resolution = 1.2)



combined_seu <- FindNeighbors(combined_seu, dims = 1:26)
Computing nearest neighbor graph
Computing SNN
combined_seu <- FindClusters(combined_seu, resolution = 0.2)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 33494
Number of edges: 1281442

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.9684
Number of communities: 25
Elapsed time: 4 seconds
# Run UMAP
combined_seu <- RunUMAP(combined_seu, dims = 1:26)
10:58:08 UMAP embedding parameters a = 0.9922 b = 1.112
10:58:08 Read 33494 rows and found 26 numeric columns
10:58:08 Using Annoy for neighbor search, n_neighbors = 30
10:58:08 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
10:58:11 Writing NN index file to temp file /tmp/RtmpzOxnpY/file6965b1c7de4f3
10:58:11 Searching Annoy index using 1 thread, search_k = 3000
10:58:19 Annoy recall = 100%
10:58:20 Commencing smooth kNN distance calibration using 1 thread with target n_neighbors = 30
10:58:22 Initializing from normalized Laplacian + noise (using RSpectra)
10:58:23 Commencing optimization for 200 epochs, with 1484690 positive edges
10:58:23 Using rng type: pcg
Using method 'umap'
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
10:58:35 Optimization finished
# UMAP plot colored by clusters
DimPlot(combined_seu, reduction = "umap", label = TRUE, pt.size = 0.2) +
  ggtitle("UMAP of CD4+ T Cells (Resolution = 0.2)")

9. Visualize UMAP with Clusters


DimPlot(combined_seu, reduction = "umap", group.by = "patient_id", label = TRUE, pt.size = 0.6) +
  ggtitle("UMAP of Sézary Syndrome CD4+ T Cells")


DimPlot(combined_seu, reduction = "umap", label = TRUE,label.box = T,repel = T, pt.size = 0.6) +
  ggtitle("UMAP of Sézary Syndrome CD4+ T Cells")

NA
NA
NA

##. Metadata enhancements


combined_seu$disease_status <- ifelse(grepl("SS", combined_seu$patient_id), "SS", "Healthy")

10. FeaturePlots for Top50 UP

top_50_up <- read.csv("../top_50_upregulated.csv")        # or read.delim("top_50_up.tsv")
top_50_down <- read.csv("../top_50_downregulated.csv")



FeaturePlot(combined_seu, 
             features = top_50_up$gene[1:10], 
             reduction = "umap", 
             cols = c("lightblue", "red"),  # Custom color gradient from light blue to red
             label = TRUE)



FeaturePlot(combined_seu, 
             features = top_50_up$gene[11:20], 
             reduction = "umap", 
             cols = c("lightblue", "red"),  # Custom color gradient from light blue to red
             label = TRUE)


FeaturePlot(combined_seu, 
             features = top_50_up$gene[21:30], 
             reduction = "umap", 
             cols = c("lightblue", "red"),  # Custom color gradient from light blue to red
             label = TRUE)

FeaturePlot(combined_seu, 
             features = top_50_up$gene[31:40], 
             reduction = "umap", 
             cols = c("lightblue", "red"),  # Custom color gradient from light blue to red
             label = TRUE)

FeaturePlot(combined_seu, 
             features = top_50_up$gene[41:50], 
             reduction = "umap", 
             cols = c("lightblue", "red"),  # Custom color gradient from light blue to red
             label = TRUE)

11. FeaturePlots for Top50 DOWN


FeaturePlot(combined_seu, 
             features = top_50_down$gene[1:10], 
             reduction = "umap", 
             cols = c("lightblue", "red"),  # Custom color gradient from light blue to red
             label = TRUE)



FeaturePlot(combined_seu, 
             features = top_50_down$gene[11:20], 
             reduction = "umap", 
             cols = c("lightblue", "red"),  # Custom color gradient from light blue to red
             label = TRUE)


FeaturePlot(combined_seu, 
             features = top_50_down$gene[21:30], 
             reduction = "umap", 
             cols = c("lightblue", "red"),  # Custom color gradient from light blue to red
             label = TRUE)

FeaturePlot(combined_seu, 
             features = top_50_down$gene[31:40], 
             reduction = "umap", 
             cols = c("lightblue", "red"),  # Custom color gradient from light blue to red
             label = TRUE)

FeaturePlot(combined_seu, 
             features = top_50_down$gene[41:50], 
             reduction = "umap", 
             cols = c("lightblue", "red"),  # Custom color gradient from light blue to red
             label = TRUE)

Visualization



DimPlot(combined_seu, group.by = "seurat_clusters", label = T, label.box = T, repel = T, reduction = "umap")

NA
NA

Visualization of Potential biomarkers-Upregulated



# Vector of genes to plot
up_genes <- c("CLIC1", "COX5A","GTSF1", "MAD2L1","MYBL2","MYL6B","NME1","PLK1", "PYCR1", "SLC25A5", "SRI", "TUBA1C", "UBE2T", "YWHAH")

# DotPlot with custom firebrick-red gradient
DotPlot(combined_seu, features = up_genes) +
  RotatedAxis() +
  scale_color_gradient2(low = "lightblue", mid = "red", high = "firebrick", midpoint = 1) +
  ggtitle("Expression of Upregulated Genes in Sézary Syndrome") +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1, size = 12),
    axis.text.y = element_text(size = 12),
    plot.title = element_text(hjust = 0.5, face = "bold", size = 14)
  )
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

# DotPlot with custom firebrick-red gradient
DotPlot(combined_seu, features = up_genes, group.by = "patient_id") +
  RotatedAxis() +
  scale_color_gradient2(low = "lightblue", mid = "red", high = "firebrick", midpoint = 1) +
  ggtitle("Expression of Upregulated Genes in Sézary Syndrome") +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1, size = 12),
    axis.text.y = element_text(size = 12),
    plot.title = element_text(hjust = 0.5, face = "bold", size = 14)
  )
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

# DotPlot with custom firebrick-red gradient
DotPlot(combined_seu, features = up_genes, group.by = "disease_status") +
  RotatedAxis() +
  scale_color_gradient2(low = "lightblue", mid = "red", high = "firebrick", midpoint = 1) +
  ggtitle("Expression of Upregulated Genes in Sézary Syndrome") +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1, size = 12),
    axis.text.y = element_text(size = 12),
    plot.title = element_text(hjust = 0.5, face = "bold", size = 14)
  )
Warning: Scaling data with a low number of groups may produce misleading resultsScale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

Visualization of Potential biomarkers-Downregulated



# Downregulated genes
down_genes <- c("TXNIP", "RASA3", "RIPOR2", 
                "ZFP36", "ZFP36L1", "ZFP36L2",
                "PRMT2", "MAX", "PIK3IP1", 
                "BTG1", "CDKN1B")

# DotPlot with firebrick color for high expression
DotPlot(combined_seu, features = down_genes) +
  RotatedAxis() +
  scale_color_gradient2(low = "lightblue", mid = "red", high = "firebrick", midpoint = 1) +
  ggtitle("Expression of Downregulated Genes in Sézary Syndrome") +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1, size = 12),
    axis.text.y = element_text(size = 12),
    plot.title = element_text(hjust = 0.5, face = "bold", size = 14)
  )
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

# DotPlot with firebrick color for high expression
DotPlot(combined_seu, features = down_genes, group.by = "patient_id") +
  RotatedAxis() +
  scale_color_gradient2(low = "lightblue", mid = "red", high = "firebrick", midpoint = 1) +
  ggtitle("Expression of Downregulated Genes in Sézary Syndrome") +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1, size = 12),
    axis.text.y = element_text(size = 12),
    plot.title = element_text(hjust = 0.5, face = "bold", size = 14)
  )
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

# DotPlot with firebrick color for high expression
DotPlot(combined_seu, features = down_genes, group.by = "disease_status") +
  RotatedAxis() +
  scale_color_gradient2(low = "lightblue", mid = "red", high = "firebrick", midpoint = 1) +
  ggtitle("Expression of Downregulated Genes in Sézary Syndrome") +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1, size = 12),
    axis.text.y = element_text(size = 12),
    plot.title = element_text(hjust = 0.5, face = "bold", size = 14)
  )
Warning: Scaling data with a low number of groups may produce misleading resultsScale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

12. Compare disease status using RNA assay

# Load required libraries
library(Seurat)
library(dplyr)
library(tibble)

# Join the layers of the RNA assay
combined_seu <- JoinLayers(combined_seu, assay = "RNA")

# Ensure your identity class is set to disease status
Idents(combined_seu) <- "disease_status"  # e.g., levels: "SS", "Control"

# Run differential expression between SS vs Control
markers_disease <- FindMarkers(
  object = combined_seu,
  ident.1 = "SS",
  ident.2 = "Healthy",
  assay = "RNA",
  logfc.threshold = 0,
  min.pct = 0,
  test.use = "wilcox"  # or "MAST" if RNA assay supports it
)

# Save results to CSV
write.csv(markers_disease, file = "DE_SS_vs_Healthy.csv", row.names = TRUE)

# Get log-normalized expression matrix (RNA assay)
expression_data_RNA <- GetAssayData(combined_seu, assay = "RNA", slot = "data")

# Get cell names for each group
ss_cells <- WhichCells(combined_seu, idents = "SS")
healthy_cells <- WhichCells(combined_seu, idents = "Healthy")

# Function to add mean expression per group
calculate_mean_expression <- function(markers, group1_cells, group2_cells, expression_data) {
  group1_mean <- rowMeans(expression_data[, group1_cells, drop = FALSE], na.rm = TRUE)
  group2_mean <- rowMeans(expression_data[, group2_cells, drop = FALSE], na.rm = TRUE)
  markers <- markers %>%
    rownames_to_column("gene") %>%
    mutate(
      mean_expr_SS = group1_mean[gene],
      mean_expr_Healthy = group2_mean[gene],
      log2FC_manual = log2(mean_expr_SS + 1) - log2(mean_expr_Healthy + 1)
    )
  return(markers)
}

# Apply the function and save final result
markers_disease_with_mean <- calculate_mean_expression(markers_disease, ss_cells, healthy_cells, expression_data_RNA)
write.csv(markers_disease_with_mean, "DE_SS_vs_Healthy_with_MeanExpr.csv", row.names = FALSE)

Save the Seurat object as an RDS



saveRDS(combined_seu, file = "../data/ss_Gaydosik_SS_Healthy_Combined.rds")
LS0tCnRpdGxlOiAiUG90ZW50aWFsIGJpb21hcmtlcnMgVmFsaWRhdGlvbiAoR2F5ZG9zaWtfM19NYWxpZ25hbnRfMl9EaXNlYXNlX3N0YXRlX2hlYWx0aHkpIgphdXRob3I6IE5hc2lyIE1haG1vb2QgQWJiYXNpCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogICNybWRmb3JtYXRzOjpyZWFkdGhlZG93bgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgdG9jX2NvbGxhcHNlZDogdHJ1ZQotLS0KCiMgMS4gbG9hZCBsaWJyYXJpZXMKYGBge3IgLCBpbmNsdWRlPUZBTFNFfQoKIyBBdCB0aGUgdG9wIG9mIHlvdXIgc2NyaXB0LCBjb21iaW5lIHBhY2thZ2VzIGxpa2UgdGhpczoKbGlicmFyeSh0aWR5dmVyc2UpICAgICAgIyBpbmNsdWRlcyBkcGx5ciwgZ2dwbG90MiwgdGliYmxlLCByZWFkciwgZXRjLgpsaWJyYXJ5KFNldXJhdCkKbGlicmFyeShoYXJtb255KQpsaWJyYXJ5KGRpdHRvU2VxKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShTZXVyYXREaXNrKQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShybWFya2Rvd24pCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkodGlueXRleCkKbGlicmFyeShBemltdXRoKQpsaWJyYXJ5KFNUQUNBUykKbGlicmFyeShQcm9qZWNUSUxzKQpsaWJyYXJ5KFNpbmdsZUNlbGxFeHBlcmltZW50KQoKYGBgCgoKIyAyLiBMb2FkIERhdGEgaW50byBTZXVyYXQKYGBge3IgfQoKIyBQYXRpZW50IElEcyBhbmQgSDUgZmlsZXMgKHJlcGxhY2Ugd2l0aCB5b3VyIGFjdHVhbCBmaWxlIHBhdGhzKQoKIyBEZWZpbmUgcGF0aHMgdG8geW91ciBINSBmaWxlcwpoNV9maWxlcyA8LSBsaXN0KAogICJTUzEiID0gIi4uL2RhdGEvU1MvR1NNNTY4Nzc2N19TWjIycmF3X2ZlYXR1cmVfYmNfbWF0cml4Lmg1IiwgI1NTLVBhdGllbnQKICAiU1MyIiA9ICIuLi9kYXRhL1NTL0dTTTU2ODc3NjlfU1oxNnJhd19mZWF0dXJlX2JjX21hdHJpeC5oNSIsICNTUy1QYXRpZW50CiAgIlNTMyIgPSAiLi4vZGF0YS9TUy9HU001Njg3NzcyX1NaMzByYXdfZmVhdHVyZV9iY19tYXRyaXguaDUiLCAjU1MtUGF0aWVudAogICJIQzFfMiIgPSAiLi4vZGF0YS9EaXNlYXNlX3N0YXRlX0hlYWx0aHlfQ29udHJvbC9HU001NTM4Nzg1X1NDMzI3cmF3X2ZlYXR1cmVfYmNfbWF0cml4Lmg1IiwgIyBkaXNlYXNlIHN0YXRlOiBIZWFsdGh5IGNvbnRyb2wvdGlzc3VlOiBTa2luIHR1bW9yCiAgIkhDM180IiA9ICIuLi9kYXRhL0Rpc2Vhc2Vfc3RhdGVfSGVhbHRoeV9Db250cm9sL0dTTTU1Mzg3ODZfU0MzNzRyYXdfZmVhdHVyZV9iY19tYXRyaXguaDUiICMgZGlzZWFzZSBzdGF0ZTogSGVhbHRoeSBjb250cm9sL3Rpc3N1ZTogU2tpbiB0dW1vcgopCgojIFJlYWQgYW5kIGNyZWF0ZSBpbmRpdmlkdWFsIFNldXJhdCBvYmplY3RzCnNldXJhdF9saXN0IDwtIGxpc3QoKQoKYGBgCgojIDMuIExvYWQgZWFjaCBwYXRpZW50IHNhbXBsZSB3aXRoIG1ldGFkYXRhCmBgYHtyICwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTB9CgpzZXVyYXRfbGlzdCA8LSBsaXN0KCkKCmZvciAocGF0aWVudCBpbiBuYW1lcyhoNV9maWxlcykpIHsKICByYXdfZGF0YSA8LSBSZWFkMTBYX2g1KGg1X2ZpbGVzW1twYXRpZW50XV0pCgogICMgSWYgcmF3X2RhdGEgaXMgYSBsaXN0IChtdWx0aS1tb2RhbCksIGV4dHJhY3QgIkdlbmUgRXhwcmVzc2lvbiIKICBpZiAoaXMubGlzdChyYXdfZGF0YSkpIHsKICAgIGlmICghIkdlbmUgRXhwcmVzc2lvbiIgJWluJSBuYW1lcyhyYXdfZGF0YSkpIHsKICAgICAgc3RvcChwYXN0ZSgiR2VuZSBFeHByZXNzaW9uIG1vZGFsaXR5IG5vdCBmb3VuZCBpbiBwYXRpZW50IiwgcGF0aWVudCkpCiAgICB9CiAgICByYXdfY291bnRzIDwtIHJhd19kYXRhW1siR2VuZSBFeHByZXNzaW9uIl1dCiAgfSBlbHNlIHsKICAgICMgU2luZ2xlIG1vZGFsaXR5CiAgICByYXdfY291bnRzIDwtIHJhd19kYXRhCiAgfQoKICBzZXUgPC0gQ3JlYXRlU2V1cmF0T2JqZWN0KAogICAgY291bnRzID0gcmF3X2NvdW50cywKICAgIG1pbi5jZWxscyA9IDMsCiAgICBtaW4uZmVhdHVyZXMgPSAyMDAsCiAgICBwcm9qZWN0ID0gcGF0aWVudAogICkKCiAgc2V1JHBhdGllbnRfaWQgPC0gcGF0aWVudAogIHNldXJhdF9saXN0W1twYXRpZW50XV0gPC0gc2V1Cn0KCgoKYGBgCgojIDQuIE1lcmdlIEFsbCBQYXRpZW50cyBpbnRvIE9uZSBTZXVyYXQgT2JqZWN0CmBgYHtyIH0KCiMgU3RlcCAyOiBNZXJnZSBpbnRvIGNvbWJpbmVkIFNldXJhdCBvYmplY3QKY29tYmluZWRfc2V1IDwtIG1lcmdlKAogIHNldXJhdF9saXN0W1sxXV0sCiAgeSA9IHNldXJhdF9saXN0Wy0xXSwKICBhZGQuY2VsbC5pZHMgPSBuYW1lcyhzZXVyYXRfbGlzdCksCiAgcHJvamVjdCA9ICJTU19IQ19NZXJnZWQiCikKCiMgVXNpbmcgTG9nTm9ybWFsaXplIGZvciBzaW1wbGljaXR5IGFuZCBjb21wYXRpYmlsaXR5IHdpdGggcmVmZXJlbmNlIGRhdGFzZXRzOyAKIyBTQ1RyYW5zZm9ybSBjb3VsZCBiZSB1c2VkIGZvciBkZWVwZXIgaW50ZWdyYXRpb24uCgojIFN0ZXAgMzogTm9ybWFsaXphdGlvbiAoTG9nTm9ybWFsaXplKQpjb21iaW5lZF9zZXUgPC0gTm9ybWFsaXplRGF0YShjb21iaW5lZF9zZXUsIG5vcm1hbGl6YXRpb24ubWV0aG9kID0gIkxvZ05vcm1hbGl6ZSIsIHNjYWxlLmZhY3RvciA9IDEwMDAwKQoKIyBTdGVwIDQ6IFZhcmlhYmxlIGZlYXR1cmVzIHNlbGVjdGlvbgpjb21iaW5lZF9zZXUgPC0gRmluZFZhcmlhYmxlRmVhdHVyZXMoY29tYmluZWRfc2V1LCBzZWxlY3Rpb24ubWV0aG9kID0gInZzdCIsIG5mZWF0dXJlcyA9IDIwMDApCgojIFN0ZXAgNTogU2NhbGluZwpjb21iaW5lZF9zZXUgPC0gU2NhbGVEYXRhKGNvbWJpbmVkX3NldSkKCiMgU3RlcCA2OiBQQ0EKY29tYmluZWRfc2V1IDwtIFJ1blBDQShjb21iaW5lZF9zZXUsIGZlYXR1cmVzID0gVmFyaWFibGVGZWF0dXJlcyhjb21iaW5lZF9zZXUpKQoKIyBTdGVwIDc6IEVsYm93IFBsb3QgdG8gZGVjaWRlIG51bWJlciBvZiBQQ3MKRWxib3dQbG90KGNvbWJpbmVkX3NldSkKCgojICMgU3RlcCA4OiBKYWNrU3RyYXcgKG9wdGlvbmFsIGJ1dCB1c2VmdWwgZm9yIFBDIHNpZ25pZmljYW5jZSkKIyBjb21iaW5lZF9zZXUgPC0gSmFja1N0cmF3KGNvbWJpbmVkX3NldSwgbnVtLnJlcGxpY2F0ZSA9IDEwMCkKIyBjb21iaW5lZF9zZXUgPC0gU2NvcmVKYWNrU3RyYXcoY29tYmluZWRfc2V1LCBkaW1zID0gMToyMCkKIyBKYWNrU3RyYXdQbG90KGNvbWJpbmVkX3NldSwgZGltcyA9IDE6MjApCgoKYGBgCgoKCiMgNS4gUUMKYGBge3IgLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMH0KCiMgQ2FsY3VsYXRlIHBlcmNlbnQubXQgZm9yIGVhY2ggY2VsbApjb21iaW5lZF9zZXVbWyJwZXJjZW50Lm10Il1dIDwtIFBlcmNlbnRhZ2VGZWF0dXJlU2V0KGNvbWJpbmVkX3NldSwgcGF0dGVybiA9ICJeTVQtIikKCiMgRmlsdGVyIGNlbGxzIHdpdGggdG9vIGZldyBvciB0b28gbWFueSBnZW5lcwpjb21iaW5lZF9zZXUgPC0gc3Vic2V0KGNvbWJpbmVkX3NldSwgCiAgICAgICAgICAgICAgICAgICAgICAgc3Vic2V0ID0gbkZlYXR1cmVfUk5BID4gMjAwICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbkZlYXR1cmVfUk5BIDwgODAwMCAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmNlbnQubXQgPCAxMCkKCgpWbG5QbG90KGNvbWJpbmVkX3NldSwgZmVhdHVyZXMgPSBjKCJuRmVhdHVyZV9STkEiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm5Db3VudF9STkEiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInBlcmNlbnQubXQiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IDMpCgpWbG5QbG90KGNvbWJpbmVkX3NldSwgZmVhdHVyZXMgPSBjKCJuRmVhdHVyZV9STkEiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibkNvdW50X1JOQSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJwZXJjZW50Lm10IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAicGVyY2VudC5yYiIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5jb2wgPSA0LCBwdC5zaXplID0gMC4xKSAmIAogICAgICAgICAgICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCkpCgpGZWF0dXJlU2NhdHRlcihjb21iaW5lZF9zZXUsIAogICAgICAgICAgICAgICBmZWF0dXJlMSA9ICJuQ291bnRfUk5BIiwgCiAgICAgICAgICAgICAgIGZlYXR1cmUyID0gIm5GZWF0dXJlX1JOQSIpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nKQoKYGBgCgojI0ZlYXR1cmVTY2F0dGVyIGlzIHR5cGljYWxseSB1c2VkIHRvIHZpc3VhbGl6ZSBmZWF0dXJlLWZlYXR1cmUgcmVsYXRpb25zaGlwcwojI2ZvciBhbnl0aGluZyBjYWxjdWxhdGVkIGJ5IHRoZSBvYmplY3QsIAojI2kuZS4gY29sdW1ucyBpbiBvYmplY3QgbWV0YWRhdGEsIFBDIHNjb3JlcyBldGMuCgpgYGB7ciAsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTEwfQoKRmVhdHVyZVNjYXR0ZXIoY29tYmluZWRfc2V1LCAKICAgICAgICAgICAgICAgZmVhdHVyZTEgPSAibkNvdW50X1JOQSIsIAogICAgICAgICAgICAgICBmZWF0dXJlMiA9ICJwZXJjZW50Lm10IikrCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gJ2xtJykKCkZlYXR1cmVTY2F0dGVyKGNvbWJpbmVkX3NldSwgCiAgICAgICAgICAgICAgIGZlYXR1cmUxID0gIm5Db3VudF9STkEiLCAKICAgICAgICAgICAgICAgZmVhdHVyZTIgPSAibkZlYXR1cmVfUk5BIikrCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gJ2xtJykKCmBgYAoKIyA2LiBQQ0EgVmlzdWFsaXphdGlvbgpgYGB7ciBQQ0EtVEVTVDIsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTEwfQoKCgojIFRFU1QtMQojIGdpdmVuIHRoYXQgdGhlIG91dHB1dCBvZiBSdW5QQ0EgaXMgInBjYSIKIyByZXBsYWNlICJzbyIgYnkgdGhlIG5hbWUgb2YgeW91ciBzZXVyYXQgb2JqZWN0CgpwY3QgPC0gY29tYmluZWRfc2V1W1sicGNhIl1dQHN0ZGV2IC8gc3VtKGNvbWJpbmVkX3NldVtbInBjYSJdXUBzdGRldikgKiAxMDAKY3VtdSA8LSBjdW1zdW0ocGN0KSAjIENhbGN1bGF0ZSBjdW11bGF0aXZlIHBlcmNlbnRzIGZvciBlYWNoIFBDCiMgRGV0ZXJtaW5lIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdmFyaWF0aW9uIG9mIFBDIGFuZCBzdWJzZXF1ZW50IFBDCmNvMiA8LSBzb3J0KHdoaWNoKChwY3RbLWxlbmd0aChwY3QpXSAtIHBjdFstMV0pID4gMC4xKSwgZGVjcmVhc2luZyA9IFQpWzFdICsgMQojIGxhc3QgcG9pbnQgd2hlcmUgY2hhbmdlIG9mICUgb2YgdmFyaWF0aW9uIGlzIG1vcmUgdGhhbiAwLjElLiAtPiBjbzIKY28yCgojIFRFU1QtMgojIGdldCBzaWduaWZpY2FudCBQQ3MKc3RkdiA8LSBjb21iaW5lZF9zZXVbWyJwY2EiXV1Ac3RkZXYKc3VtLnN0ZHYgPC0gc3VtKGNvbWJpbmVkX3NldVtbInBjYSJdXUBzdGRldikKcGVyY2VudC5zdGR2IDwtIChzdGR2IC8gc3VtLnN0ZHYpICogMTAwCmN1bXVsYXRpdmUgPC0gY3Vtc3VtKHBlcmNlbnQuc3RkdikKY28xIDwtIHdoaWNoKGN1bXVsYXRpdmUgPiA5MCAmIHBlcmNlbnQuc3RkdiA8IDUpWzFdCmNvMiA8LSBzb3J0KHdoaWNoKChwZXJjZW50LnN0ZHZbMTpsZW5ndGgocGVyY2VudC5zdGR2KSAtIDFdIC0gCiAgICAgICAgICAgICAgICAgICAgICAgcGVyY2VudC5zdGR2WzI6bGVuZ3RoKHBlcmNlbnQuc3RkdildKSA+IDAuMSksIAogICAgICAgICAgICAgIGRlY3JlYXNpbmcgPSBUKVsxXSArIDEKbWluLnBjIDwtIG1pbihjbzEsIGNvMikKbWluLnBjCgojIENyZWF0ZSBhIGRhdGFmcmFtZSB3aXRoIHZhbHVlcwpwbG90X2RmIDwtIGRhdGEuZnJhbWUocGN0ID0gcGVyY2VudC5zdGR2LCAKICAgICAgICAgICBjdW11ID0gY3VtdWxhdGl2ZSwgCiAgICAgICAgICAgcmFuayA9IDE6bGVuZ3RoKHBlcmNlbnQuc3RkdikpCgojIEVsYm93IHBsb3QgdG8gdmlzdWFsaXplIAogIGdncGxvdChwbG90X2RmLCBhZXMoY3VtdWxhdGl2ZSwgcGVyY2VudC5zdGR2LCBsYWJlbCA9IHJhbmssIGNvbG9yID0gcmFuayA+IG1pbi5wYykpICsgCiAgZ2VvbV90ZXh0KCkgKyAKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSA5MCwgY29sb3IgPSAiZ3JleSIpICsgCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gbWluKHBlcmNlbnQuc3RkdltwZXJjZW50LnN0ZHYgPiA1XSksIGNvbG9yID0gImdyZXkiKSArCiAgdGhlbWVfYncoKQoKICAKCmBgYAoKIyA4LiBDbHVzdGVyaW5nIChyZXNvbHV0aW9uID0gMS4yKQpgYGB7ciAsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTEwfQoKCmNvbWJpbmVkX3NldSA8LSBGaW5kTmVpZ2hib3JzKGNvbWJpbmVkX3NldSwgZGltcyA9IDE6MjYpCmNvbWJpbmVkX3NldSA8LSBGaW5kQ2x1c3RlcnMoY29tYmluZWRfc2V1LCByZXNvbHV0aW9uID0gMC4yKQoKIyBSdW4gVU1BUApjb21iaW5lZF9zZXUgPC0gUnVuVU1BUChjb21iaW5lZF9zZXUsIGRpbXMgPSAxOjI2KQoKIyBVTUFQIHBsb3QgY29sb3JlZCBieSBjbHVzdGVycwpEaW1QbG90KGNvbWJpbmVkX3NldSwgcmVkdWN0aW9uID0gInVtYXAiLCBsYWJlbCA9IFRSVUUsIHB0LnNpemUgPSAwLjIpICsKICBnZ3RpdGxlKCJVTUFQIG9mIENENCsgVCBDZWxscyAoUmVzb2x1dGlvbiA9IDAuMikiKQoKYGBgCgojIDkuIFZpc3VhbGl6ZSBVTUFQIHdpdGggQ2x1c3RlcnMKYGBge3IgLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMH0KCkRpbVBsb3QoY29tYmluZWRfc2V1LCByZWR1Y3Rpb24gPSAidW1hcCIsIGdyb3VwLmJ5ID0gInBhdGllbnRfaWQiLCBsYWJlbCA9IFRSVUUsIHB0LnNpemUgPSAwLjYpICsKICBnZ3RpdGxlKCJVTUFQIG9mIFPDqXphcnkgU3luZHJvbWUgQ0Q0KyBUIENlbGxzIikKCkRpbVBsb3QoY29tYmluZWRfc2V1LCByZWR1Y3Rpb24gPSAidW1hcCIsIGxhYmVsID0gVFJVRSxsYWJlbC5ib3ggPSBULHJlcGVsID0gVCwgcHQuc2l6ZSA9IDAuNikgKwogIGdndGl0bGUoIlVNQVAgb2YgU8OpemFyeSBTeW5kcm9tZSBDRDQrIFQgQ2VsbHMiKQoKCgpgYGAKCiMjLiBNZXRhZGF0YSBlbmhhbmNlbWVudHMKYGBge3IgLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMH0KCiNjb21iaW5lZF9zZXUkZGlzZWFzZV9zdGF0dXMgPC0gaWZlbHNlKGdyZXBsKCJTUyIsIGNvbWJpbmVkX3NldSRwYXRpZW50X2lkKSwgIlNTIiwgIkhlYWx0aHkiKQoKCgpgYGAKCgojIDEwLiAgRmVhdHVyZVBsb3RzIGZvciBUb3A1MCBVUApgYGB7ciAsIGZpZy5oZWlnaHQ9MTYsIGZpZy53aWR0aD0yMH0KdG9wXzUwX3VwIDwtIHJlYWQuY3N2KCIuLi90b3BfNTBfdXByZWd1bGF0ZWQuY3N2IikgICAgICAgICMgb3IgcmVhZC5kZWxpbSgidG9wXzUwX3VwLnRzdiIpCnRvcF81MF9kb3duIDwtIHJlYWQuY3N2KCIuLi90b3BfNTBfZG93bnJlZ3VsYXRlZC5jc3YiKQoKCgpGZWF0dXJlUGxvdChjb21iaW5lZF9zZXUsIAogICAgICAgICAgICAgZmVhdHVyZXMgPSB0b3BfNTBfdXAkZ2VuZVsxOjEwXSwgCiAgICAgICAgICAgICByZWR1Y3Rpb24gPSAidW1hcCIsIAogICAgICAgICAgICAgY29scyA9IGMoImxpZ2h0Ymx1ZSIsICJyZWQiKSwgICMgQ3VzdG9tIGNvbG9yIGdyYWRpZW50IGZyb20gbGlnaHQgYmx1ZSB0byByZWQKICAgICAgICAgICAgIGxhYmVsID0gVFJVRSkKCgpGZWF0dXJlUGxvdChjb21iaW5lZF9zZXUsIAogICAgICAgICAgICAgZmVhdHVyZXMgPSB0b3BfNTBfdXAkZ2VuZVsxMToyMF0sIAogICAgICAgICAgICAgcmVkdWN0aW9uID0gInVtYXAiLCAKICAgICAgICAgICAgIGNvbHMgPSBjKCJsaWdodGJsdWUiLCAicmVkIiksICAjIEN1c3RvbSBjb2xvciBncmFkaWVudCBmcm9tIGxpZ2h0IGJsdWUgdG8gcmVkCiAgICAgICAgICAgICBsYWJlbCA9IFRSVUUpCgpGZWF0dXJlUGxvdChjb21iaW5lZF9zZXUsIAogICAgICAgICAgICAgZmVhdHVyZXMgPSB0b3BfNTBfdXAkZ2VuZVsyMTozMF0sIAogICAgICAgICAgICAgcmVkdWN0aW9uID0gInVtYXAiLCAKICAgICAgICAgICAgIGNvbHMgPSBjKCJsaWdodGJsdWUiLCAicmVkIiksICAjIEN1c3RvbSBjb2xvciBncmFkaWVudCBmcm9tIGxpZ2h0IGJsdWUgdG8gcmVkCiAgICAgICAgICAgICBsYWJlbCA9IFRSVUUpCkZlYXR1cmVQbG90KGNvbWJpbmVkX3NldSwgCiAgICAgICAgICAgICBmZWF0dXJlcyA9IHRvcF81MF91cCRnZW5lWzMxOjQwXSwgCiAgICAgICAgICAgICByZWR1Y3Rpb24gPSAidW1hcCIsIAogICAgICAgICAgICAgY29scyA9IGMoImxpZ2h0Ymx1ZSIsICJyZWQiKSwgICMgQ3VzdG9tIGNvbG9yIGdyYWRpZW50IGZyb20gbGlnaHQgYmx1ZSB0byByZWQKICAgICAgICAgICAgIGxhYmVsID0gVFJVRSkKRmVhdHVyZVBsb3QoY29tYmluZWRfc2V1LCAKICAgICAgICAgICAgIGZlYXR1cmVzID0gdG9wXzUwX3VwJGdlbmVbNDE6NTBdLCAKICAgICAgICAgICAgIHJlZHVjdGlvbiA9ICJ1bWFwIiwgCiAgICAgICAgICAgICBjb2xzID0gYygibGlnaHRibHVlIiwgInJlZCIpLCAgIyBDdXN0b20gY29sb3IgZ3JhZGllbnQgZnJvbSBsaWdodCBibHVlIHRvIHJlZAogICAgICAgICAgICAgbGFiZWwgPSBUUlVFKQoKYGBgCgoKCiMgMTEuICBGZWF0dXJlUGxvdHMgZm9yIFRvcDUwIERPV04KYGBge3IgLCBmaWcuaGVpZ2h0PTE2LCBmaWcud2lkdGg9MjB9CgpGZWF0dXJlUGxvdChjb21iaW5lZF9zZXUsIAogICAgICAgICAgICAgZmVhdHVyZXMgPSB0b3BfNTBfZG93biRnZW5lWzE6MTBdLCAKICAgICAgICAgICAgIHJlZHVjdGlvbiA9ICJ1bWFwIiwgCiAgICAgICAgICAgICBjb2xzID0gYygibGlnaHRibHVlIiwgInJlZCIpLCAgIyBDdXN0b20gY29sb3IgZ3JhZGllbnQgZnJvbSBsaWdodCBibHVlIHRvIHJlZAogICAgICAgICAgICAgbGFiZWwgPSBUUlVFKQoKCkZlYXR1cmVQbG90KGNvbWJpbmVkX3NldSwgCiAgICAgICAgICAgICBmZWF0dXJlcyA9IHRvcF81MF9kb3duJGdlbmVbMTE6MjBdLCAKICAgICAgICAgICAgIHJlZHVjdGlvbiA9ICJ1bWFwIiwgCiAgICAgICAgICAgICBjb2xzID0gYygibGlnaHRibHVlIiwgInJlZCIpLCAgIyBDdXN0b20gY29sb3IgZ3JhZGllbnQgZnJvbSBsaWdodCBibHVlIHRvIHJlZAogICAgICAgICAgICAgbGFiZWwgPSBUUlVFKQoKRmVhdHVyZVBsb3QoY29tYmluZWRfc2V1LCAKICAgICAgICAgICAgIGZlYXR1cmVzID0gdG9wXzUwX2Rvd24kZ2VuZVsyMTozMF0sIAogICAgICAgICAgICAgcmVkdWN0aW9uID0gInVtYXAiLCAKICAgICAgICAgICAgIGNvbHMgPSBjKCJsaWdodGJsdWUiLCAicmVkIiksICAjIEN1c3RvbSBjb2xvciBncmFkaWVudCBmcm9tIGxpZ2h0IGJsdWUgdG8gcmVkCiAgICAgICAgICAgICBsYWJlbCA9IFRSVUUpCkZlYXR1cmVQbG90KGNvbWJpbmVkX3NldSwgCiAgICAgICAgICAgICBmZWF0dXJlcyA9IHRvcF81MF9kb3duJGdlbmVbMzE6NDBdLCAKICAgICAgICAgICAgIHJlZHVjdGlvbiA9ICJ1bWFwIiwgCiAgICAgICAgICAgICBjb2xzID0gYygibGlnaHRibHVlIiwgInJlZCIpLCAgIyBDdXN0b20gY29sb3IgZ3JhZGllbnQgZnJvbSBsaWdodCBibHVlIHRvIHJlZAogICAgICAgICAgICAgbGFiZWwgPSBUUlVFKQpGZWF0dXJlUGxvdChjb21iaW5lZF9zZXUsIAogICAgICAgICAgICAgZmVhdHVyZXMgPSB0b3BfNTBfZG93biRnZW5lWzQxOjUwXSwgCiAgICAgICAgICAgICByZWR1Y3Rpb24gPSAidW1hcCIsIAogICAgICAgICAgICAgY29scyA9IGMoImxpZ2h0Ymx1ZSIsICJyZWQiKSwgICMgQ3VzdG9tIGNvbG9yIGdyYWRpZW50IGZyb20gbGlnaHQgYmx1ZSB0byByZWQKICAgICAgICAgICAgIGxhYmVsID0gVFJVRSkKYGBgCgojIyBWaXN1YWxpemF0aW9uCmBgYHtyICwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTB9CgoKRGltUGxvdChjb21iaW5lZF9zZXUsIGdyb3VwLmJ5ID0gInNldXJhdF9jbHVzdGVycyIsIGxhYmVsID0gVCwgbGFiZWwuYm94ID0gVCwgcmVwZWwgPSBULCByZWR1Y3Rpb24gPSAidW1hcCIpCgoKYGBgCgojIyBWaXN1YWxpemF0aW9uIG9mIFBvdGVudGlhbCBiaW9tYXJrZXJzLVVwcmVndWxhdGVkCmBgYHtyICwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTB9CgoKIyBWZWN0b3Igb2YgZ2VuZXMgdG8gcGxvdAp1cF9nZW5lcyA8LSBjKCJDTElDMSIsICJDT1g1QSIsIkdUU0YxIiwgIk1BRDJMMSIsIk1ZQkwyIiwiTVlMNkIiLCJOTUUxIiwiUExLMSIsICJQWUNSMSIsICJTTEMyNUE1IiwgIlNSSSIsICJUVUJBMUMiLCAiVUJFMlQiLCAiWVdIQUgiKQoKIyBEb3RQbG90IHdpdGggY3VzdG9tIGZpcmVicmljay1yZWQgZ3JhZGllbnQKRG90UGxvdChjb21iaW5lZF9zZXUsIGZlYXR1cmVzID0gdXBfZ2VuZXMpICsKICBSb3RhdGVkQXhpcygpICsKICBzY2FsZV9jb2xvcl9ncmFkaWVudDIobG93ID0gImxpZ2h0Ymx1ZSIsIG1pZCA9ICJyZWQiLCBoaWdoID0gImZpcmVicmljayIsIG1pZHBvaW50ID0gMSkgKwogIGdndGl0bGUoIkV4cHJlc3Npb24gb2YgVXByZWd1bGF0ZWQgR2VuZXMgaW4gU8OpemFyeSBTeW5kcm9tZSIpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSwgc2l6ZSA9IDEyKSwKICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiLCBzaXplID0gMTQpCiAgKQoKIyBEb3RQbG90IHdpdGggY3VzdG9tIGZpcmVicmljay1yZWQgZ3JhZGllbnQKRG90UGxvdChjb21iaW5lZF9zZXUsIGZlYXR1cmVzID0gdXBfZ2VuZXMsIGdyb3VwLmJ5ID0gInBhdGllbnRfaWQiKSArCiAgUm90YXRlZEF4aXMoKSArCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQyKGxvdyA9ICJsaWdodGJsdWUiLCBtaWQgPSAicmVkIiwgaGlnaCA9ICJmaXJlYnJpY2siLCBtaWRwb2ludCA9IDEpICsKICBnZ3RpdGxlKCJFeHByZXNzaW9uIG9mIFVwcmVndWxhdGVkIEdlbmVzIGluIFPDqXphcnkgU3luZHJvbWUiKSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEsIHNpemUgPSAxMiksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE0KQogICkKCiMgRG90UGxvdCB3aXRoIGN1c3RvbSBmaXJlYnJpY2stcmVkIGdyYWRpZW50CkRvdFBsb3QoY29tYmluZWRfc2V1LCBmZWF0dXJlcyA9IHVwX2dlbmVzLCBncm91cC5ieSA9ICJkaXNlYXNlX3N0YXR1cyIpICsKICBSb3RhdGVkQXhpcygpICsKICBzY2FsZV9jb2xvcl9ncmFkaWVudDIobG93ID0gImxpZ2h0Ymx1ZSIsIG1pZCA9ICJyZWQiLCBoaWdoID0gImZpcmVicmljayIsIG1pZHBvaW50ID0gMSkgKwogIGdndGl0bGUoIkV4cHJlc3Npb24gb2YgVXByZWd1bGF0ZWQgR2VuZXMgaW4gU8OpemFyeSBTeW5kcm9tZSIpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSwgc2l6ZSA9IDEyKSwKICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiLCBzaXplID0gMTQpCiAgKQpgYGAKCgoKIyMgVmlzdWFsaXphdGlvbiBvZiBQb3RlbnRpYWwgYmlvbWFya2Vycy1Eb3ducmVndWxhdGVkCmBgYHtyIEMyLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMH0KCgojIERvd25yZWd1bGF0ZWQgZ2VuZXMKZG93bl9nZW5lcyA8LSBjKCJUWE5JUCIsICJSQVNBMyIsICJSSVBPUjIiLCAKICAgICAgICAgICAgICAgICJaRlAzNiIsICJaRlAzNkwxIiwgIlpGUDM2TDIiLAogICAgICAgICAgICAgICAgIlBSTVQyIiwgIk1BWCIsICJQSUszSVAxIiwgCiAgICAgICAgICAgICAgICAiQlRHMSIsICJDREtOMUIiKQoKIyBEb3RQbG90IHdpdGggZmlyZWJyaWNrIGNvbG9yIGZvciBoaWdoIGV4cHJlc3Npb24KRG90UGxvdChjb21iaW5lZF9zZXUsIGZlYXR1cmVzID0gZG93bl9nZW5lcykgKwogIFJvdGF0ZWRBeGlzKCkgKwogIHNjYWxlX2NvbG9yX2dyYWRpZW50Mihsb3cgPSAibGlnaHRibHVlIiwgbWlkID0gInJlZCIsIGhpZ2ggPSAiZmlyZWJyaWNrIiwgbWlkcG9pbnQgPSAxKSArCiAgZ2d0aXRsZSgiRXhwcmVzc2lvbiBvZiBEb3ducmVndWxhdGVkIEdlbmVzIGluIFPDqXphcnkgU3luZHJvbWUiKSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEsIHNpemUgPSAxMiksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE0KQogICkKCiMgRG90UGxvdCB3aXRoIGZpcmVicmljayBjb2xvciBmb3IgaGlnaCBleHByZXNzaW9uCkRvdFBsb3QoY29tYmluZWRfc2V1LCBmZWF0dXJlcyA9IGRvd25fZ2VuZXMsIGdyb3VwLmJ5ID0gInBhdGllbnRfaWQiKSArCiAgUm90YXRlZEF4aXMoKSArCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQyKGxvdyA9ICJsaWdodGJsdWUiLCBtaWQgPSAicmVkIiwgaGlnaCA9ICJmaXJlYnJpY2siLCBtaWRwb2ludCA9IDEpICsKICBnZ3RpdGxlKCJFeHByZXNzaW9uIG9mIERvd25yZWd1bGF0ZWQgR2VuZXMgaW4gU8OpemFyeSBTeW5kcm9tZSIpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSwgc2l6ZSA9IDEyKSwKICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiLCBzaXplID0gMTQpCiAgKQoKIyBEb3RQbG90IHdpdGggZmlyZWJyaWNrIGNvbG9yIGZvciBoaWdoIGV4cHJlc3Npb24KRG90UGxvdChjb21iaW5lZF9zZXUsIGZlYXR1cmVzID0gZG93bl9nZW5lcywgZ3JvdXAuYnkgPSAiZGlzZWFzZV9zdGF0dXMiKSArCiAgUm90YXRlZEF4aXMoKSArCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQyKGxvdyA9ICJsaWdodGJsdWUiLCBtaWQgPSAicmVkIiwgaGlnaCA9ICJmaXJlYnJpY2siLCBtaWRwb2ludCA9IDEpICsKICBnZ3RpdGxlKCJFeHByZXNzaW9uIG9mIERvd25yZWd1bGF0ZWQgR2VuZXMgaW4gU8OpemFyeSBTeW5kcm9tZSIpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSwgc2l6ZSA9IDEyKSwKICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiLCBzaXplID0gMTQpCiAgKQoKYGBgCgojIDEyLiBDb21wYXJlIGRpc2Vhc2Ugc3RhdHVzIHVzaW5nIFJOQSBhc3NheQpgYGB7ciBhemltdXRoX0Fubm90YXRpb24sIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTZ9CiMgTG9hZCByZXF1aXJlZCBsaWJyYXJpZXMKbGlicmFyeShTZXVyYXQpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGliYmxlKQoKIyBKb2luIHRoZSBsYXllcnMgb2YgdGhlIFJOQSBhc3NheQpjb21iaW5lZF9zZXUgPC0gSm9pbkxheWVycyhjb21iaW5lZF9zZXUsIGFzc2F5ID0gIlJOQSIpCgojIEVuc3VyZSB5b3VyIGlkZW50aXR5IGNsYXNzIGlzIHNldCB0byBkaXNlYXNlIHN0YXR1cwpJZGVudHMoY29tYmluZWRfc2V1KSA8LSAiZGlzZWFzZV9zdGF0dXMiICAjIGUuZy4sIGxldmVsczogIlNTIiwgIkNvbnRyb2wiCgojIFJ1biBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBiZXR3ZWVuIFNTIHZzIENvbnRyb2wKbWFya2Vyc19kaXNlYXNlIDwtIEZpbmRNYXJrZXJzKAogIG9iamVjdCA9IGNvbWJpbmVkX3NldSwKICBpZGVudC4xID0gIlNTIiwKICBpZGVudC4yID0gIkhlYWx0aHkiLAogIGFzc2F5ID0gIlJOQSIsCiAgbG9nZmMudGhyZXNob2xkID0gMCwKICBtaW4ucGN0ID0gMCwKICB0ZXN0LnVzZSA9ICJ3aWxjb3giICAjIG9yICJNQVNUIiBpZiBSTkEgYXNzYXkgc3VwcG9ydHMgaXQKKQoKIyBTYXZlIHJlc3VsdHMgdG8gQ1NWCndyaXRlLmNzdihtYXJrZXJzX2Rpc2Vhc2UsIGZpbGUgPSAiREVfU1NfdnNfSGVhbHRoeS5jc3YiLCByb3cubmFtZXMgPSBUUlVFKQoKIyBHZXQgbG9nLW5vcm1hbGl6ZWQgZXhwcmVzc2lvbiBtYXRyaXggKFJOQSBhc3NheSkKZXhwcmVzc2lvbl9kYXRhX1JOQSA8LSBHZXRBc3NheURhdGEoY29tYmluZWRfc2V1LCBhc3NheSA9ICJSTkEiLCBzbG90ID0gImRhdGEiKQoKIyBHZXQgY2VsbCBuYW1lcyBmb3IgZWFjaCBncm91cApzc19jZWxscyA8LSBXaGljaENlbGxzKGNvbWJpbmVkX3NldSwgaWRlbnRzID0gIlNTIikKaGVhbHRoeV9jZWxscyA8LSBXaGljaENlbGxzKGNvbWJpbmVkX3NldSwgaWRlbnRzID0gIkhlYWx0aHkiKQoKIyBGdW5jdGlvbiB0byBhZGQgbWVhbiBleHByZXNzaW9uIHBlciBncm91cApjYWxjdWxhdGVfbWVhbl9leHByZXNzaW9uIDwtIGZ1bmN0aW9uKG1hcmtlcnMsIGdyb3VwMV9jZWxscywgZ3JvdXAyX2NlbGxzLCBleHByZXNzaW9uX2RhdGEpIHsKICBncm91cDFfbWVhbiA8LSByb3dNZWFucyhleHByZXNzaW9uX2RhdGFbLCBncm91cDFfY2VsbHMsIGRyb3AgPSBGQUxTRV0sIG5hLnJtID0gVFJVRSkKICBncm91cDJfbWVhbiA8LSByb3dNZWFucyhleHByZXNzaW9uX2RhdGFbLCBncm91cDJfY2VsbHMsIGRyb3AgPSBGQUxTRV0sIG5hLnJtID0gVFJVRSkKICBtYXJrZXJzIDwtIG1hcmtlcnMgJT4lCiAgICByb3duYW1lc190b19jb2x1bW4oImdlbmUiKSAlPiUKICAgIG11dGF0ZSgKICAgICAgbWVhbl9leHByX1NTID0gZ3JvdXAxX21lYW5bZ2VuZV0sCiAgICAgIG1lYW5fZXhwcl9IZWFsdGh5ID0gZ3JvdXAyX21lYW5bZ2VuZV0sCiAgICAgIGxvZzJGQ19tYW51YWwgPSBsb2cyKG1lYW5fZXhwcl9TUyArIDEpIC0gbG9nMihtZWFuX2V4cHJfSGVhbHRoeSArIDEpCiAgICApCiAgcmV0dXJuKG1hcmtlcnMpCn0KCiMgQXBwbHkgdGhlIGZ1bmN0aW9uIGFuZCBzYXZlIGZpbmFsIHJlc3VsdAptYXJrZXJzX2Rpc2Vhc2Vfd2l0aF9tZWFuIDwtIGNhbGN1bGF0ZV9tZWFuX2V4cHJlc3Npb24obWFya2Vyc19kaXNlYXNlLCBzc19jZWxscywgaGVhbHRoeV9jZWxscywgZXhwcmVzc2lvbl9kYXRhX1JOQSkKd3JpdGUuY3N2KG1hcmtlcnNfZGlzZWFzZV93aXRoX21lYW4sICJERV9TU192c19IZWFsdGh5X3dpdGhfTWVhbkV4cHIuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpCgpgYGAKCgojIFNhdmUgdGhlIFNldXJhdCBvYmplY3QgYXMgYW4gUkRTCmBgYHtyIHNhdmVSRFN9CgoKc2F2ZVJEUyhjb21iaW5lZF9zZXUsIGZpbGUgPSAiLi4vZGF0YS9zc19HYXlkb3Npa19TU19IZWFsdGh5X0NvbWJpbmVkLnJkcyIpCgoKYGBgCgoKCg==