nsmela / brachify

test app for a brachy applicator builder
GNU General Public License v3.0
0 stars 0 forks source link

Have the script generate the OR reference sheet #19

Closed michaelwkudla closed 11 months ago

michaelwkudla commented 1 year ago

Talk to MK about what this means.

michaelwkudla commented 1 year 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.

michaelwkudla commented 1 year ago

Examples here:

TandC Ref Sheet Ref Sheet

nsmela commented 1 year ago

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?

michaelwkudla commented 1 year ago

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: @.***>

michaelwkudla commented 11 months ago

I have c# code that does most/nearly all of this. Is that useable or should we make it in python?

nsmela commented 11 months ago

We can try. Post it here and I'll let you know if we need more info.

michaelwkudla commented 11 months ago

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