Closed michaelwkudla closed 11 months ago
There should be an option to have a user-defined image for the pdf generated, as well as custom header info in a settings file.
Examples here:
Something to consider: do we want to mark the model to show which channel is which? Like dots or dashes to indicate which channel number a hole is? Would we want to mark anterior and posterior?
For resin prints, eventually! Definitely a "version 2" goal.
On Fri, Oct 13, 2023, 10:15 a.m. NathanSmela @.***> wrote:
Something to cobsider: do we want to mark the model to show which channel is which? Like dots or dashes to indicate which channel number a hole is? Would we want to mark anterior and posterior?
— Reply to this email directly, view it on GitHub https://github.com/nsmela/BrachyApp-PyQt5/issues/19#issuecomment-1761857110, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGEPREWMKLHSOYIIHNC4JJTX7FZMBANCNFSM6AAAAAA5XAGXWA . You are receiving this because you authored the thread.Message ID: @.***>
I have c# code that does most/nearly all of this. Is that useable or should we make it in python?
We can try. Post it here and I'll let you know if we need more info.
See below. Obviously, some of the text would need to have the ability to be changed by the user in an exposed json or XML file. And of course, the "curvelist" info will come from our objects.
A couple of changes to make:
using MigraDoc.DocumentObjectModel.Shapes;
using MigraDoc.Rendering;
using MigraDoc.DocumentObjectModel.Fields;
using MigraDoc.DocumentObjectModel.Tables;
using MigraDoc;
using MigraDoc.DocumentObjectModel;
using PdfSharp.Pdf;
using MigraDoc.Rendering.ChartMapper;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
#region Create Reference Sheet
//setup info.
Document document = new Document();
Table table = new Table();
TextFrame addressFrame = new TextFrame();
MigraDoc.DocumentObjectModel.Color TableBorder = Colors.Black;
MigraDoc.DocumentObjectModel.Color TableBlue = Colors.LightBlue;
MigraDoc.DocumentObjectModel.Color TableGray = Colors.Gray;
MigraDoc.DocumentObjectModel.Color MURed = Colors.Red;
MigraDoc.DocumentObjectModel.Color TableYellow = Colors.Yellow;
MigraDoc.DocumentObjectModel.Color TableGreen = Colors.LightGreen;
string logo = @"U:\Visual Studio 2013\Projects\PlanSummaryPDF\PlanSummaryPDF\CCSILogo.png";
//Create the pdf
public void CreateReferencePDF(PatientInfo Patient1, string filepath, string logo)
{
// Create a new MigraDoc document
document.Info.Title = "PSCT OR Reference Sheet";
document.Info.Subject = "PSCT OR Reference Sheet";
document.Info.Author = "BC Cancer - Kelowna";
string refsheetfilename = System.IO.Path.Combine(filepath, "ReferenceSheet.pdf");
string pngfilename = System.IO.Path.Combine(filepath, "BaseDiagram.png");
//Set up the styles for the document
DefineStyles();
//Create the page (just the logo and the header table which has basic patient and plan information)
CreatePage(Patient1, logo);
//Create a table which shows each catheter length
createTable(Patient1);
//Create the base diagram
CreateBaseDiagram(Patient1, pngfilename);
PdfDocumentRenderer pdfRenderer = new PdfDocumentRenderer(false){Document = document};
pdfRenderer.RenderDocument();
pdfRenderer.PdfDocument.Save(@refsheetfilename);
System.Diagnostics.Process.Start(refsheetfilename);
}
// Sets the default styles for the page
void DefineStyles()
{
// Get the predefined style Normal.
Style style = this.document.Styles["Normal"];
// Because all styles are derived from Normal, the next line changes the
// font of the whole document. Or, more exactly, it changes the font of
// all styles and paragraphs that do not redefine the font.
style.Font.Name = "Verdana";
style = this.document.Styles[StyleNames.Header];
style.ParagraphFormat.AddTabStop("16cm", MigraDoc.DocumentObjectModel.TabAlignment.Right);
style = this.document.Styles[StyleNames.Footer];
style.ParagraphFormat.AddTabStop("8cm", MigraDoc.DocumentObjectModel.TabAlignment.Center);
// Create a new style called Table based on style Normal
style = this.document.Styles.AddStyle("Table", "Normal");
style.Font.Name = "Verdana";
style.Font.Name = "Times New Roman";
style.Font.Size = 12;
// Create a new style called Reference based on style Normal
style = this.document.Styles.AddStyle("Reference", "Normal");
style.ParagraphFormat.SpaceBefore = "5mm";
style.ParagraphFormat.SpaceAfter = "5mm";
style.ParagraphFormat.TabStops.AddTabStop("16cm", MigraDoc.DocumentObjectModel.TabAlignment.Right);
}
//Used to create the page, patient info table, and the logo.
void CreatePage(PatientInfo Patient1, string logopath)
{
// Each MigraDoc document needs at least one section.
Section section = document.AddSection();
section.PageSetup.PageFormat = PageFormat.Letter;
// Put a logo in the header
//Input the header image
MigraDoc.DocumentObjectModel.Shapes.Image image = section.Headers.Primary.AddImage(@logopath);
image.Height = "2.0cm";
image.LockAspectRatio = true;
image.RelativeVertical = RelativeVertical.Line;
image.RelativeHorizontal = RelativeHorizontal.Margin;
image.Top = ShapePosition.Top;
image.Left = ShapePosition.Left;
image.WrapFormat.Style = WrapStyle.Through;
//New paragraph details
Paragraph paragraph = section.AddParagraph(); // section.Footers.Primary.AddParagraph();
paragraph.Format.Font.Size = 12;
paragraph.Format.Font.Bold = true;
paragraph.Format.Alignment = ParagraphAlignment.Center;
paragraph.Format.Font.Name = "Times New Roman";
paragraph.Format.Font.Size = 12;
paragraph.Format.SpaceAfter = 3;
//not sure why the next 2 lines are needed here. they are above as well?
paragraph = section.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
paragraph.Style = "Reference";
paragraph.AddFormattedText("Patient Specific Cylindrical Template - Reference Sheet", MigraDoc.DocumentObjectModel.TextFormat.Bold);
paragraph.AddLineBreak();
//Add a table of all of the relavent patient details. (left as-is for now)
table = section.AddTable();
table.Style = "Table";
table.Borders.Width = 0.25;
table.Borders.Left.Width = 0.5;
table.Borders.Right.Width = 0.5;
table.Rows.LeftIndent = "0 cm";
table.Format.Font.Size = 11;
//Add all the columns first
Column column = table.AddColumn("7.5 cm"); // 15.8 total width is good
column.Format.Alignment = ParagraphAlignment.Right;
column = table.AddColumn("4 cm");
column.Format.Alignment = ParagraphAlignment.Center;
column = table.AddColumn("4 cm");
column.Format.Alignment = ParagraphAlignment.Center;
//add the rows and cell content details
Row row = table.AddRow();
row.Cells[0].AddParagraph("Name: " + Patient1.PatientName ); //cell content
row.Cells[0].Format.Alignment = ParagraphAlignment.Left;
row.Cells[1].AddParagraph("Patient ID: " + Patient1.PatientID);
row.Cells[1].Format.Alignment = ParagraphAlignment.Left;
row.Cells[2].AddParagraph("Plan ID: " + Patient1.PlanID);
row.Cells[2].Format.Alignment = ParagraphAlignment.Left;
row.Format.Font.Bold = true;
row.Shading.Color = TableBlue;
table.SetEdge(0, 0, 3, 1, MigraDoc.DocumentObjectModel.Tables.Edge.Box, MigraDoc.DocumentObjectModel.BorderStyle.Single, 0.5, MigraDoc.DocumentObjectModel.Color.Empty);
}
// Used to insert the catheter info Table
void createTable(PatientInfo Patient1)
{
//Add a new paragraph (basically start a new line)
Paragraph paragraph = document.LastSection.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
table = document.LastSection.AddTable();
table.Style = "Table";
table.Borders.Color = TableBorder;
table.Borders.Width = 0.25;
table.Borders.Left.Width = 0.5;
table.Borders.Right.Width = 0.5;
table.Format.Font.Size = 11;
table.Rows.LeftIndent = "0 cm";
// Before you can add a row, you must define the columns
// insert 3 columns (catheter #, extension length from base of catheter, and total insertion length)
Column column = table.AddColumn("3cm");
column.Format.Alignment = ParagraphAlignment.Right;
column = table.AddColumn("10.0 cm");
column.Format.Alignment = ParagraphAlignment.Center;
//column = table.AddColumn("5.0 cm");
//column.Format.Alignment = ParagraphAlignment.Center;
//add a table title/header row
Row row = table.AddRow();
row.Shading.Color = TableBlue;
row.Cells[0].AddParagraph("Catheter Protrusion Lengths (Plastic Portion Only)");
row.Format.Font.Bold = true;
row.Cells[0].MergeRight = 1; //This is the number of cells to the right of the identified cells ( [0] in this case) to be merged into the current cell. If this index is more than the total number of cells, there will be an exception.
row.Cells[0].Format.Alignment = ParagraphAlignment.Center;
//a titles row
row = table.AddRow();
row.Format.Font.Bold = true;
row.Cells[0].AddParagraph("Catheter #");
row.Cells[1].AddParagraph("Catheter Protrusion From Base (cm)");
//row.Cells[2].AddParagraph("Catheter Insertion Past Base (cm)");
row.Cells[0].Format.Alignment = ParagraphAlignment.Center;
row.Cells[1].Format.Alignment = ParagraphAlignment.Center;
//row.Cells[2].Format.Alignment = ParagraphAlignment.Center;
//Add a row for each catheter
int i = 0;
double dx;
double dy;
double dz;
foreach (Curve curve in Patient1.Applicator.CurveList) {
i += 1;
row = table.AddRow();
row.Cells[0].AddParagraph(i.ToString());
row.Cells[0].Format.Alignment = ParagraphAlignment.Center;
//calculate the length inserted pase the applicator base
double insertionlength = 0;
double extensionlength = 0;
for (int j = 1; j < curve.CathPath.Count(); j++ )
{
double[] posstart = curve.CathPath[j - 1];
double[] posend = curve.CathPath[j];
dx = posend[0] - posstart[0];
dy = posend[1] - posstart[1];
dz = posend[2] - posstart[2];
insertionlength += Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
insertionlength = Math.Round(insertionlength, 0)/10;
extensionlength = 30.5 - insertionlength;
//fill the cells
row.Cells[1].AddParagraph(extensionlength.ToString());
row.Cells[1].Format.Font.Bold = true;
row.Cells[1].Format.Alignment = ParagraphAlignment.Center;
}
}
// Used to create a diagram of the base of the applicator.
void CreateBaseDiagram(PatientInfo Patient1, string basediagramfilename)
{
//start a new bitmap
int bmpwidth = 500;
int bmpheight = 500;
int OD = Convert.ToInt32(Patient1.Applicator.CylinderOD) * 10;
Bitmap bm = new Bitmap(bmpwidth, bmpheight); //this size may be too small, but we'll try it for now.
using (Graphics gr = Graphics.FromImage(bm))
{
//Make the graphics and text look smooth
gr.SmoothingMode = SmoothingMode.AntiAlias;
gr.TextRenderingHint = TextRenderingHint.AntiAlias;
// Make the background white.
gr.Clear(System.Drawing.Color.White);
//Each ellipse is drawn using a "Pen" and using a "rect". Remember that bitmap coordinates are X+ to the right, Y+ down.
//rect:
int ODcenterX = bmpwidth / 2;
int ODcenterY = bmpheight / 2;
int rectX = ODcenterX - OD / 2;
int rectY = ODcenterY - OD / 2;
Rectangle ODrect = new Rectangle(rectX, rectY, OD, OD);
Pen pen = new Pen(Brushes.Black);
gr.DrawEllipse(pen, ODrect); //draw the outer circle
Pen pen2 = new Pen(Brushes.Black, 10);
Rectangle outlinerect = new Rectangle(0, 0, bmpwidth-1, bmpheight-1);
gr.DrawRectangle(pen, outlinerect); //draw the frame
//Write ANT and POST
string ant = "Anterior";
string post = "Posterior";
System.Drawing.Font font = new System.Drawing.Font("Times New Roman", 12);//set the font and height
Brush brush = new SolidBrush(System.Drawing.Color.Black);//a black brush is used to draw the number
gr.DrawString(ant, font, brush, ODcenterX-30, ODcenterY - OD/2 - 30);
gr.DrawString(post, font, brush, ODcenterX-30, ODcenterY + OD/2 + 15);
//OD of each hole
int ODhole = 3 * 10;
//counter for which hole we are on
int holenumber = 0;
//here we draw each hole, and write the number of that hole inside the hole
foreach(Curve curve in Patient1.Applicator.CurveList)
{
//all the setup info
holenumber += 1;
double[] hole = curve.CathPath[0];
int holecenterX = Convert.ToInt32(hole[0])*10 + 100; //Convert to mm and add 100 so it ends up near the middle?
int holecenterY = Convert.ToInt32(hole[2])*10 + 100;
//Since we want our image to be looking at the bottom of the cylinder, we need to reflect the image about the X axis. The X-centers will be the same, but the Y's reflect.
holecenterY = bmpheight - holecenterY;
int holecornerX = holecenterX - ODhole / 2;
int holecornerY = holecenterY - ODhole / 2;
//each hole starts as a rectangle (upper left corner, width, and height)
Rectangle holerect = new Rectangle(holecornerX, holecornerY, ODhole, ODhole);
gr.DrawEllipse(pen, holerect);//draw the circle
//write the number in the hole
StringFormat string_format = new StringFormat(); //not sure if this is really needed
string_format.Alignment = StringAlignment.Center; //not sure if this is really needed
string_format.LineAlignment = StringAlignment.Center; //not sure if this is really needed
string txt = holenumber.ToString(); // the text in the hole will be the current hole number (this must be a string)
gr.DrawString(txt, font, brush, holecornerX + 8, holecornerY + 8); //draw the number
}
}
bm.Save(basediagramfilename);
//Add some extra lines
Paragraph paragraph2 = document.LastSection.AddParagraph();
Paragraph paragraph3 = document.LastSection.AddParagraph();
MigraDoc.DocumentObjectModel.Shapes.Image image2 = paragraph3.AddImage(basediagramfilename);
image2.Height = "12.0cm";
image2.LockAspectRatio = true;
image2.RelativeVertical = RelativeVertical.Line;
image2.RelativeHorizontal = RelativeHorizontal.Margin;
image2.Top = ShapePosition.Top;
image2.Left = ShapePosition.Left;
image2.WrapFormat.Style = WrapStyle.Through;
}
#endregion
Talk to MK about what this means.