示例#1
0
        public ArrayList retrieveAllHashes()
        {
            DBConnect connection       = new DBConnect();
            ArrayList imageFingerprint = new ArrayList();
            string    query            = "SELECT i.imageId, i.imageTitle, i.imageAuthor, i.digestId, i.digestHash, ic.value FROM image i INNER JOIN imageCoeffs ic ON i.imageId = ic.imageId;";

            try
            {
                MySqlCommand cmd = new MySqlCommand(query, connection.OpenConnection());
                cmd.CommandText = query;
                cmd.Prepare();

                MySqlDataReader dataReader   = cmd.ExecuteReader();
                int             i            = 0;
                byte[]          digestCoeffs = new byte[40];

                while (dataReader.Read())
                {
                    int    imageId     = (int)dataReader[0];
                    string imageTitle  = (string)dataReader[1];
                    string imageAuthor = (string)dataReader[2];
                    string digestId    = (string)dataReader[3];
                    ulong  hash        = (ulong)dataReader[4];

                    if (digestId.Equals("n"))
                    {
                        digestId = null;
                    }

                    int temp = (int)dataReader[5];
                    digestCoeffs[i] = (byte)temp;
                    i++;

                    if (i == 40)
                    {
                        ImageFingerprint image = new ImageFingerprint(imageTitle, imageAuthor, digestId, digestCoeffs, hash);
                        imageFingerprint.Add(image);
                        i            = 0;
                        digestCoeffs = new byte[40];
                    }
                }

                return(imageFingerprint);
            }
            catch (Exception ex)
            {
                return(null);
            }
        }
示例#2
0
        public ArrayList retrieveAllHashes()
        {
            DBConnect connection = new DBConnect();
            ArrayList imageFingerprint = new ArrayList();
            string query = "SELECT i.imageId, i.imageTitle, i.imageAuthor, i.digestId, i.digestHash, ic.value FROM image i INNER JOIN imageCoeffs ic ON i.imageId = ic.imageId;";

            try
            {
                MySqlCommand cmd = new MySqlCommand(query, connection.OpenConnection());
                cmd.CommandText = query;
                cmd.Prepare();

                MySqlDataReader dataReader = cmd.ExecuteReader();
                int i = 0;
                byte[] digestCoeffs = new byte[40];

                while (dataReader.Read())
                {
                    int imageId = (int)dataReader[0];
                    string imageTitle = (string)dataReader[1];
                    string imageAuthor = (string)dataReader[2];
                    string digestId = (string)dataReader[3];
                    ulong hash = (ulong)dataReader[4];

                    if (digestId.Equals("n"))
                        digestId = null;

                    int temp = (int)dataReader[5];
                    digestCoeffs[i] = (byte)temp;
                    i++;

                    if (i == 40)
                    {
                        ImageFingerprint image = new ImageFingerprint(imageTitle, imageAuthor, digestId, digestCoeffs, hash);
                        imageFingerprint.Add(image);
                        i = 0;
                        digestCoeffs = new byte[40];
                    }
                }

                return imageFingerprint;
            }
            catch (Exception ex)
            {
                return null;
            }
        }
示例#3
0
        protected void scanAudio_Click(object sender, EventArgs e)
        {
            addSongResult.Visible = false;
            waiting.Visible = false;
            ingestFingerprint.Visible = false;
            videoAuthor.Visible = false;
            videoTitle.Visible = false;

            if (uploadedfile.HasFile)
            {
                string displayedResult = "";
                ArrayList listOfResults = new ArrayList();

                // Upload the file to server
                String fileName = uploadedfile.FileName;
                savePath += fileName;
                uploadedfile.SaveAs(savePath);

                string fileExtension = Path.GetExtension(savePath);

                if (!fileExtension.Equals(".mp3"))
                {
                    savePath = convertVideoToAudio(fileName);
                    savePath = savePath.Trim();
                }

                SongRetrieval sr = new SongRetrieval();
                sr.retrieveAllHash();
                ArrayList hashValue = sr.getSongHashes();

                double songLength = Math.Round((getMediaDuration(savePath) / 4) / 60, 2);

                //for (int b = 0; b < (getMediaDuration(savePath) / 4 - 2); b += 30)
                for(int b = 0; b < 60; b+=30)
                {
                    // Analyze the files (input file)
                    string filename = savePath;
                    IntPtr fingerprint = getFingerPrint(filename, b, 30);
                    string analysisResults = Marshal.PtrToStringAnsi(fingerprint);

                    SongInfo song = JsonHelper.JsonDeserializer<SongInfo>(analysisResults);

                    double[,] clientCode = analyzeCodegen(song.code);

                    // Analyze input file with hashes obtained from database
                    double[,] masterCode = null;
                    ArrayList matchingEvents = new ArrayList(); //List of events that gives the greatest match (value stored is the index that gives the most number of matches)

                    int maxConsecutiveCount = 9;
                    int previousCount = 0;
                    int currentCount = 0;

                    // Identify events that matches
                    for (int i = 0; i < hashValue.Count; i++)
                    {
                        masterCode = analyzeCodegen(hashValue[i].ToString());
                        int[] comparisonResult = compareClientMasterWithoutTimeOffset(clientCode, masterCode);
                        currentCount = comparisonResult[1];

                        // Compares first criteria - Consecutive hashes
                        if (comparisonResult[0] == maxConsecutiveCount)
                        {
                            matchingEvents.Add(i);
                        }
                        else if (comparisonResult[0] > maxConsecutiveCount)
                        {
                            matchingEvents.Clear();
                            matchingEvents.Add(i);

                            maxConsecutiveCount = comparisonResult[0];
                        }

                        // Compares second criteria - Total count
                        if (matchingEvents.Count > 1)
                        {
                            if (currentCount > previousCount)
                            {
                                matchingEvents.Clear();
                                matchingEvents.Add(i);
                            }
                            else
                                matchingEvents.RemoveAt(1);
                        }

                        if (comparisonResult[0] >= maxConsecutiveCount)
                            previousCount = currentCount;
                    }

                    ArrayList songIdList = (ArrayList)sr.getSongIdList();
                    int[] songId = new int[matchingEvents.Count];

                    for (int i = 0; i < matchingEvents.Count; i++)
                    {
                        songId[i] = (int)songIdList[(int)matchingEvents[i]];
                    }

                    // Identify the song
                    string title = "Unidentified";
                    SongRetrieval identifiedSong = null;

                    for (int i = 0; i < songId.Length; i++)
                    {
                        SongRetrieval songHash = new SongRetrieval((int)songId[i], true);

                        if (i == 0)
                        {
                            identifiedSong = songHash.retrieveSongFromSongId();
                            title = identifiedSong.getSongTitle();
                        }
                        else
                        {
                            if (!title.Equals(songHash.retrieveSongFromSongId().getSongTitle()))
                                title = "Unidentified";
                        }
                    }

                    // Identifying the accuracy of the results
                    int accuracy = 0;

                    if (maxConsecutiveCount > 700)
                        accuracy = 100;
                    else if (maxConsecutiveCount > 500)
                        accuracy = 80;
                    else if (maxConsecutiveCount > 300)
                        accuracy = 50;
                    else if (maxConsecutiveCount > 100)
                        accuracy = 30;
                    else if (maxConsecutiveCount > 50)
                        accuracy = 20;
                    else if (title.Equals("Unidentified"))
                        accuracy = 0;
                    else
                        accuracy = 10;

                    // Identifying whether the identified song matches the meta data
                    string misc = "";
                    if (song.metadata.title.Equals(""))
                    {
                        misc = "This song has no file properties attached.";
                    }
                    else if (title.Equals(song.metadata.title))
                    {
                        if (accuracy < 90)
                            accuracy += 10;
                        misc = "Song identified matches file properties of the file sent for analysis.";
                    }
                    else if (title.Equals("Unidentified"))
                    {
                        misc = "Song could not be identified. This song might not exist in our database.";
                    }
                    else
                    {
                        if (accuracy <= 10)
                            accuracy -= 10;

                        if (accuracy > 50)
                            misc = "The file properties might not be labelled correctly as it does not match the song identifed by us!";
                        else
                        {
                            misc = "Song identified might not be accurate as the desired song might not be in our database or large changes has been done to the file";
                        }
                    }

                    displayedResult = "File Analyzed: " + fileName + "<br/><br/>File Properties<br/>============<br/>" +
                                        "Title: " + song.metadata.title + "<br/>" +
                                        "Album Artist: " + song.metadata.artist + "<br/>" +
                                        "Album: " + song.metadata.release + "<br/>" +
                                        "Length: " + songLength + " minutes<br/>" +
                                        "Genre: " + song.metadata.genre + "<br/>" +
                                        "Bitrate: " + song.metadata.bitrate + " kbps<br/><br/>" +
                                        "Identification Results" + "<br/>=====================<br/>";

                    // If the song can be identified and the accuracy threshold is set to 20%, it will be added to be displayed as results
                    if (identifiedSong != null && accuracy > 20)
                    {
                        SongIdentification si = new SongIdentification(identifiedSong, accuracy, maxConsecutiveCount, misc);
                        listOfResults.Add(si);
                    }
                }

                string sumTitle = "";

                for (int j = 0; j < listOfResults.Count; j++)
                {
                    SongIdentification mainObj = (SongIdentification)listOfResults[j];
                    SongRetrieval song = mainObj.getIdentifiedSong();

                    if (!sumTitle.Contains(song.getSongTitle()))
                    {
                        if (!sumTitle.Equals("") && mainObj.getAccuracy() > 10)
                        {
                            sumTitle = sumTitle + ", " + song.getSongTitle();
                        }
                        else
                        {
                            sumTitle = song.getSongTitle();
                        }

                        displayedResult = displayedResult + "Title: " + song.getSongTitle() + "<br/>" +
                                        "Album Artist: " + song.getArtistName() + "<br/>" +
                                        "Album: " + song.getAlbumName() + "<br/>" +
                                        "Total Matching Consecutive Hashes: " + mainObj.getMaxConsecutiveHashes() + "<br/>" +
                                        "Accuracy Level: " + mainObj.getAccuracy() + " %<br/>" +
                                        "Matching of File Properties: " + mainObj.getMisc() + "<br/><br/>";
                    }
                }

                if (!sumTitle.Equals(""))
                {
                    if (fileExtension.Equals(".mp3"))
                    {
                        result.Text = "<b>File Analyzed: " + fileName + "<br/>Song Identified: " + sumTitle + "</b>";
                        displayedResult = displayedResult + "Conclusion<br/>=============<br/>" +
                                    "Song Identified: " + sumTitle;
                    }
                    else
                    {
                        result.Text = "<b>File Analyzed: " + fileName + "<br/>The video consist of the following identified song: " + sumTitle + "</b>";
                        displayedResult = displayedResult + "Conclusion<br/>=============<br/>" +
                                    "The video consist of the following identified song: " + sumTitle;
                    }

                    ingestFingerprint.Visible = false;
                }
                else
                {
                    if (fileExtension.Equals(".mp3"))
                    {
                        result.Text = "<b>File Analyzed: " + fileName + "<br/>Song could not be identified</b><br/><br/>To contribute, you can choose to add this song into our database.";

                        displayedResult = displayedResult + "Title: Unidentified" + "<br/>" +
                                        "Album Artist: Unidentified" + "<br/>" +
                                        "Album: Unidentified" + "<br/><br/>" +
                                        "Conclusion<br/>=========<br/>" +
                                        "Song could not be identified";

                        ingestFingerprint.Visible = true;
                        ingestFingerprint.Enabled = true;
                        ingestFingerprint.Text = "Add Song";
                        pathName.Text = savePath;
                    }
                    else
                    {
                        // Video format - no song identified
                        // Extract key frames from the videos to do comparison
                        int totalFrames = convertVideoToKeyFrames(fileName);
                        string outputFile = @"C:\temp\uploads\output";

                        // Retrieve all fingerprint for comparison
                        ImageFingerprint image = new ImageFingerprint();
                        ArrayList fingerprint = image.retrieveAllHashes();

                        int pcc = 0;
                        ImageFingerprint matchingPictures = null;
                        int highestPcc = 35;
                        bool identified = false;

                        for (int i = 1; i < totalFrames; i++)
                        {
                            string fileCount = i.ToString();
                            while (fileCount.Count() < 3)
                            {
                                fileCount = "0" + fileCount;
                            }
                            outputFile = outputFile + fileCount + ".jpg";

                            // Retrieve digest of input image for comparison
                            IdentifyImage1.DigestRaw raw = new IdentifyImage1.DigestRaw();
                            IdentifyImage1.ph_image_digest(outputFile, 1, 1, ref raw, 180);
                            IdentifyImage1.Digest d = IdentifyImage1.Digest.fromRaw(raw);

                            for (int j = 0; j < fingerprint.Count; j++)
                            {
                                // Generate a struct for comparison from database
                                IdentifyImage1.Digest d2 = new IdentifyImage1.Digest();
                                d2.id = ((ImageFingerprint)fingerprint[j]).getDigestId();
                                d2.coeffs = ((ImageFingerprint)fingerprint[j]).getDigestCoeffs();
                                d2.size = 40;

                                // Compare the results
                                pcc = IdentifyImage1.computeSimilarities(d.coeffs, d2.coeffs);

                                if (pcc > highestPcc)
                                {
                                    matchingPictures = (ImageFingerprint)fingerprint[j];
                                    highestPcc = pcc;
                                }
                            }

                            if (matchingPictures == null)
                            {
                                ulong hasha = 0;
                                IdentifyImage1.ph_dct_imagehash(outputFile, ref hasha);

                                int minHammingDistance = 20;
                                for (int a = 0; a < fingerprint.Count; a++)
                                {
                                    // Compute Hamming Distance
                                    int hammingDistance = IdentifyImage1.computeHammingDistance(hasha, ((ImageFingerprint)fingerprint[a]).getHash());

                                    if (hammingDistance < minHammingDistance)
                                    {
                                        matchingPictures = (ImageFingerprint)fingerprint[a];
                                        minHammingDistance = hammingDistance;
                                    }
                                }

                                if (minHammingDistance < 10)
                                {
                                    identified = true;
                                }
                            }
                            else
                                identified = true;

                            if (identified)
                                break;
                            else
                            {
                                highestPcc = 35;
                                outputFile = @"C:\temp\uploads\output";
                                matchingPictures = null;
                            }
                        }

                        if (identified)
                        {
                            result.Text = "<b>File Analyzed: " + fileName + "<br/>Song in the video could not be identified but identical key frames from videos has been identified to be identical to copyrighted videos</b><br/>";
                            displayedResult = displayedResult + "Title: Unidentified" + "<br/>" +
                                            "Album Artist: Unidentified" + "<br/>" +
                                            "Album: Unidentified" + "<br/>" +
                                            "Key Frames: Identified to be identical to copyrighted videos" + "<br/><br/>" +
                                            "Conclusion<br/>=========<br/>" +
                                            "Song from the video could not be identified but key frames are identified to be identical to copyrighted videos.";
                        }
                        else
                        {
                            result.Text = "<b>File Analyzed: " + fileName + "<br/>Song and key frames from video could not be identified</b>";

                            displayedResult = displayedResult + "Title: Unidentified" + "<br/>" +
                                            "Album Artist: Unidentified" + "<br/>" +
                                            "Album: Unidentified" + "<br/>" +
                                            "Key Frames: Unidentified" + "<br/><br/>" +
                                            "Conclusion<br/>=========<br/>" +
                                            "Song and key frames from the video could not be identified.";

                            ingestFingerprint.Visible = true;
                            ingestFingerprint.Enabled = true;
                            ingestFingerprint.Text = "Add Video";
                            pathName.Text = savePath + ";" + totalFrames;

                            videoAuthor.Visible = true;
                            videoTitle.Visible = true;

                            videoTitle.Text = "Enter Title...";
                            videoAuthor.Text = "Enter Author...";

                            videoTitle.Attributes.Add("onfocus", "if(this.value == 'Enter Title...') this.value='';");
                            videoTitle.Attributes.Add("onblur", "if(this.value == '' || this.value == ' ') this.value='Enter Title...'");

                            videoAuthor.Attributes.Add("onfocus", "if(this.value == 'Enter Author...') this.value='';");
                            videoAuthor.Attributes.Add("onblur", "if(this.value == '' || this.value == ' ') this.value='Enter Author...'");
                        }
                    }
                }

                exportedResults.Text = displayedResult;

                waiting.Text = "Analysis Completed!";

                result.Visible = true;
                exportToPdf.Enabled = true;
                exportToPdf.Visible = true;
            }
            else
            {
                waiting.Visible = true;
                waiting.Text = "No file has been selected for analysis";
            }
        }
示例#4
0
        protected void ingestFingerprint_Click(object sender, EventArgs e)
        {
            bool success = false;
            string text = ingestFingerprint.Text;

            if (!pathName.Text.Equals(""))
            {
                if(text.Equals("Add Song"))
                {
                    success = addNewFingerprint(pathName.Text);
                    if (success)
                    {
                        addSongResult.Text = "Song has been added successfully. Thank you for your contribution";
                        ingestFingerprint.Enabled = false;
                    }
                    else
                        addSongResult.Text = "Required meta data fields has been left empty. Please fill in associated meta data";
                }
                else
                {
                    // Add video - add both song and keyframes
                    // Retrieve path name for song and keyframes (Format: filename;totalNumOfKeyFrames)
                    string pathParam = pathName.Text;
                    int index = pathName.Text.IndexOf(";");
                    int totalFrames = Convert.ToInt32(pathParam.Substring(index+1));
                    string path = pathName.Text.Substring(0, index);

                    string title = videoTitle.Text;
                    string author = videoAuthor.Text;

                    if (!String.IsNullOrEmpty(title) && !String.IsNullOrEmpty(author) && !title.Equals("Enter Title...") && !author.Equals("Enter Author..."))
                    {
                        // Add song fingerprint
                        success = addNewFingerprint(path);

                        if (success)
                        {
                            // Carry out addition of key frames
                            string outputFile = @"C:\temp\uploads\output";
                            for (int i = 1; i < totalFrames; i++)
                            {
                                string fileCount = i.ToString();
                                while (fileCount.Count() < 3)
                                {
                                    fileCount = "0" + fileCount;
                                }
                                outputFile = outputFile + fileCount + ".jpg";

                                IdentifyImage1.DigestRaw raw = new IdentifyImage1.DigestRaw();
                                IdentifyImage1.ph_image_digest(outputFile, 1, 1, ref raw, 180);
                                IdentifyImage1.Digest d = IdentifyImage1.Digest.fromRaw(raw);

                                ulong hash = 0;
                                IdentifyImage1.ph_dct_imagehash(outputFile, ref hash);

                                outputFile = @"C:\temp\uploads\output";

                                ImageFingerprint image;
                                if (!String.IsNullOrEmpty(d.id))
                                    image = new ImageFingerprint(title, author, d.id.ToString(), d.coeffs, hash);
                                else
                                    image = new ImageFingerprint(title, author, null, d.coeffs, hash);

                                success = image.insertNewImage();

                                if (success)
                                {
                                    addSongResult.Text = "Video has been added successfully. Thank you for your contribution";
                                    ingestFingerprint.Enabled = false;
                                }
                                else
                                {
                                    addSongResult.Text = "Error in adding videos. Please try again later.";
                                }
                            }
                        }
                        else
                        {
                            addSongResult.Text = "Required meta data fields has been left empty. Please fill in associated meta data";
                        }
                    }
                    else
                    {
                        addSongResult.Text = "Please fill in associated fields about this video.";
                    }
                }
            }
            else
            {
                addSongResult.Text = "No file has been selected for addition into our database.";
            }

            addSongResult.Visible = true;
        }
示例#5
0
        protected void addImage_Click(object sender, EventArgs e)
        {
            DigestRaw raw = new DigestRaw();
            ph_image_digest(pathName.Text, 1, 1, ref raw, 180);
            Digest d = Digest.fromRaw(raw);

            ulong hash = 0;
            ph_dct_imagehash(pathName.Text, ref hash);

            string title = imageTitle.Text;
            string author = imageAuthor.Text;

            if (!String.IsNullOrEmpty(title) && !String.IsNullOrEmpty(author) && !title.Equals("Enter Title...") && !author.Equals("Enter Author..."))
            {
                ImageFingerprint image;
                if (!String.IsNullOrEmpty(d.id))
                    image = new ImageFingerprint(title, author, d.id.ToString(), d.coeffs, hash);
                else
                    image = new ImageFingerprint(title, author, null, d.coeffs, hash);

                if (image.insertNewImage())
                    addImageResult.Text = "Image successfully added. Thank you for your contribution";
                else
                    addImageResult.Text = "Error in inserting image. Please try again later";
            }
            else
            {
                addImageResult.Text = "Please fill in the appropriate fields to add an image into our database.";
            }

            addImageResult.Visible = true;
        }
示例#6
0
        protected void scanFile_Click(object sender, EventArgs e)
        {
            waiting.Visible = false;
            addImage.Visible = false;
            imageTitle.Visible = false;
            imageAuthor.Visible = false;
            addImageResult.Visible = false;

            if (uploadedfile.HasFile)
            {
                String fileName = uploadedfile.FileName;
                savePath += fileName;
                uploadedfile.SaveAs(savePath);

                string fileExtension = Path.GetExtension(savePath);

                if (fileExtension.Equals(".jpg") || fileExtension.Equals(".png"))
                {
                    // Retrieve all fingerprint for comparison
                    ImageFingerprint image = new ImageFingerprint();
                    ArrayList fingerprint = image.retrieveAllHashes();

                    int pcc = 0;
                    ImageFingerprint matchingPictures = null;
                    int highestPcc = 30;

                    // Retrieve digest of input image for comparison
                    DigestRaw raw = new DigestRaw();
                    ph_image_digest(savePath, 1, 1, ref raw, 180);
                    Digest d = Digest.fromRaw(raw);

                    // Compare fingerprint
                    for (int i = 0; i < fingerprint.Count; i++)
                    {
                        // Generate a struct for comparison from database
                        Digest d2 = new Digest();
                        d2.id = ((ImageFingerprint)fingerprint[i]).getDigestId();
                        d2.coeffs = ((ImageFingerprint)fingerprint[i]).getDigestCoeffs();
                        d2.size = 40;

                        // Compare the results
                        pcc = computeSimilarities(d.coeffs, d2.coeffs);

                        if (pcc >= highestPcc)
                        {
                            matchingPictures = (ImageFingerprint)fingerprint[i];
                            highestPcc = pcc;
                        }
                    }

                    // Compute hash values
                    ulong hasha = 0;
                    ph_dct_imagehash(savePath, ref hasha);

                    if (matchingPictures == null)
                    {
                        int minHammingDistance = 20;
                        for (int i = 0; i < fingerprint.Count; i++)
                        {
                            // Compute Hamming Distance
                            int hammingDistance = computeHammingDistance(hasha, ((ImageFingerprint)fingerprint[i]).getHash());

                            if (hammingDistance < minHammingDistance)
                            {
                                matchingPictures = (ImageFingerprint)fingerprint[i];
                                minHammingDistance = hammingDistance;
                            }
                        }

                        if (minHammingDistance <= 10)
                        {
                            result.Text = "An image has been identified with the following information:<br/><br/>" +
                                    "Title: <b>" + matchingPictures.getImageTitle() + "</b><br/>" +
                                    "Author: " + matchingPictures.getImageAuthor() + "<br/>" +
                                    "Hamming Distance: " + minHammingDistance + "<br/>";
                        }
                        else
                        {
                            result.Text = "<b>No pictures has been identified in our database.</b><br/><br/>You can contribute by adding this image into our database.";
                            pathName.Text = savePath;
                            addImage.Visible = true;
                            imageAuthor.Visible = true;
                            imageTitle.Visible = true;

                            imageTitle.Text = "Enter Title...";
                            imageAuthor.Text = "Enter Author...";

                            imageTitle.Attributes.Add("onfocus", "if(this.value == 'Enter Title...') this.value='';");
                            imageTitle.Attributes.Add("onblur", "if(this.value == '' || this.value == ' ') this.value='Enter Title...'");

                            imageAuthor.Attributes.Add("onfocus", "if(this.value == 'Enter Author...') this.value='';");
                            imageAuthor.Attributes.Add("onblur", "if(this.value == '' || this.value == ' ') this.value='Enter Author...'");
                        }
                    }
                    else
                    {
                        result.Text = "An image has been identified with the following information:<br/><br/>" +
                                    "Title: <b>" + matchingPictures.getImageTitle() + "</b><br/>" +
                                    "Author: " + matchingPictures.getImageAuthor() + "<br/>" +
                                    "Similarities with copyrighted image: " + (highestPcc / 40.0) * 100 + " %<br/>";
                    }

                    result.Visible = true;
                }
                else
                {
                    waiting.Text = "Incorrect file format has been used. Only jpg and png files will be accepted.";
                    waiting.Visible = true;
                }
            }
            else
            {
                waiting.Text = "No file has been selected for analysis";
                waiting.Visible = true;
            }
        }