Trying to output coordinates from a CSV file and plot a CoG

Hi, I am trying to plot the CoG of 3 body landmarks stored in coordinates within a CSV file. However before I can plot the CoG, I need to import the CSV coordinates successfully for which I am struggling to acheive. Please see the attached matlab file and respond with any advice. Thanks. I am running Matlab on windows 11.

12 commentaires

Mathieu NOE
Mathieu NOE le 3 Jan 2023
Modifié(e) : Mathieu NOE le 3 Jan 2023
hi
it would help if you share the csv file along
have you tried with csvread ?
David Gill
David Gill le 3 Jan 2023
Modifié(e) : David Gill le 3 Jan 2023
Hi, here is the CSV file. And No i havent. I just tried using the CSVread function and it worked perfectly! thanks
attached is my new CSVread function.
I cannot figure out what you are calculating, however using either readtable or readmatrix will be easier than what you’re currently doing —
T1 = readtable('https://www.mathworks.com/matlabcentral/answers/uploaded_files/1250332/object_cropDLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000.csv')
T1 = 7500×10 table
scorer DLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000 DLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000_1 DLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000_2 DLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000_3 DLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000_4 DLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000_5 DLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000_6 DLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000_7 DLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000_8 ______ __________________________________________________________ ____________________________________________________________ ____________________________________________________________ ____________________________________________________________ ____________________________________________________________ ____________________________________________________________ ____________________________________________________________ ____________________________________________________________ ____________________________________________________________ 0 699.87 79.891 0.96422 641.94 217.73 0.99753 637.62 218.52 0.00081067 1 699.61 80.296 0.98424 641.63 216.81 0.99921 637.81 218.57 0.00090318 2 698.58 80.72 0.98754 640.86 214.8 0.99921 637.84 218.31 0.0010191 3 699.53 80.327 0.9768 640.27 212.96 0.99943 649.86 207.26 0.0015077 4 698.81 80.492 0.945 639.9 211.77 0.99845 649.42 206.91 0.0020226 5 698.33 80.535 0.95612 639.57 211.61 0.99893 649.5 206.94 0.0020382 6 698.46 80.216 0.97416 639.22 210.88 0.99867 649.56 206.17 0.0017877 7 697.79 80.356 0.93437 638.67 209.18 0.99918 649.38 206 0.0021939 8 698.11 79.884 0.86681 638.21 208.9 0.9975 649.47 205.8 0.0015312 9 695.95 81 0.90741 638.02 207.37 0.99377 649.56 205.6 0.0014585 10 695.62 81.047 0.8713 637.35 205.11 0.99804 649.7 205.71 0.00092554 11 695.72 80.667 0.22285 636.41 202.6 0.99903 636.39 203.12 0.00067276 12 695.52 80.425 0.72089 635.8 202.16 0.99878 636.31 202.93 0.00056085 13 694.44 80.531 0.32807 635.33 201.52 0.99801 636.32 202.36 0.00052334 14 693.85 80.307 0.074261 635.07 199.68 0.9992 636.04 201.49 0.00049541 15 693.9 80.033 0.11171 634.68 199.02 0.99948 635.84 201.07 0.00048081
file = websave('plotting_CoG','https://www.mathworks.com/matlabcentral/answers/uploaded_files/1250297/plotting_CoG.m')
file = '/users/mss.system.W90vb2/plotting_CoG.m'
type(file)
% Open the file for reading fid = fopen('object_cropDLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000.csv'); % Read the column names from row 3 header = textscan(fid, '%s %s %s %s %s %s %s %s %s %s', 1, 'Delimiter', ',', 'TreatAsEmpty', {'NA', 'na', 'NaN', 'nan'}); % Read the data from the file into a cell array data = textscan(fid, '%f %f %f %f %f %f', 'Delimiter', ',', 'TreatAsEmpty', {'NA', 'na', 'NaN', 'nan'}); % Close the file fclose(fid); % Create the table using the cell2table function table = cell2table(data, 'VariableNames', header{1}); % Extract the x & y coordinates for the body landmarks from the table: x1 = table.x1; y1 = table.y1; x2 = table.x2; y2 = table.y2; x3 = table.x3; y3 = table.y3; % Next, calculate the CoG of the body landmarks by averaging the x & y coordinates using the 'mean' function CoGx = mean([x1 x2 x3]); CoGy = mean([y1 y2 y3]);
M = table2array(T1(:,2:end))
M = 7500×9
699.8743 79.8905 0.9642 641.9415 217.7304 0.9975 637.6163 218.5184 0.0008 699.6126 80.2956 0.9842 641.6265 216.8070 0.9992 637.8083 218.5661 0.0009 698.5805 80.7200 0.9875 640.8589 214.7985 0.9992 637.8358 218.3107 0.0010 699.5323 80.3266 0.9768 640.2693 212.9610 0.9994 649.8575 207.2623 0.0015 698.8057 80.4921 0.9450 639.8954 211.7656 0.9984 649.4243 206.9110 0.0020 698.3347 80.5354 0.9561 639.5690 211.6094 0.9989 649.4987 206.9365 0.0020 698.4645 80.2159 0.9742 639.2159 210.8830 0.9987 649.5642 206.1651 0.0018 697.7853 80.3557 0.9344 638.6707 209.1821 0.9992 649.3811 206.0018 0.0022 698.1066 79.8839 0.8668 638.2108 208.9003 0.9975 649.4743 205.8028 0.0015 695.9453 80.9998 0.9074 638.0172 207.3687 0.9938 649.5565 205.6006 0.0015
ColumnMeans = mean(M)
ColumnMeans = 1×9
119.8227 62.1773 0.1561 94.4083 52.8251 0.0802 120.5071 57.5818 0.0503
Then do the other calculations on those values, if necessary. (I used readtable and then table2array here because I wanted to see what was in the table.)
.
your code is correct (if the intention was to have one CoG per landmark)
all the best
minor graphical output modifications :
% Read the data from the file into a matrix
data = csvread('object_cropDLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000.csv', 3, 0);
% Extract the x & y coordinates for the body landmarks from the matrix:
x1 = data(:,1);
y1 = data(:,2);
x2 = data(:,3);
y2 = data(:,4);
x3 = data(:,5);
y3 = data(:,6);
% Next, calculate the CoG of the body landmarks by averaging the x & y coordinates using the 'mean' function
CoGx = mean([x1 x2 x3]);
CoGy = mean([y1 y2 y3]);
% Plot the landmarks using the scatter function
figure;
scatter(x1, y1, 'r', 'o','filled');
hold on;
scatter(x2, y2, 'g', 'o','filled');
scatter(x3, y3, 'b', 'o','filled');
plot(CoGx, CoGy, '*k', 'Markersize',15);
legend({'Landmark 1', 'Landmark 2', 'Landmark 3', 'CoG'});
you say per landmark? my goal is to have 1 CoG for all 3 landmarks
How about posting a screenshot of the data plotted, with an indication of where you think the centroid should approximately be?
here is the output figure. It seems, as you pointed out, a CoG has been plotted for each landmark (3 in total). What I need is 1 CoG representing the centre point of all 3 landmarks. Do you know how I could adapt my code to do this? thanks for all the help.
simply change these lines
CoGx = mean([x1 x2 x3]);
CoGy = mean([y1 y2 y3]);
to
CoGx = mean([x1; x2; x3]);
CoGy = mean([y1; y2; y3]);
now you have 1 coG
@Mathieu NOE Thanks! I next need to label each frame of the input video with the landmarks and add the CoG as a labelled point on these. I need to export these labelled frames as a completed MP4 video file. Ive attached what I have created so far, what do you think? One processing issue I have is it pops up the scatter graph of each frame which is too computer heavy for me(i have about 600 frames). It would be better if it just showed a progress bar/percentage and processed each frame in the background. additionally i dont need a scatter graph for each frame, just need the CoG outputted in the MP4. Let me know if you have any questions! thanks again for all the help
hello
seems to me you have another post dedicated to this video processing issue (is not my expertise area I apologize)
maybe Image Analyst is again keen to help you on that topic
@Image Analyst With Mathieu's help, I have written a function to plot the CoG for the 3 landmarks on each frame and ouput a scatter graph for the average CoG position. Do you know how I could get the code to plot the CoG for each frame and then complile these CoG-labelled frames in an outputted Mp4 (with identical resolution and frame rate to the input video). I have attached the script I have been working on. The issue with this code is processing time, it pops up the scatter graph for each frame (I have over 600 frames); I just need the outputted Mp4, not the scatter graphs for each frame. any thoughts? Thanks in advance.

Connectez-vous pour commenter.

 Réponse acceptée

@David Gill try this:
% Demo to track and plot the weighted center of gravity of a video and create an output video.
% Demo by Image Analyst.
clc; % Clear the command window.
fprintf('Beginning to run %s.m ...\n', mfilename);
close all; % Close all figures (except those of imtool.)
clearvars;
workspace; % Make sure the workspace panel is showing.
format short g;
format compact;
fontSize = 14;
% Set the name and path of the input video file
inputVideoFileName = "G:\Whisker Tracking\DLC\3_landmarks_for_whiskers-David Gill-2022-12-13\object_cropDLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000_labeled.mp4";
% Set the name and path of the output video file
outputVideoFileName = 'G:\Whisker Tracking\DLC\outputCoG.mp4';
% Or, if you don't have your own video, use the sample video that ships with MATLAB.
% inputVideoFileName = 'rhinos.avi';
% outputVideoFileName = 'rhinos 2.avi';
% Read the input video file
inputVideo = VideoReader(inputVideoFileName)
% Create the output video file
outputVideo = VideoWriter(outputVideoFileName);
open(outputVideo);
% Define the region of interest to be the whole image.
mask = true(inputVideo.Height, inputVideo.Width);
% Extract the frames from the input video
hFig = figure;
frameCounter = 0;
while hasFrame(inputVideo)
thisFrame = readFrame(inputVideo);
frameCounter = frameCounter + 1;
imshow(thisFrame);
if size(thisFrame, 3) > 1
grayImage = rgb2gray(thisFrame);
else
grayImage = thisFrame;
end
% Get Weighted Centroid.
props = regionprops(mask, grayImage, 'WeightedCentroid');
xCentroid(frameCounter) = props.WeightedCentroid(1);
yCentroid(frameCounter) = props.WeightedCentroid(2);
% Plot the body landmarks and CoG on the frame
hold on;
scatter(xCentroid(frameCounter), yCentroid(frameCounter), 30, 'red', 'filled', 'o');
text(xCentroid(frameCounter), yCentroid(frameCounter), ...
' COG', 'VerticalAlignment', 'middle', 'Color', 'r', ...
'FontSize', fontSize, 'FontWeight','bold');
caption = sprintf('Frame #%d', frameCounter);
title(caption, 'FontSize', fontSize)
hold off;
drawnow; % Force immediate update
% Burn graphics into an output image. Requires the Computer Vision Toolbox.
outputFrame = insertMarker(thisFrame, [xCentroid(frameCounter), yCentroid(frameCounter)] ,'+','Color', 'r','size',30);
% Write the frame to the output video
writeVideo(outputVideo, outputFrame);
end
% Close the input video file
% close(inputVideo);
% Close the output video file
close(outputVideo);
% Close that figure and bring up another one where we can show the tracked centroid.
close(hFig);
hFig = figure;
subplot(2, 1, 1);
imshow(thisFrame);
hold on;
plot(xCentroid, yCentroid, 'r-', 'LineWidth', 2);
fontSize = 20
title('Path of Center of Gravity in Red', 'FontSize', fontSize)
% Plot centroid in next axes.
subplot(2, 1, 2);
plot(xCentroid, 'r-', 'LineWidth', 2);
hold on;
plot(yCentroid, 'g-', 'LineWidth', 2);
grid on;
legend('x centroid column', 'y centroid row', 'Location', 'west')
xlabel('Frame Number', 'FontSize', fontSize)
ylabel('Column (for x) or Row (for y)', 'FontSize', fontSize)
title('Weighted Center of Gravity (Weighted Centroid)', 'FontSize', fontSize)
% Maximize the figure.
hFig.WindowState = "maximized";
% Open the output video
if ispc
winopen(outputVideoFileName);
end
message = sprintf('Done processing %d frame.', frameCounter);
uiwait(helpdlg(message))

11 commentaires

@Image Analyst thanks for that! However, when running it I see that the CoG of the video frame is tracked, not the CoG of the 3 body landmarks of the video. Sorry if I wasnt clear about that, here is my code for plotting said CoG from the CSV file. This file contains the XY coordinates of the body landmarks from the MP4 file.
% Read the data from the file into a matrix
data = csvread('object_cropDLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000.csv', 3, 0);
% Extract the x & y coordinates for the body landmarks from the matrix:
x1 = data(:,1);
y1 = data(:,2);
x2 = data(:,3);
y2 = data(:,4);
x3 = data(:,5);
y3 = data(:,6);
% Next, calculate the CoG of the body landmarks by averaging the x & y coordinates using the 'mean' function
CoGx = mean([x1; x2; x3]);
CoGy = mean([y1; y2; y3]);
% Plot the landmarks using the scatter function
figure;
scatter(x1, y1, 'r', 'o','filled');
hold on;
scatter(x2, y2, 'g', 'o','filled');
scatter(x3, y3, 'b', 'o','filled');
plot(CoGx, CoGy, '*k', 'Markersize',15);
legend({'Landmark 1', 'Landmark 2', 'Landmark 3', 'CoG'});
Then just use your precomputed centroids instead of computing new ones in the loop. In other words, delete these lines:
% Get Weighted Centroid.
props = regionprops(mask, grayImage, 'WeightedCentroid');
xCentroid(frameCounter) = props.WeightedCentroid(1);
yCentroid(frameCounter) = props.WeightedCentroid(2);
and just use your own
xCentroid(frameCounter) = CoGx(frameCounter);
yCentroid(frameCounter) = CoGy(frameCounter);
@Image Analyst Thank you. I made the changes you said, as you can see from the attached code. However, although the code now runs, the outputted MP4 does not have a labelled CoG point in any of the frames. Any idea what could be going wrong here? I have attached a screenshot of the additional outputted figure.
% Demo to track and plot the weighted center of gravity of a video and create an output video.
clc; % Clear the command window.
fprintf('Beginning to run %s.m ...\n', mfilename);
close all; % Close all figures (except those of imtool.)
clearvars;
workspace; % Make sure the workspace panel is showing.
format short g;
format compact;
fontSize = 14;
% Set the name and path of the input video file
inputVideoFileName = "G:\Whisker Tracking\DLC\3_landmarks_for_whiskers-David Gill-2022-12-13\object_cropDLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000_labeled.mp4";
% Set the name and path of the output video file
outputVideoFileName = 'G:\Whisker Tracking\DLC\outputCoG.mp4';
% Read the input video file
inputVideo = VideoReader(inputVideoFileName)
% Create the output video file
outputVideo = VideoWriter(outputVideoFileName);
open(outputVideo);
% Define the region of interest to be the whole image.
mask = true(inputVideo.Height, inputVideo.Width);
% Extract the frames from the input video
hFig = figure;
frameCounter = 1;
while hasFrame(inputVideo)
thisFrame = readFrame(inputVideo);
frameCounter = frameCounter + 1;
imshow(thisFrame);
if size(thisFrame, 3) > 1
grayImage = rgb2gray(thisFrame);
else
grayImage = thisFrame;
end
% Read the data from the file into a matrix
data = csvread('object_cropDLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000.csv', 3, 0);
% Extract the x & y coordinates for the body landmarks from the matrix:
x1 = data(:,1);
y1 = data(:,2);
x2 = data(:,3);
y2 = data(:,4);
x3 = data(:,5);
y3 = data(:,6);
% Next, calculate the CoG of the body landmarks by averaging the x & y coordinates using the 'mean' function
CoGx = mean([x1; x2; x3]);
CoGy = mean([y1; y2; y3]);
xCentroid(frameCounter) = CoGx;
yCentroid(frameCounter) = CoGy;
% Plot the body landmarks and CoG on the frame
hold on;
scatter(xCentroid(frameCounter), yCentroid(frameCounter), 30, 'red', 'filled', 'o');
text(xCentroid(frameCounter), yCentroid(frameCounter), ...
' COG', 'VerticalAlignment', 'middle', 'Color', 'r', ...
'FontSize', fontSize, 'FontWeight','bold');
caption = sprintf('Frame #%d', frameCounter);
title(caption, 'FontSize', fontSize)
hold off;
drawnow; % Force immediate update
% Burn graphics into an output image. Requires the Computer Vision Toolbox.
outputFrame = insertMarker(thisFrame, [xCentroid(frameCounter), yCentroid(frameCounter)] ,'+','Color', 'r','size',30);
% Write the frame to the output video
writeVideo(outputVideo, outputFrame);
end
% Close the input video file
% close(inputVideo);
% Close the output video file
close(outputVideo);
% Close that figure and bring up another one where we can show the tracked centroid.
close(hFig);
hFig = figure;
subplot(2, 1, 1);
imshow(thisFrame);
hold on;
plot(xCentroid, yCentroid, 'r-', 'LineWidth', 2);
fontSize = 20
title('Path of Center of Gravity in Red', 'FontSize', fontSize)
% Plot centroid in next axes.
subplot(2, 1, 2);
plot(xCentroid, 'r-', 'LineWidth', 2);
hold on;
plot(yCentroid, 'g-', 'LineWidth', 2);
grid on;
legend('x centroid column', 'y centroid row', 'Location', 'west')
xlabel('Frame Number', 'FontSize', fontSize)
ylabel('Column (for x) or Row (for y)', 'FontSize', fontSize)
title('Weighted Center of Gravity (Weighted Centroid)', 'FontSize', fontSize)
% Maximize the figure.
hFig.WindowState = "maximized";
% Open the output video
if ispc
winopen(outputVideoFileName);
end
close(outputVideo);
message = sprintf('Done processing %d frame.', frameCounter);
uiwait(helpdlg(message))
Please attach the video object_cropDLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000_labeled.mp4
If it's too big, zip it up. If still too big, tell me if I can use the standard rhinos.avi.
@Image Analyst Hi, could you try it with the standard rhino file. Please also note that my video file is MP4. not AVI. thanks.
So do you just want to plot a spot on the image for (x1,y1), (x2, y2) and (x3, y3) for every frame in the image? Are you sure that the range of x and y are within your image? And are you sure there is an x and y for every frame?
David Gill
David Gill le 13 Jan 2023
Modifié(e) : David Gill le 13 Jan 2023
@Image Analyst well a single CoG spot for the 3 landmarks combined, for every frame in the image. There are some frames though when the landmarks are occluded and thus not visualised so no there wont be an x and y for every frame. The end goal is to eliminate objects (orange lines in image) that are not within a certain distance of the CoG, I have attached an image illustrating this longterm goal, but first I need to plot that CoG on the video and extract the coordinates of such.
Try this. I'm only plotting the average COG, not all 3 points.
% Demo by Image Analyst
clc; % Clear the command window.
close all; % Close all figures (except those of imtool.)
clear; % Erase all existing variables. Or clearvars if you want.
workspace; % Make sure the workspace panel is showing.
format short g;
format compact;
fontSize = 22;
markerSize = 15;
% Demo to track and plot the weighted center of gravity of a video and create an output video.
clc; % Clear the command window.
fprintf('Beginning to run %s.m ...\n', mfilename);
close all; % Close all figures (except those of imtool.)
clearvars;
workspace; % Make sure the workspace panel is showing.
format short g;
format compact;
fontSize = 14;
% Set the name and path of the input video file
folder = "G:\Whisker Tracking\DLC\3_landmarks_for_whiskers-David Gill-2022-12-13\";
inputVideoFileName = fullfile(folder, "\object_cropDLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000_labeled.mp4");
inputVideoFileName = 'rhinos.avi';
% Set the name and path of the output video file
outputVideoFileName = 'outputCoG.mp4'; % 'G:\Whisker Tracking\DLC\outputCoG.mp4';
% Read the input video file
inputVideo = VideoReader(inputVideoFileName)
% Create the output video file
outputVideo = VideoWriter(outputVideoFileName);
open(outputVideo);
% Read the data from the file into a matrix
data = csvread('object_cropDLC_resnet50_3_landmarks_for_whiskersDec13shuffle1_1030000.csv', 3, 0);
% Extract the x & y coordinates for the body landmarks from the matrix:
x1 = data(:,1);
y1 = data(:,2);
x2 = data(:,3);
y2 = data(:,4);
x3 = data(:,5);
y3 = data(:,6);
% Next, calculate the CoG of the body landmarks by averaging the x & y coordinates using the 'mean' function
CoGx = mean([x1, x2, x3], 2);
CoGy = mean([y1, y2, y3], 2);
%=================================================================================================
% SPECIAL CODE FOR RHINOS VIDEO ONLY!
% Scale so it fits in the image (Just for debugging using sample standard rhinos video ONLY!)
CoGx = CoGx(1:inputVideo.NumFrames);
CoGy = CoGy(1:inputVideo.NumFrames);
CoGx = rescale(CoGx, 1, inputVideo.Width);
CoGy = rescale(CoGy, 1, inputVideo.Height);
%=================================================================================================
% Plot the data
figure;
plot(x1, y1, '-', 'LineWidth', 2);
hold on;
plot(x2, y2, '-', 'LineWidth', 2);
plot(x3, y3, '-', 'LineWidth', 2);
legend('1', '2', '3')
grid on;
% Extract the frames from the input video
hFig = figure;
frameCounter = 1;
while hasFrame(inputVideo)
thisFrame = readFrame(inputVideo);
frameCounter = frameCounter + 1;
imshow(thisFrame);
if size(thisFrame, 3) > 1
grayImage = rgb2gray(thisFrame);
else
grayImage = thisFrame;
end
if frameCounter < numel(CoGy)
% Plot point if it's within the frame.
if CoGx(frameCounter) <= inputVideo.Width && CoGy(frameCounter) <= inputVideo.Height
% Plot the body landmarks and CoG on the frame
hold on;
scatter(CoGx(frameCounter), CoGy(frameCounter), 30, 'red', 'filled', 'o');
text(CoGx(frameCounter), CoGy(frameCounter), ...
' COG', 'VerticalAlignment', 'middle', 'Color', 'r', ...
'FontSize', fontSize, 'FontWeight','bold');
caption = sprintf('Frame #%d', frameCounter);
title(caption, 'FontSize', fontSize)
hold off;
drawnow; % Force immediate update
% Burn graphics into an output image. Requires the Computer Vision Toolbox.
outputFrame = insertMarker(thisFrame, [CoGx(frameCounter), CoGy(frameCounter)] ,'+','Color', 'r','size',30);
% Write the frame to the output video
writeVideo(outputVideo, outputFrame);
else
fprintf('Point (%.1f, %.1f) is outside the image, which is (%d, %d).\n', ...
CoGx(frameCounter), CoGy(frameCounter), inputVideo.Width, inputVideo.Height);
end
end
end
% Close the output video file
close(outputVideo);
% Maximize the figure.
hFig.WindowState = "maximized";
% Open the output video
outputVideoFileName = outputVideo.Filename
if ispc
winopen(outputVideoFileName);
end
close(outputVideo);
message = sprintf('Done processing %d frame.', frameCounter);
uiwait(helpdlg(message))
@Image Analyst Hi, thanks, I ran that code but it doesnt seem to be correctly identifying the CoG of the 3 landmarks. See attached frame screenshot
Then you better examine the x1, x2, and x3. The x1 looks especially suspicious.
in what way can you tell x1 looks suspicious?

Connectez-vous pour commenter.

Plus de réponses (0)

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by