Adjuvant Therapy Reduces the Benefit of Palliative Treatment in Disseminated Breast Cancer

based on our PIO cancer registry I was helping to conduct the above titled article (PubMed-Link).


Background: Adjuvant treatment concepts have improved the 10-year cure rate of breast and colon cancer, but new treatments for metastatic disease have yielded only incremental benefit. If treatments for disseminated cancer were actually prolonging life rather than only increasing remission rates, this effect should have been documented over the last 30+ years. However, published data concerning advances in treatment for disseminated cancer have been contradictory.

Patients and Methods: To add data-based information, we analyzed 2 sources: a regional population-based cancer registry (Hamburgisches Krebsregister, HKR), and a research cancer registry (Projektgruppe Internistische Onkologie, PIO). We compared the survival of several thousand patients with metastatic disease who received treatment only after dissemination with that of patients who received initial adjuvant therapy.

Results: After adjuvant treatment, survival in patients with disseminated breast cancer is up to a third shorter than that of patients without adjuvant therapy.

Conclusions: In accordance with published evidence, we conclude that ineffective adjuvant treatment shortens survival after documentation of metastatic disease. This is probably due to the elimination of chemo-sensitive tumor cells or to the induction of resistance in remaining micrometatases. This negative effect on survival after dissemination has been shown clearly for breast cancer and is also probable for cancer of the colon and other sites. © 2013 S. Karger GmbH, Freiburg.

ONCOReg – Neue Registerstudie

Seid einiger Zeit läuft die Rekrutierung der Registerstudie ONCOReg, deren Leitung der klinischen Prüfung ich übernommen habe.

ONCOReg ist ein Register zur Versorgungsforschung und Qualitätssicherung im Sektor niedergelassener onkologisch tätiger Ärzte.

ONCOReg ermöglicht die nicht-interventionelle, longitudinale und epidemiologische Analyse administrativer und klinischer Daten als Qualitätssicherungsinstrument zur Darstellung der Behandlungsrealität in der onkologischen Praxis.
Ziel ist, niedergelassene Ärzte mit einem Schwerpunkt in der Behandlung onkologischer oder hämatologischer Erkrankungen dabei zu unterstützen, die Daten der eigenen Patienten zu dokumentieren und zu analysieren sowie in Relation zu Praxengruppen bzw. klinischen Studien zu setzen.

ONCOReg wurde unter der Kennung  DRKS00004818 beim Deutschen Register Klinischer Studien registriert.

SharePoint Foundation 2013: Hoher Verbrauch von Arbeitsspeicher durch noderunner.exe


Bei der Vorbereitung der Migration eines Dokumentationsarchivs auf SharePoint Foundation 2013 fiel schon kurz nach Aufsetzen einer virtuellen Maschine deren hohe RAM-Auslastung ins Auge, welche fast ausschließlich durch den mehrfach ausgeführten Prozess “noderunner.exe” verursacht wurde. Hier wurden pro Prozess bis zu 650 MB RAM und mehr belegt, was bei fünf gleichzeitig agierenden Prozessen schnell einen RAM-Gebrauch jenseits der 2,5 GB ausmachte.

Die multiplen noderunner Prozesse repräsentieren die verschiedenen Komponenten der aktuellen Suchtopologie in SharePoint 2013. Einen ausgezeichneten Überblick über die aktuelle Searchtopologie gibt der Blogeintrag “Search is Everywhere!” von Corey Roth.

Die Lösung des Problems besteht in der Herabstufung des Performance Levels des Suchdienstes unter Verwendung des folgenden Power Shell Befehls:

   1: Set-SPEnterpriseSearchService -PerformanceLevel Reduced


Im Anschluss limitiert man den zu verwendenden Arbeitsspeicher des Suchdienstes in dem man die noderunner-Konfiguration unter C:\Program Files\Windows SharePoint Services\15.0\Search\Runtime\1.0\noderunner.exe.config öffnet und im folgenden Tag das memoryLimitMegabyxte Attribut von 0 auf wenigstens 250 ändert:

   1: <nodeRunnerSettings memoryLimitMegabytes="250" />


Nach einem Neustart lässt sich beobachten, das sich Summe des genutzten Arbeitsspeichers durch den Suchdienst nun in einem vertretbaren Rahmen bewegt.


Extending Reporting Services with survival charts using a custom report item

Still searching for the best solution to integrate Kaplan Meier survival estimates into Reporting Services based reports I finally came up with the development of a custom report item. Inspired by and based on the polygons example that is available as a custom report item sample for SQL Server 2008 I put together all my previous efforts in creating Kaplan-Meier Charts and calculating survival estimates.


The goal was to implement a custom report item that is able to consume a data set containing an identifier (e.g patient id), a survival period (float), a survival status (bit) and a group label (string) and create a feature-rich Kaplan-Meier Plot. The following picture shows an example giving the overall survival of patients suffering from multiple myeloma grouped by their initial treatment.


The custom report item should have a design-time component as a control that can be used in the Visual Studio Report Designer environment.

Because I was not able to figure out how to create chart series and access the data within the custom report item run-time component using the Chart Class in Microsoft.ReportingServices.OnDemandReportRendering I decided to implement the plotting using the windows forms chart controls. Maybe someone can leave a comment if there is a documentation I overlooked.


The custom report item has been developed and tested against SQL Server 2008 R2. My Visual Studio solution contains three projects: One Project for the run-time component, one for the design-time component and a third one that contains all the methods for chart rendering and calculations. Within the design-time component these rendering methods are used to give the user a preview image.

To install the custom report item follow these steps:

Designer Extension:

Copy Assemblies to “%PROGRAMFILES%\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies”. In the same Folder modify “RSReportDesigner.config”


    <ReportItem Name="KMChart" Type="biobits.cri.kmanalysis.KMChart,biobits.cri.kmanalysis"/>
    <ReportItem Name="KMChart" Type="biobits.cri.kmchartdesigner.KMChartDesigner, biobits.cri.kmchartdesigner" />

before the closing </Extensions> tag.

Add the kmchartdesigner to the Visual Studio Toolbox:

  • Right-click the Visual Studio Toolbox.
  • Select Choose items.
  • Navigate to the “%PROGRAMFILES%\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies” folder and select biobits.cri.kmchartdesigner.dll.

Report Server:

Navigate to “%PROGRAMFILES%\Microsoft SQL Server\MSRS10_50.MSSQLSERVER\Reporting Services\ReportServer\” and copy “biobits.cri.kmanalysis.dll” and “biobits.cri.kmchart.dll” into the “bin” Folder

Open the reportserver.config file and place

    <ReportItem Name="KMChart" Type="biobits.cri.kmanalysis.KMChart,biobits.cri.kmanalysis"/>

Before the closing </Extensions> tag.

Open the rssrvpolicy.config and add

<CodeGroup class="UnionCodeGroup" version="1"
    Description="This code group grants biobits.cri.kmanalysis.dll FullTrust permission. ">
    <IMembershipCondition class="UrlMembershipCondition" version="1"
        Url="c:\Program Files\Microsoft SQL Server\MSRS10_50.MSSQLSERVER\Reporting Services\ReportServer\bin\biobits.cri.kmanalysis.dll" />

Finally restart the Reporting Services service.

User experience

After adding the KMChart Report Item to your report you will see a preview image that is rendered based on some default data integrated into the chart control. To edit a report using the kmchartdesigner custom control in Visual Studio, you can do any of the following:

Set the properties of the KMChart control in the property browser.


Edit properties through the control’s shortcut menu.


Drag fields onto the drop areas of the control from the fields list.




Using a custom report item finally turns out as the best solution for implementing advanced reporting needs in SSRS. Although I had a hard time to figure out all the nuts and bolts of a report lifecycle (especially the data part) it is worth the effort to get a reusable chart item for survival analysis. A download of the visual studio solution will be available soon.

.NET Bio 1.01 now available

.NET Bio the formerly Microsoft Biology Foundation is a open-source language-neutral bioinformatics toolkit built as an extension to the Microsoft .NET Framework. Applications written for this platform can be implemented in a variety of .NET languages, including C#, F#, Visual Basic®, .NET, and IronPython

This release fixes a range of bugs reported in version 1.0 as well as adding new new features such as AB1 and SFF file format parsing, better output and help for command-line tools.

For further information and downloads of the .NET Bio project see

Using a CLR User-Defined Aggregate in SQL Server for calculating and plotting Kaplan-Meier survival estimates


Inspired by the use of User-Defined Table Types I was looking for a way to pass a whole result set to a CLR user-defined function. The aim was to let the database server (SQL Server 2012) create a survival plot using the Kaplan-Meier estimator. A table valued parameter seems to be a promising way but unfortunately you are not able to pass a user-defined table type as a table-valued parameter to a managed stored procedure or function executing in the SQL Server process.  So, how to deal with the need to pass a complete result set into a user defined function?


The idea was to create a CLR User-Defined Aggregate to read the data into a custom “Survival” object that will be passed as parameter into a downstream CLR function to create the survival curve.  The resulting chart should be created by using windows forms chart controls. The result of the user-defined aggregate will return a varbinary(max) data type that contains the image.

The resulting chart should look like this one:

Kaplan Meier survival curve


Chart Control

These chart controls are available as a separate download if you a have to use .Net 3.5 in SQL Server 2008 R2.  If you are developing against SQL Server 2012 you are lucky because System.Windows.Forms.DataVisualization.Charting is included in the .Net 4.0 Framework.

SurvInfo object for survival data

Our SurvInfo object will hold all the survival times, the survival status and the grouping attribute. Furthermore it will hold the survival probability (s_i) and the number of “patients at risk” (n_i) at timepoint time.

Because our data has to be serialized during aggregation our SurvInfo Class has to be serializable.

public class SurvInfo
        public double time { get; set; }
        public int status { get; set; }
        public string group { get; set; }
        public double s_i { get; set; }
        public int n_i { get; set; }

        public SurvInfo()

Custom Aggregate

An aggregate is created by defining a class tagged with the  SqlUserDefinedAggregate attribute. Using the attribute, we are able to define the following options:

  • Format: Serialization format for the class. This is typically either Native or UserDefined. In case of Native format, the framework handles all the necessary steps to serialize and deserialize the structure. Aggregates using format UserDefined has to implement and handle their own serialization (see below).
  • IsInvariantToDuplicates (bool): Whether or not duplicate values affect the result
  • IsInvariantToNulls (bool): Whether or not NULL values affect the result
  • IsInvariantToOrder (bool):  Whether or not the order of the rows affects the result
  • IsNullIfEmpty (bool): Whether or not an empty result set produces NULL or a value
  • Name (string): Sets the name of the aggregate

The class itself must contain the following methods:

  • Init:  Init is called when a new group of values is going to be handled using an instance of the class
  • Accumulate: Each value is passed to the Accumulate method that is responsible for making the necessary calculations. Here we will fill our SurvInfo object
  • Merge: This method is used to merge the data after the original set of values is divided into several independent groups in parallel operations.
  • Terminate: Terminate returns the result and contains any further methods based on the aggregated data. Here we will place our method to generate the chart .

Because we are dealing with custom data types we have to implement the IBinarySerialize interface in our custom aggregate.  This will require implementing the “Read” and “Write” method.  User-defined aggregates must be marked with Format UserDefined and handle their own serialization.  We are using the BinaryFormatter class to serialize and deserialize our custom object.

 Write method:

public void Write(BinaryWriter writer)
        MemoryStream ms = new MemoryStream();
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(ms, this.TimeList); /* We are serializing our SurfInfo object “TimeList”*/

Read method:

public void Read(BinaryReader reader)
      MemoryStream ms = new MemoryStream((byte[])reader.ReadBytes(Convert.ToInt32(reader.BaseStream.Length)));
         BinaryFormatter bf = new BinaryFormatter();
         this.TimeList = (List<SurvInfo>)bf.Deserialize(ms); /*object has to be desirialized from memory stream*/

Survival Chart

The Chart itself is generated by iteration through the SurvInfo Object and calculation the survival probability for each time point. Please refer to the code sample for further description.

Sql Server Configuration and Registering

To have our custom aggregate generate the chart we have to enable clr-integration and set the trustworthy property to on.  Additionally we need to register some dependent assemblies:

sp_configure 'show advanced options', 1;
sp_configure 'clr enabled', 1;



CREATE ASSEMBLY [System.Drawing]
from 'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\System.Drawing.dll'

CREATE ASSEMBLY [System.Windows.Forms.DataVisualization]
from 'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\'

CREATE ASSEMBLY [System.Windows.Forms]
from 'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\'

CREATE ASSEMBLY [Accessibility]
from 'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\accessibility.dll'

Now we have to register our custom aggregate

FROM 'C:\Data\Temp\biobits.KMChart.Core.dll' /*where c:\data\temp is the folder containing your dll*/

And create our custom Aggregate

CREATE Aggregate dbo.KMAgg(@time float,@status bit,@group nvarchar(150))
RETURNS varbinary(max)
EXTERNAL NAME [biobitsKMChartCore].[biobits.KMChart.Core.KMAgg]

Finally, we perform a test

drop table survdata
create table survdata (ID int,[time] float,[status] int,[group] nvarchar(150))

insert survdata values (9,0.8,0,'Group A')
insert survdata values (7,0.9,1,'Group A')
insert survdata values (12,1.6,0,'Group A')
insert survdata values (13,1.6,0,'Group A')
insert survdata values (14,1.6,0,'Group A')
insert survdata values (3,1.7,1,'Group B')
insert survdata values (1,1.8,1,'Group B')
insert survdata values (8,1.9,1,'Group A')
insert survdata values (10,2.5,1,'Group B')
insert survdata values (2,2.8,1,'Group B')
insert survdata values (11,2.9,0,'Group A')
insert survdata values (5,3.8,1,'Group B')
insert survdata values (6,3.9,0,'Group B')
insert survdata values (4,4.9,0,'Group A')

select dbo.KMAgg(s.time,s.status, from survdata s


We developed a SQL Server user defined custom aggregate that aggregates multiple columns into a Kaplan-Meier Chart that is returned as binary data. This method overcomes the restriction of passing table valued parameters into a clr enabled function.

The downside of this method is the use of the aggregate itself. Passing parameters that are not of type “column” must be handled as a column that is passed to the user defined aggregate. For shure this solution will run into performance issues when dealing with a huge amount of data.

Code Sample

Reporting Services – List all Reports that are used as Subreport

Today I wanted to find out if a report is used as a subreport. In my project I am heavily using subreports so doing the search manually was not an option. I came up with a solution that queries the rdl-code stored in the ReportServer database to give me a complete List of Reports that are used as subreport or find a specific subreport and its main report.

  1: USE ReportServer
  3: /* declare search variable */
  4: declare @subreport nvarchar(50)
  5: set @subreport= null    /* add name of report to search here;
  6:                          * NULL lists all subreport an their main reports*/
  8: /* declare and populate table variable */
  9: DECLARE @rdltab TABLE (ID uniqueidentifier,Name NVARCHAR(400),Path NVARCHAR(MAX),xmlRDL XML)
 10: INSERT @rdltab
 11: SELECT ItemID,Catalog.Name, Path, CONVERT(XML, CONVERT(VARBINARY(MAX), Content))
 12: FROM Catalog WHERE Type=2
 13: ;
 14: /* find subreport in RS 2008 and 2008 R2 based main reports */
 15: WITH XMLNAMESPACES ('' as rdl)
 16: SELECT distinct Name as MainReport, Path,
 17: sub.value('(text())[1]','varchar(150)') as ReferencedReport
 18: FROM @rdltab
 19: CROSS APPLY xmlRDL.nodes('//rdl:ReportItems') as Items(i)
 20: Cross Apply i.nodes('rdl:Subreport/rdl:ReportName') as Subs(sub)
 21: where
 22: (ISNULL(sub.value('(text())[1]','varchar(150)'),0)=
 23: ISNULL(@subreport, ISNULL(sub.value('(text())[1]','varchar(150)'),0)))
 24: order by ReferencedReport;

Zeilenumbruch in einem SSRS-Ausdruck

Sollte es in einer Textbox innerhalb eines SQL Server Reporting Services Reports notwendig sein einen Zeilenubruch einzufügen, so kann man sich sehr einfach einer vordefinierten VisualBasic Konstante bedienen:

Der Ausdruck vbcrlf, Visual Basic Carriage Return Line Feed, ermöglicht das Einfügen eines Zeilenumbruchs innerhalb eines SSRS Ausdrucks.



…wird wie folgt gerendert:  


A User-Defined function for calculating Kaplan-Meier survival estimates

With SQL-Server 2008 Microsoft introduced the creation of user-defined table types. These table types can be used as parameters, the so called table-valued parameter. Now you have the ability to send multiple rows of data into a stored procedure or function. However,  this feature does not apply to CLR User-Defined functions.

I used this new feature to calculate Kaplan Meier survival estimates on a database level. For the calculation you need the observed survival time, the survival status (eg. dead (1) / alive (0)) and in case you want to compare different groups a grouping factor for each observation. This data could be passed as a custom table type into a user defined function that calculates the cumulated survival rate and its variance for each time point.

First we create our table type:

CREATE TYPE [dbo].[SurvivalTableType] AS TABLE(
    [id_patient] [int] NULL,
    [time] [float] NULL,
    [status] [smallint] NULL,
    [group] [varchar](150) NULL


Now we write our function to calculate the Kaplan-Meier survival estimate. This function will have two parameters.

@survivaldata: The survival data as our custom “SurvivalTableType” table type

@usegroup: A Boolean indicating if the KM-survival should be calculated group wise.


create FUNCTION CalculateKMSurvival 
    -- Add the parameters for the function here
    @survdata SurvivalTableType READONLY ,
    @usegroup as bit
[groupname] nvarchar(150),
[time] float,
[status] smallint, -- Events
num smallint, -- num of values at specific time
n_i int, -- patients at risk
q_i float, -- rate of survivors (qi=(ni-di)/ni)
s_i float,-- survival rate (q1 * q2 * ... * qi)
var_s float)

    declare @N int;declare @S float;declare @n_i int;declare @q_i float;declare @mintime float;
    declare @timepoint float;declare @status int;declare @num int;declare @group nvarchar (150);
    -- local writable copy of our input table
    declare @survlocal table (id_patient int, [time] float, [status] int,[group] nvarchar (150));
    -- copy data into local table
    insert into @survlocal
    select s.id_patient,s.[time],s.[status],[group]=case when s.[group] IS null then 'NA' else s.[group] end from @survdata s
    where [time] is not null 
    if (@usegroup = 0)
            insert @grouptable (groupname) values ('NA')
            update @survlocal
            set [group]= 'NA'
    if (@usegroup = 1)
            insert @grouptable (groupname)
            select distinct [group] from @survlocal
    while (select COUNT (*) from @grouptable) >0
        -- select first or only group
        select Top 1 @group= groupname from @grouptable
        -- determine N and mintime
        select @N=COUNT(id_patient),@mintime=MIN(time) from @survlocal where [time] is not null and [group]=@group 
        -- Set S=1
        Set @S=1
        -- Get List od Timepoints for iterating through values
        Declare @timepoints table(timepoint float)
        insert @timepoints
            select s.time from @survlocal s
            where s.[group]=@group 
            order by s.time
        -- insert only valid survival data into our result set
        insert into @result (groupname,time,status,num)
        select s.[group],s.time,sum(s.status),COUNT(s.id_patient) from @survlocal s
        where s.time is not null and s.status is not null and s.[group]=@group
        group by s.[group],s.time
        order by s.time

        -- iterate through survival data
        while (select COUNT(*) from @timepoints) >0
             select top 1 @timepoint= timepoint from @timepoints order by timepoint
             -- determine nI and qi for this time point
             select @status=r.status, @num=r.num from @result r where r.groupname=@group and r.time=@timepoint
             select @n_i=@N-@num
             select @q_i=(@N-(@status*1.0))/@N
             -- calculate survival rate
             select @S=@S*@q_i
             -- update table
             update @result
             set n_i=@N,q_i=@q_i,s_i=@S ,var_s=@S*SQRT((1-@S)/@N)
             where groupname=@group and time=@timepoint
             -- clean-up
             Set @N=@n_i
             delete from @timepoints where timepoint=@timepoint
            delete from @survlocal where [group]=@group
        delete from @grouptable where groupname =@group

Some test data:

declare @survdata SurvivalTableType
insert @survdata values (4,4.9,0,'NA')
insert @survdata values (9,0.8,0,'NA')
insert @survdata values (7,0.9,1,'NA')
insert @survdata values (12,1.6,0,'NA')
insert @survdata values (13,1.6,0,'NA')
insert @survdata values (14,1.6,0,'NA')
insert @survdata values (3,1.7,1,'NA')
insert @survdata values (1,1.8,1,'NA')
insert @survdata values (8,1.9,1,'NA')
insert @survdata values (10,2.5,1,'NA')
insert @survdata values (2,2.8,1,'NA')
insert @survdata values (11,2.9,0,'NA')
insert @survdata values (5,3.8,1,'NA')
insert @survdata values (6,3.9,0,'NA')

select * from dbo.CalculateKMSurvival(@survdata,0)


We used a custom table type to pass multiple rows of survival data to a User-Defined function that calculates Kaplan-Meier survival estimates.

Microsoft moves BLAST into the Cloud

Last month Microsoft research team announced AzureBlast a case study of a scientific application that was developed for cloud computing. AzureBlast is an massive parallel implementation of the BLAST search engine using the newly released BLAST+ executables.

The general principal to perform parallelization in sequence alignment has not changed with this implementation. Given a number of query sequences, AzureBlast will first split the input sequences into multiple partitions, each of which will be delivered to one worker instance to execute. Once all partitions have been processed, the results will be merged together. For more details see this PDF.

It sounds like a perfect scalable method to support analysis of pyro sequencing results. I think I have to to try it for myself and I will come back to the topic as soon as I have performed some tests.