import nibabel as nib
import pydicom
import os
import glob
import numpy as np
from copy import deepcopy
from matplotlib.patches import Polygon
import warnings
from scipy.ndimage import find_objects
from scipy.ndimage.morphology import binary_fill_holes
from skimage import measure
from PIL import Image, ImageDraw
import scipy
import datetime
from dicom_to_nii import set_header_info

def convert_nii_to_dicom(dicomctdir, predictedNiiFile, predictedDicomFile, predicted_structures=[], rtstruct_colors=[], refCT = None):
    # img = nib.load(os.path.join(predniidir, patient_id, 'RTStruct.nii.gz'))
    # data = img.get_fdata()[:,:,:,1]
    # patient_list = PatientList() # initialize list of patient data
    # patient_list.list_dicom_files(os.path.join(ct_ref_path,patient,inner_ct_ref_path), 1) # search dicom files in the patient data folder, stores all files in the attributes (all CT images, dose file, struct file)
    # refCT = patient_list.list[0].CTimages[0]
    # refCT.import_Dicom_CT()

    struct = RTstruct()
    struct.load_from_nii(predictedNiiFile, predicted_structures, rtstruct_colors) #TODO add already the refCT info in here because there are fields to do that
    if not struct.Contours[0].Mask_PixelSpacing == refCT.PixelSpacing:
      struct.resample_struct(refCT.PixelSpacing)
    struct.export_Dicom(refCT, predictedDicomFile)

    # create_RT_struct(dicomctdir, data.transpose([1,0,2]).astype(int), dicomdir, predicted_structures)

def integer_to_onehot(niiFile):
    
    # get contours in nnunet format
    nnunet_integer_nib = nib.load(niiFile)
    nnunet_integer_data = nnunet_integer_nib.get_fdata()
   
    # convert to onehot encoding (2**i)
    onehot_data = np.zeros(nnunet_integer_data.shape)
    for i in np.unique(nnunet_integer_data):
        onehot_data[nnunet_integer_data == i] = 2**i
        
    # get contours_exist
    contours_exist = np.ones(len(np.unique(onehot_data))).astype(bool).tolist()
    #contours_exist = np.ones(len(np.unique(onehot_data))-1).astype(bool) # -1 to remove the background which we don't want
    # save it back to nii format (will overwrite the predicted file - integer format - with this one - onehot format -)
    image_nii = nib.Nifti1Image(onehot_data, affine=np.eye(4)) # for Nifti1 header, change for a Nifti2 type of header
    # Update header fields
    image_nii = set_header_info(image_nii, nnunet_integer_nib.header['pixdim'][1:4], [nnunet_integer_nib.header['qoffset_x'],nnunet_integer_nib.header['qoffset_y'],nnunet_integer_nib.header['qoffset_z']], contours_exist = contours_exist)
    # Save  nii 
    nib.save(image_nii,niiFile) #overwrites old file     

    return

def save_nii_image(nib_img, nib_header,dst_dir, dst_filename, contours_exist = None):
    
    image_nii = nib.Nifti1Image(nib_img, affine=np.eye(4)) # for Nifti1 header, change for a Nifti2 type of header
    # Update header fields
    if contours_exist is not None:
        image_nii = set_header_info(image_nii, nib_header['pixdim'][1:4], [nib_header['qoffset_x'],nib_header['qoffset_y'],nib_header['qoffset_z']], contours_exist = contours_exist)
    else:
        image_nii = set_header_info(image_nii, nib_header['pixdim'][1:4], [nib_header['qoffset_x'],nib_header['qoffset_y'],nib_header['qoffset_z']])
    # Save  nii 
    nib.save(image_nii, os.path.join(dst_dir,dst_filename))

def Taubin_smoothing(contour):
    """ Here, we do smoothing in 2D contours!
        Parameters:
            a Nx2 numpy array containing the contour to smooth
        Returns:
            a Nx2 numpy array containing the smoothed contour """
    smoothingloops = 5
    smoothed = [np.empty_like(contour) for i in range(smoothingloops+1)]
    smoothed[0] = contour
    for i in range(smoothingloops):
        # loop over all elements in the contour
        for vertex_i in range(smoothed[0].shape[0]):
            if vertex_i == 0:
                vertex_prev = smoothed[i].shape[0]-1
                vertex_next = vertex_i+1
            elif vertex_i == smoothed[i].shape[0]-1:
                vertex_prev = vertex_i-1
                vertex_next = 0
            else:
                vertex_prev = vertex_i -1
                vertex_next = vertex_i +1
            neighbours_x = np.array([smoothed[i][vertex_prev,0], smoothed[i][vertex_next,0]])
            neighbours_y = np.array([smoothed[i][vertex_prev,1], smoothed[i][vertex_next,1]])
            smoothed[i+1][vertex_i,0] = smoothed[i][vertex_i,0] - 0.3*(smoothed[i][vertex_i,0] - np.mean(neighbours_x))
            smoothed[i+1][vertex_i,1] = smoothed[i][vertex_i,1] - 0.3*(smoothed[i][vertex_i,1] - np.mean(neighbours_y))

    return np.round(smoothed[smoothingloops],3)

class RTstruct:

  def __init__(self):
    self.SeriesInstanceUID = ""
    self.PatientInfo = {}
    self.StudyInfo = {}
    self.CT_SeriesInstanceUID = ""
    self.DcmFile = ""
    self.isLoaded = 0
    self.Contours = []
    self.NumContours = 0
    
    
  def print_struct_info(self, prefix=""):
    print(prefix + "Struct: " + self.SeriesInstanceUID)
    print(prefix + "   " + self.DcmFile)
    
    
  def print_ROINames(self):
    print("RT Struct UID: " + self.SeriesInstanceUID)
    count = -1
    for contour in self.Contours:
      count += 1
      print('  [' + str(count) + ']  ' + contour.ROIName)
    
  def resample_struct(self, newvoxelsize):
    # Rescaling to the newvoxelsize if given in parameter
    if newvoxelsize is not None: 
      for i, Contour in enumerate(self.Contours):
        source_shape = Contour.Mask_GridSize
        voxelsize = Contour.Mask_PixelSpacing
        VoxelX_source = Contour.Mask_Offset[0] + np.arange(source_shape[0])*voxelsize[0]
        VoxelY_source = Contour.Mask_Offset[1] + np.arange(source_shape[1])*voxelsize[1]
        VoxelZ_source = Contour.Mask_Offset[2] + np.arange(source_shape[2])*voxelsize[2]

        target_shape = np.ceil(np.array(source_shape).astype(float)*np.array(voxelsize).astype(float)/newvoxelsize).astype(int)
        VoxelX_target = Contour.Mask_Offset[0] + np.arange(target_shape[0])*newvoxelsize[0]
        VoxelY_target = Contour.Mask_Offset[1] + np.arange(target_shape[1])*newvoxelsize[1]
        VoxelZ_target = Contour.Mask_Offset[2] + np.arange(target_shape[2])*newvoxelsize[2]

        contour = Contour.Mask
        
        if(all(source_shape == target_shape) and np.linalg.norm(np.subtract(voxelsize, newvoxelsize) < 0.001)):
          print("! Image does not need filtering")
        else:
          # anti-aliasing filter
          sigma = [0, 0, 0]
          if(newvoxelsize[0] > voxelsize[0]): sigma[0] = 0.4 * (newvoxelsize[0]/voxelsize[0])
          if(newvoxelsize[1] > voxelsize[1]): sigma[1] = 0.4 * (newvoxelsize[1]/voxelsize[1])
          if(newvoxelsize[2] > voxelsize[2]): sigma[2] = 0.4 * (newvoxelsize[2]/voxelsize[2])
          
          if(sigma != [0, 0, 0]):
              contour = scipy.ndimage.gaussian_filter(contour.astype(float), sigma)
              #come back to binary
              contour[np.where(contour>=0.5)] = 1
              contour[np.where(contour<0.5)] = 0
              
          xi = np.array(np.meshgrid(VoxelX_target, VoxelY_target, VoxelZ_target))
          xi = np.rollaxis(xi, 0, 4)
          xi = xi.reshape((xi.size // 3, 3))
          
          # get resized ct
          contour = scipy.interpolate.interpn((VoxelX_source,VoxelY_source,VoxelZ_source), contour, xi, method='nearest', fill_value=0, bounds_error=False).astype(bool).reshape(target_shape).transpose(1,0,2)
        Contour.Mask_PixelSpacing = newvoxelsize
        Contour.Mask_GridSize = list(contour.shape) 
        Contour.NumVoxels = Contour.Mask_GridSize[0] * Contour.Mask_GridSize[1] * Contour.Mask_GridSize[2]
        Contour.Mask = contour
        self.Contours[i]=Contour
        
  
  def import_Dicom_struct(self, CT):
    if(self.isLoaded == 1):
      print("Warning: RTstruct " + self.SeriesInstanceUID + " is already loaded")
      return 
    dcm = pydicom.dcmread(self.DcmFile)
    
    self.CT_SeriesInstanceUID = CT.SeriesInstanceUID
    
    for dcm_struct in dcm.StructureSetROISequence:  
      ReferencedROI_id = next((x for x, val in enumerate(dcm.ROIContourSequence) if val.ReferencedROINumber == dcm_struct.ROINumber), -1)
      dcm_contour = dcm.ROIContourSequence[ReferencedROI_id]
    
      Contour = ROIcontour()
      Contour.SeriesInstanceUID = self.SeriesInstanceUID
      Contour.ROIName = dcm_struct.ROIName
      Contour.ROIDisplayColor = dcm_contour.ROIDisplayColor
    
      #print("Import contour " + str(len(self.Contours)) + ": " + Contour.ROIName)
    
      Contour.Mask = np.zeros((CT.GridSize[0], CT.GridSize[1], CT.GridSize[2]), dtype=np.bool)
      Contour.Mask_GridSize = CT.GridSize
      Contour.Mask_PixelSpacing = CT.PixelSpacing
      Contour.Mask_Offset = CT.ImagePositionPatient
      Contour.Mask_NumVoxels = CT.NumVoxels   
      Contour.ContourMask = np.zeros((CT.GridSize[0], CT.GridSize[1], CT.GridSize[2]), dtype=np.bool)
      
      SOPInstanceUID_match = 1
      
      if not hasattr(dcm_contour, 'ContourSequence'):
          print("This structure has no attribute ContourSequence. Skipping ...")
          continue

      for dcm_slice in dcm_contour.ContourSequence:
        Slice = {}
      
        # list of Dicom coordinates
        Slice["XY_dcm"] = list(zip( np.array(dcm_slice.ContourData[0::3]), np.array(dcm_slice.ContourData[1::3]) ))
        Slice["Z_dcm"] = float(dcm_slice.ContourData[2])
      
        # list of coordinates in the image frame
        Slice["XY_img"] = list(zip( ((np.array(dcm_slice.ContourData[0::3]) - CT.ImagePositionPatient[0]) / CT.PixelSpacing[0]), ((np.array(dcm_slice.ContourData[1::3]) - CT.ImagePositionPatient[1]) / CT.PixelSpacing[1]) ))
        Slice["Z_img"] = (Slice["Z_dcm"] - CT.ImagePositionPatient[2]) / CT.PixelSpacing[2]
        Slice["Slice_id"] = int(round(Slice["Z_img"]))
      
        # convert polygon to mask (based on matplotlib - slow)
        #x, y = np.meshgrid(np.arange(CT.GridSize[0]), np.arange(CT.GridSize[1]))
        #points = np.transpose((x.ravel(), y.ravel()))
        #path = Path(Slice["XY_img"])
        #mask = path.contains_points(points)
        #mask = mask.reshape((CT.GridSize[0], CT.GridSize[1]))
      
        # convert polygon to mask (based on PIL - fast)
        img = Image.new('L', (CT.GridSize[0], CT.GridSize[1]), 0)
        if(len(Slice["XY_img"]) > 1): ImageDraw.Draw(img).polygon(Slice["XY_img"], outline=1, fill=1)
        mask = np.array(img)
        Contour.Mask[:,:,Slice["Slice_id"]] = np.logical_or(Contour.Mask[:,:,Slice["Slice_id"]], mask)
        
        # do the same, but only keep contour in the mask
        img = Image.new('L', (CT.GridSize[0], CT.GridSize[1]), 0)
        if(len(Slice["XY_img"]) > 1): ImageDraw.Draw(img).polygon(Slice["XY_img"], outline=1, fill=0)
        mask = np.array(img)
        Contour.ContourMask[:,:,Slice["Slice_id"]] = np.logical_or(Contour.ContourMask[:,:,Slice["Slice_id"]], mask)
            
        Contour.ContourSequence.append(Slice)
      
        # check if the contour sequence is imported on the correct CT slice:
        if(hasattr(dcm_slice, 'ContourImageSequence') and CT.SOPInstanceUIDs[Slice["Slice_id"]] != dcm_slice.ContourImageSequence[0].ReferencedSOPInstanceUID):
          SOPInstanceUID_match = 0
      
      if SOPInstanceUID_match != 1:
        print("WARNING: some SOPInstanceUIDs don't match during importation of " + Contour.ROIName + " contour on CT image")
      
      self.Contours.append(Contour)
      self.NumContours += 1
    #print("self.NumContours",self.NumContours, len(self.Contours))
    self.isLoaded = 1

  def load_from_nii(self, struct_nii_path, rtstruct_labels, rtstruct_colors):
      
    # load the nii image 
    struct_nib = nib.load(struct_nii_path)
    struct_data = struct_nib.get_fdata()
            
    # get contourexists from header
    if len(struct_nib.header.extensions)==0:
      contoursexist = []
    else:
        # TODO ENABLE IN CASE WE DONT HAVE contoursexist TAKE JUST THE LENGTH OF LABELS
        contoursexist = list(struct_nib.header.extensions[0].get_content())
    
    # get number of rois in struct_data 
    # for nii with consecutive integers
    #roinumbers = np.unique(struct_data) 
    # for nii with power of 2 format
    #roinumbers = list(np.arange(np.floor(np.log2(np.max(struct_data))).astype(int)+1)) # CAREFUL WITH THIS LINE, MIGHT NOT WORK ALWAYS IF WE HAVE OVERLAP OF 
    #nb_rois_in_struct = len(roinumbers)
    
    # check that they match
    if not len(rtstruct_labels) == len(contoursexist) :
        #raise TypeError("The number or struct labels, contoursexist, and  masks in struct.nii.gz is not the same")
        # raise Warning("The number or struct labels and contoursexist in struct.nii.gz is not the same. Taking len(contoursexist) as number of rois")
        self.NumContours = len(rtstruct_labels)#len(contoursexist)
    else:
        self.NumContours = len(rtstruct_labels)#len(contoursexist)
    print("num contours", self.NumContours, len(rtstruct_labels) , len(contoursexist))    
    # fill in contours
    #TODO fill in ContourSequence and ContourData to be faster later in writeDicomRTstruct
    for c in range(self.NumContours):
        
        Contour = ROIcontour()
        Contour.SeriesInstanceUID = self.SeriesInstanceUID
        Contour.ROIName = rtstruct_labels[c]
        if rtstruct_colors[c] == None:
            Contour.ROIDisplayColor = [0, 0, 255] # default color is blue
        else:
            Contour.ROIDisplayColor = rtstruct_colors[c] 
        if len(contoursexist)!=0 and contoursexist[c] == 0:
            Contour.Mask = np.zeros((struct_nib.header['dim'][1], struct_nib.header['dim'][2], struct_nib.header['dim'][3]), dtype=np.bool_)
        else:
            Contour.Mask = np.bitwise_and(struct_data.astype(int), 2 ** c).astype(bool)
        #TODO enable option for consecutive integers masks?
        Contour.Mask_GridSize = [struct_nib.header['dim'][1], struct_nib.header['dim'][2], struct_nib.header['dim'][3]]
        Contour.Mask_PixelSpacing = [struct_nib.header['pixdim'][1], struct_nib.header['pixdim'][2], struct_nib.header['pixdim'][3]]
        Contour.Mask_Offset = [struct_nib.header['qoffset_x'], struct_nib.header['qoffset_y'], struct_nib.header['qoffset_z']]
        Contour.Mask_NumVoxels = struct_nib.header['dim'][1].astype(int) * struct_nib.header['dim'][2].astype(int) * struct_nib.header['dim'][3].astype(int) 
        # Contour.ContourMask --> this should be only the contour, so far we don't need it so I'll skip it
      
        # apend to self
        self.Contours.append(Contour)
        

  def export_Dicom(self, refCT, outputFile):   
    print("EXPORT DICOM")             
    # meta data
    
    # generate UID
    #uid_base = '' #TODO define one for us if we want? Siri is using: uid_base='1.2.826.0.1.3680043.10.230.',
    # personal UID, applied for via https://www.medicalconnections.co.uk/FreeUID/
    
    SOPInstanceUID = pydicom.uid.generate_uid() #TODO verify this! Siri was using a uid_base, this line is taken from OpenTPS writeRTPlan
    #SOPInstanceUID = pydicom.uid.generate_uid('1.2.840.10008.5.1.4.1.1.481.3.') # siri's version
    
    meta = pydicom.dataset.FileMetaDataset()
    meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.481.3' # UID class for RTSTRUCT
    meta.MediaStorageSOPInstanceUID = SOPInstanceUID
    # meta.ImplementationClassUID = uid_base + '1.1.1' # Siri's
    meta.ImplementationClassUID =  '1.2.250.1.59.3.0.3.5.0' # from OpenREGGUI
    meta.TransferSyntaxUID = '1.2.840.10008.1.2' # Siri's and OpenREGGUI
    meta.FileMetaInformationGroupLength = 188 # from Siri
    # meta.ImplementationVersionName = 'DCIE 2.2' # from Siri
    
    
    # Main data elements - only required fields, optional fields like StudyDescription are not included for simplicity
    ds = pydicom.dataset.FileDataset(outputFile, {}, file_meta=meta, preamble=b"\0" * 128) # preamble is taken from this example https://pydicom.github.io/pydicom/dev/auto_examples/input_output/plot_write_dicom.html#sphx-glr-auto-examples-input-output-plot-write-dicom-py
    
    # Patient info - will take it from the referenced CT image
    ds.PatientName = refCT.PatientInfo.PatientName
    ds.PatientID = refCT.PatientInfo.PatientID
    ds.PatientBirthDate = refCT.PatientInfo.PatientBirthDate
    ds.PatientSex = refCT.PatientInfo.PatientSex
    
    # General Study 
    dt = datetime.datetime.now()
    ds.StudyDate = dt.strftime('%Y%m%d')
    ds.StudyTime = dt.strftime('%H%M%S.%f')
    ds.AccessionNumber = '1' # A RIS/PACS (Radiology Information System/picture archiving and communication system) generated number that identifies the order for the Study.
    ds.ReferringPhysicianName = 'NA'
    ds.StudyInstanceUID = refCT.StudyInfo.StudyInstanceUID # get from reference CT to indicate that they belong to the same study
    ds.StudyID = refCT.StudyInfo.StudyID # get from reference CT to indicate that they belong to the same study
    
    # RT Series
    #ds.SeriesDate # optional
    #ds.SeriesTime # optional
    ds.Modality = 'RTSTRUCT'
    ds.SeriesDescription = 'AI-predicted' + dt.strftime('%Y%m%d') + dt.strftime('%H%M%S.%f')
    ds.OperatorsName = 'MIRO AI team'
    ds.SeriesInstanceUID = pydicom.uid.generate_uid() # if we have a uid_base --> pydicom.uid.generate_uid(uid_base)
    ds.SeriesNumber = '1'
    
    # General Equipment
    ds.Manufacturer = 'MIRO lab'
    #ds.InstitutionName = 'MIRO lab' # optional
    #ds.ManufacturerModelName = 'nnUNet' # optional, but can be a good tag to insert the model information or label
    #ds.SoftwareVersions # optional, but can be used to insert the version of the code in PARROT or the version of the model
    
    # Frame of Reference
    ds.FrameOfReferenceUID = refCT.FrameOfReferenceUID
    ds.PositionReferenceIndicator = '' # empty if unknown - info here https://dicom.innolitics.com/ciods/rt-structure-set/frame-of-reference/00201040
    
    # Structure Set
    ds.StructureSetLabel = 'AI predicted' # do not use - or spetial characters or the Dicom Validation in Raystation will give a warning
    #ds.StructureSetName # optional
    #ds.StructureSetDescription # optional
    ds.StructureSetDate = dt.strftime('%Y%m%d')
    ds.StructureSetTime = dt.strftime('%H%M%S.%f')
    ds.ReferencedFrameOfReferenceSequence = pydicom.Sequence()# optional
    # we assume there is only one, the CT
    dssr = pydicom.Dataset()
    dssr.FrameOfReferenceUID = refCT.FrameOfReferenceUID
    dssr.RTReferencedStudySequence = pydicom.Sequence()
    # fill in sequence
    dssr_refStudy = pydicom.Dataset()
    dssr_refStudy.ReferencedSOPClassUID = '1.2.840.10008.3.1.2.3.1' # Study Management Detached
    dssr_refStudy.ReferencedSOPInstanceUID = refCT.StudyInfo.StudyInstanceUID
    dssr_refStudy.RTReferencedSeriesSequence = pydicom.Sequence()
    #initialize
    dssr_refStudy_series = pydicom.Dataset()
    dssr_refStudy_series.SeriesInstanceUID = refCT.SeriesInstanceUID
    dssr_refStudy_series.ContourImageSequence = pydicom.Sequence()
    # loop over slices of CT
    for slc in range(len(refCT.SOPInstanceUIDs)):
        dssr_refStudy_series_slc = pydicom.Dataset()
        dssr_refStudy_series_slc.ReferencedSOPClassUID = refCT.SOPClassUID
        dssr_refStudy_series_slc.ReferencedSOPInstanceUID = refCT.SOPInstanceUIDs[slc]
        # append
        dssr_refStudy_series.ContourImageSequence.append(dssr_refStudy_series_slc)
    
    # append
    dssr_refStudy.RTReferencedSeriesSequence.append(dssr_refStudy_series)
    # append
    dssr.RTReferencedStudySequence.append(dssr_refStudy)
    #append
    ds.ReferencedFrameOfReferenceSequence.append(dssr)
    #
    ds.StructureSetROISequence = pydicom.Sequence()   
    # loop over the ROIs to fill in the fields
    for iroi in range(self.NumContours):
        # initialize the Dataset        
        dssr = pydicom.Dataset()
        dssr.ROINumber = iroi + 1 # because iroi starts at zero and ROINumber cannot be zero
        dssr.ReferencedFrameOfReferenceUID = ds.FrameOfReferenceUID # coming from refCT
        dssr.ROIName = self.Contours[iroi].ROIName
        #dssr.ROIDescription # optional 
        dssr.ROIGenerationAlgorithm = 'AUTOMATIC' # can also be 'SEMIAUTOMATIC' OR 'MANUAL', info here https://dicom.innolitics.com/ciods/rt-structure-set/structure-set/30060020/30060036
        #TODO enable a function to tell us which type of GenerationAlgorithm we have    
        ds.StructureSetROISequence.append(dssr)

    # delete to remove space
    del dssr
    
    #TODO merge all loops into one to be faster, although like this the code is easier to follow I find
    
    # ROI Contour
    ds.ROIContourSequence = pydicom.Sequence()
    # loop over the ROIs to fill in the fields
    for iroi in range(self.NumContours):
        # initialize the Dataset
        dssr = pydicom.Dataset()
        dssr.ROIDisplayColor = self.Contours[iroi].ROIDisplayColor
        dssr.ReferencedROINumber = iroi + 1 # because iroi starts at zero and ReferencedROINumber cannot be zero
        dssr.ContourSequence = pydicom.Sequence() 
        # mask to polygon
        polygonMeshList = self.Contours[iroi].getROIContour()
        # get z vector
        z_coords = list(np.arange(self.Contours[iroi].Mask_Offset[2],self.Contours[iroi].Mask_Offset[2]+self.Contours[iroi].Mask_GridSize[2]*self.Contours[iroi].Mask_PixelSpacing[2], self.Contours[iroi].Mask_PixelSpacing[2]))
        # loop over the polygonMeshList to fill in ContourSequence
        for polygon in polygonMeshList:

            # initialize the Dataset
            dssr_slc = pydicom.Dataset()
            dssr_slc.ContourGeometricType = 'CLOSED_PLANAR' # can also be 'POINT', 'OPEN_PLANAR', 'OPEN_NONPLANAR', info here https://dicom.innolitics.com/ciods/rt-structure-set/roi-contour/30060039/30060040/30060042
            #TODO enable the proper selection of the ContourGeometricType

            # fill in contour points and data
            dssr_slc.NumberOfContourPoints = len(polygon[0::3])
            #dssr_slc.ContourNumber # optional
            # Smooth contour
            smoothed_array_2D = Taubin_smoothing(np.transpose(np.array([polygon[0::3],polygon[1::3]])))
            # fill in smoothed contour
            polygon[0::3] = smoothed_array_2D[:,0]
            polygon[1::3] = smoothed_array_2D[:,1]
            dssr_slc.ContourData = polygon
            
            #get slice
            polygon_z = polygon[2]
            slc = z_coords.index(polygon_z)
            # fill in ContourImageSequence
            dssr_slc.ContourImageSequence = pydicom.Sequence() # Sequence of images containing the contour
            # in our case, we assume we only have one, the reference CT (refCT)
            dssr_slc_ref = pydicom.Dataset()
            dssr_slc_ref.ReferencedSOPClassUID = refCT.SOPClassUID
            dssr_slc_ref.ReferencedSOPInstanceUID = refCT.SOPInstanceUIDs[slc]
            dssr_slc.ContourImageSequence.append(dssr_slc_ref)
              
            # append Dataset to Sequence
            dssr.ContourSequence.append(dssr_slc)
            
        # append Dataset
        ds.ROIContourSequence.append(dssr)
    
    # RT ROI Observations
    ds.RTROIObservationsSequence = pydicom.Sequence()
    # loop over the ROIs to fill in the fields
    for iroi in range(self.NumContours):
        # initialize the Dataset
        dssr = pydicom.Dataset()
        dssr.ObservationNumber = iroi + 1 # because iroi starts at zero and ReferencedROINumber cannot be zero
        dssr.ReferencedROINumber = iroi + 1 ## because iroi starts at zero and ReferencedROINumber cannot be zero
        dssr.ROIObservationLabel = self.Contours[iroi].ROIName #optional
        dssr.RTROIInterpretedType = 'ORGAN' # we can have many types, see here https://dicom.innolitics.com/ciods/rt-structure-set/rt-roi-observations/30060080/300600a4
        # TODO enable a better fill in of the RTROIInterpretedType
        dssr.ROIInterpreter = '' # empty if unknown
        # append Dataset
        ds.RTROIObservationsSequence.append(dssr)
    
    # Approval
    ds.ApprovalStatus = 'UNAPPROVED'#'APPROVED' 
    # if ds.ApprovalStatus = 'APPROVED', then we need to fill in the reviewer information
    #ds.ReviewDate = dt.strftime('%Y%m%d')
    #ds.ReviewTime = dt.strftime('%H%M%S.%f')
    #ds.ReviewerName = 'MIRO AI team'
    
    # SOP common
    ds.SpecificCharacterSet = 'ISO_IR 100' # conditionally required - see info here https://dicom.innolitics.com/ciods/rt-structure-set/sop-common/00080005
    #ds.InstanceCreationDate # optional
    #ds.InstanceCreationTime # optional
    ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.481.3' #RTSTRUCT file
    ds.SOPInstanceUID = SOPInstanceUID# Siri's --> pydicom.uid.generate_uid(uid_base)
    #ds.InstanceNumber # optional
    
    # save dicom file
    print("Export dicom RTSTRUCT: " + outputFile)
    ds.save_as(outputFile)



      
class ROIcontour:

    def __init__(self):
      self.SeriesInstanceUID = ""
      self.ROIName = ""
      self.ContourSequence = []
      
    def getROIContour(self): # this is from new version of OpenTPS, I(ana) have adapted it to work with old version of self.Contours[i].Mask
    
        try:
            from skimage.measure import label, find_contours
            from skimage.segmentation import find_boundaries
        except:
            print('Module skimage (scikit-image) not installed, ROIMask cannot be converted to ROIContour')
            return 0
    
        polygonMeshList = []
        for zSlice in range(self.Mask.shape[2]):
    
            labeledImg, numberOfLabel = label(self.Mask[:, :, zSlice], return_num=True)
    
            for i in range(1, numberOfLabel + 1):
    
                singleLabelImg = labeledImg == i
                contours = find_contours(singleLabelImg.astype(np.uint8), level=0.6)
    
                if len(contours) > 0:
    
                    if len(contours) == 2:
    
                        ## use a different threshold in the case of an interior contour
                        contours2 = find_contours(singleLabelImg.astype(np.uint8), level=0.4)
    
                        interiorContour = contours2[1]
                        polygonMesh = []
                        for point in interiorContour:
    
                            xCoord = np.round(point[1]) * self.Mask_PixelSpacing[1] + self.Mask_Offset[1] # original Damien in OpenTPS
                            yCoord = np.round(point[0]) * self.Mask_PixelSpacing[0] + self.Mask_Offset[0] # original Damien in OpenTPS
                            # xCoord = np.round(point[1]) * self.Mask_PixelSpacing[0] + self.Mask_Offset[0] #AB
                            # yCoord = np.round(point[0]) * self.Mask_PixelSpacing[1] + self.Mask_Offset[1] #AB
                            zCoord = zSlice * self.Mask_PixelSpacing[2] + self.Mask_Offset[2]
    
                            polygonMesh.append(yCoord) # original Damien in OpenTPS
                            polygonMesh.append(xCoord) # original Damien in OpenTPS
                            # polygonMesh.append(xCoord) # AB
                            # polygonMesh.append(yCoord) # AB
                            polygonMesh.append(zCoord)
    
                        polygonMeshList.append(polygonMesh)
    
                    contour = contours[0]
    
                    polygonMesh = []
                    for point in contour:
    
                        #xCoord = np.round(point[1]) * self.Mask_PixelSpacing[1] + self.Mask_Offset[1] # original Damien in OpenTPS
                        #yCoord = np.round(point[0]) * self.Mask_PixelSpacing[0] + self.Mask_Offset[0] # original Damien in OpenTPS
                        xCoord = np.round(point[1]) * self.Mask_PixelSpacing[0] + self.Mask_Offset[0] #AB
                        yCoord = np.round(point[0]) * self.Mask_PixelSpacing[1] + self.Mask_Offset[1] #AB
                        zCoord = zSlice * self.Mask_PixelSpacing[2] + self.Mask_Offset[2]
    
                        polygonMesh.append(xCoord) # AB
                        polygonMesh.append(yCoord) # AB
                        #polygonMesh.append(yCoord) # original Damien in OpenTPS
                        #polygonMesh.append(xCoord) # original Damien in OpenTPS
                        polygonMesh.append(zCoord)
    
                    polygonMeshList.append(polygonMesh)
    
        ## I (ana) will comment this part since I will not use the class ROIContour for simplicity ###
        #from opentps.core.data._roiContour import ROIContour  ## this is done here to avoir circular imports issue
        #contour = ROIContour(name=self.ROIName, displayColor=self.ROIDisplayColor)
        #contour.polygonMesh = polygonMeshList
    
        #return contour
        
        # instead returning the polygonMeshList directly
        return polygonMeshList